Winit: cursor grab fails in fullscreen on windows

Created on 18 Jun 2018  路  13Comments  路  Source: rust-windowing/winit

When I add the line window.set_cursor(winit::CursorState:Grab).unwrap() after the window build in fullscreen example and run it on windows then it fails.

The window does not respond and windows ask to quit or wait.

hard Windows waiting bug

Most helpful comment

It looked like a deadlock was happening somewhere, and the results are a bit amusing.

As you might've heard by now, we block during resize events on Windows. However, we only unblock in run_forever/poll_events. Entering fullscreen triggers a resize, and thus a block. This means that all event processing is frozen in the duration between creating a fullscreen window and starting run_forever/poll_events execution, and due to some other fun complexities of our Windows implementation, cursor grabbing requires posting a message to the event queue. set_cursor_state doesn't return until it gets a result, but since event processing is still held up in the WM_SIZE handler, your program freezes.

Here's a really goofy temporary workaround you can use:

extern crate winit;

use winit::{ControlFlow, Event, WindowEvent};

fn main() {
    let mut event_loop = winit::EventsLoop::new();
    let monitor = event_loop.get_primary_monitor();
    let window = winit::WindowBuilder::new()
        .with_fullscreen(Some(monitor))
        .build(&event_loop)
        .unwrap();

    let mut run_count = Some(0);

    event_loop.run_forever(|event| {
        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::Resized(_) => {
                    if let Some(count) = run_count {
                        let dpi_factor = window.get_hidpi_factor();
                        if (dpi_factor == 1.0 && count == 0)
                        || (dpi_factor != 1.0 && count == 1) {
                            window.grab_cursor(true).unwrap();
                            run_count = None;
                        }
                    }
                },
                WindowEvent::CloseRequested
                | WindowEvent::KeyboardInput {
                    input: winit::KeyboardInput {
                        virtual_keycode: Some(winit::VirtualKeyCode::Escape),
                        ..
                    }, ..
                } => return ControlFlow::Break,
                _ => (),
            },
            _ => (),
        }
        run_count = run_count.map(|n| n + 1);
        ControlFlow::Continue
    });
}

@Osspial are you still down to implement #459 on Windows? I'm ready to start this migration if you are.

All 13 comments

I think you mean: window.set_cursor_state(winit::CursorState::Grab).unwrap()

I have the same issue on Windows 10
It also occurs with CursorState::Hide.

The issue occurs when with_fullscreen(Some(..)) is used to create a window while cursor is Grab or Hide.
The issue also occurs when the cursor is Grab or Hide and then set_fullscreen(Some(..)) is called.

It looked like a deadlock was happening somewhere, and the results are a bit amusing.

As you might've heard by now, we block during resize events on Windows. However, we only unblock in run_forever/poll_events. Entering fullscreen triggers a resize, and thus a block. This means that all event processing is frozen in the duration between creating a fullscreen window and starting run_forever/poll_events execution, and due to some other fun complexities of our Windows implementation, cursor grabbing requires posting a message to the event queue. set_cursor_state doesn't return until it gets a result, but since event processing is still held up in the WM_SIZE handler, your program freezes.

Here's a really goofy temporary workaround you can use:

extern crate winit;

use winit::{ControlFlow, Event, WindowEvent};

fn main() {
    let mut event_loop = winit::EventsLoop::new();
    let monitor = event_loop.get_primary_monitor();
    let window = winit::WindowBuilder::new()
        .with_fullscreen(Some(monitor))
        .build(&event_loop)
        .unwrap();

    let mut run_count = Some(0);

    event_loop.run_forever(|event| {
        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::Resized(_) => {
                    if let Some(count) = run_count {
                        let dpi_factor = window.get_hidpi_factor();
                        if (dpi_factor == 1.0 && count == 0)
                        || (dpi_factor != 1.0 && count == 1) {
                            window.grab_cursor(true).unwrap();
                            run_count = None;
                        }
                    }
                },
                WindowEvent::CloseRequested
                | WindowEvent::KeyboardInput {
                    input: winit::KeyboardInput {
                        virtual_keycode: Some(winit::VirtualKeyCode::Escape),
                        ..
                    }, ..
                } => return ControlFlow::Break,
                _ => (),
            },
            _ => (),
        }
        run_count = run_count.map(|n| n + 1);
        ControlFlow::Continue
    });
}

@Osspial are you still down to implement #459 on Windows? I'm ready to start this migration if you are.

@francesca64 I am, yes. Sorry about disappearing for a few weeks there! I'm going to be away from any Windows machines until the end of the month starting next week, but I'll try to make the most progress I can in the next couple days.

Ran into this same deadlock when resizing a window, which triggered a Focused, which my game uses to know when to grab the cursor.

I added the code to winit that blocks during windows resize events, in order to fix visual bugs that happened when resizing a window (see #250). I don't think that's as important as making sure programs don't freeze, though! I'm pretty sure getting rid of that block would not only fix this but also fix #391, so we may want to remove it in the meantime while #459 gets hammered out.

@Osspial if it's only a visual issue, then I'd definitely be in favor of that.

Removing that code should be pretty simple so I'll see if I can get a PR up in the next day or so.

It seems that this might be related to a freeze when I try to resize the window on Windows builds. No issues happen on Mac or Linux. A snippet to reproduce, using the latest pull from the winit repo:

extern crate winit;

fn main() {

    let mut events = winit::EventsLoop::new();
    let window = winit::Window::new(&events).unwrap();

    while true {
        events.poll_events(|event| {
            match event {
                // Grab mouse cursor if window is focused; release otherwise
                winit::Event::WindowEvent {
                    event: winit::WindowEvent::Focused(focused),
                    ..
                } => {
                    if let Err(e) = window.grab_cursor(focused) {
                        eprintln!("{}", e);
                    }
                },
                _ => {}
            }
        });
    }
}

A sample stacktrace from debug calls:

[Inline Frame] demo.exe!std::sys::windows::c::SleepConditionVariableSRW() Line 78 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys\windows\compat.rs:78) [Inline Frame] demo.exe!std::sys::windows::condvar::Condvar::wait() Line 32 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys\windows\condvar.rs:32) [Inline Frame] demo.exe!std::sys_common::condvar::Condvar::wait() Line 51 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sys_common\condvar.rs:51) [Inline Frame] demo.exe!std::sync::condvar::Condvar::wait() Line 214 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\condvar.rs:214) demo.exe!std::thread::park() Line 885 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\thread\mod.rs:885) demo.exe!std::sync::mpsc::blocking::WaitToken::wait() Line 81 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\blocking.rs:81) demo.exe!std::sync::mpsc::oneshot::Packet<core::result::Result<(), alloc::string::String>>::recv<core::result::Result<(), alloc::string::String>>(core::option::Option<std::time::Instant> self) Line 163 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\oneshot.rs:163) demo.exe!std::sync::mpsc::Receiver<core::result::Result<(), alloc::string::String>>::recv<core::result::Result<(), alloc::string::String>>() Line 1203 (d:\rustc\d586d5d2f51489821b471f20959333558c24b129\src\libstd\sync\mpsc\mod.rs:1203) demo.exe!winit::platform::platform::window::Window::grab_cursor(bool self) Line 397 \.cargo\git\checkouts\winit-5993d63686bf7f83\214e157\src\platform\windows\window.rs:397)

@Osspial do you know if the issue I've posted above is related to the blocking issue? It seems that you're one of the people who are most knowledgeable about the Windows backend.

@hobogenized I don't think it's related. The previous issue would freeze the entire program, preventing Winit windows from being resized. It seems like a soft-lock is happening here that's still letting Windows process events (so resizing still works), it's just that those events aren't getting delivered to Winit.

@Osspial Thanks for the feedback. Should I open up a new issue for easier tracking?

@hobogenized Sure, that would be great!

We can also close this issue since the issue it describes is resolved. cc @francesca64

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JDTX picture JDTX  路  3Comments

alexheretic picture alexheretic  路  4Comments

coderhwz picture coderhwz  路  3Comments

k0nserv picture k0nserv  路  3Comments

chemicstry picture chemicstry  路  3Comments