diff --git a/devices/video/display_sdl.cpp b/devices/video/display_sdl.cpp index 9262cb8ca9..486ca4ef1f 100644 --- a/devices/video/display_sdl.cpp +++ b/devices/video/display_sdl.cpp @@ -49,6 +49,10 @@ class Display::Impl { SDL_Texture* disp_texture = 0; SDL_Texture* cursor_texture = 0; SDL_Rect cursor_rect; // destination rectangle for cursor drawing + bool show_host_cursor = true; // desired SDL host pointer visibility + bool guest_cursor_drawn = false; // guest is drawing its own cursor + bool was_grabbed = false; // last reported grab state + bool manual_grab = false; // grab toggled on with Ctrl+G int display_w; int display_h; double drawable_w; @@ -312,6 +316,14 @@ void Display::handle_events(const WindowEvent& wnd_event) { if (wnd_event.window_id == impl->disp_wnd_id) { SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); SDL_SetWindowKeyboardGrab(impl->display_wnd, SDL_TRUE); + // When the window (re)gains focus (e.g. the user clicks it), SDL + // may have dropped the relative mouse grab on focus loss. Re-grab + // it so the guest cursor keeps working; this is the reliable + // re-grab trigger instead of polling every frame. + if (impl->guest_cursor_drawn && !impl->manual_grab && !SDL_GetRelativeMouseMode()) { + SDL_SetRelativeMouseMode(SDL_TRUE); + this->update_window_title(); + } } break; @@ -394,9 +406,11 @@ void Display::toggle_mouse_grab() { if (SDL_GetRelativeMouseMode()) { SDL_SetRelativeMouseMode(SDL_FALSE); + impl->manual_grab = false; } else { this->update_mouse_grab(true); SDL_SetRelativeMouseMode(SDL_TRUE); + impl->manual_grab = true; } } @@ -434,7 +448,9 @@ void Display::update_window_title() std::to_string(impl->display_w) + "x" + std::to_string(impl->display_h) + " " + std::to_string(int(std::round(impl->renderer_scale_x * 100))) + "%"; if (is_grabbed) - new_window_title += " (🖱 Grabbed)"; + new_window_title += " (Grabbed, Ctrl+G to release)"; + else if (impl->guest_cursor_drawn) + new_window_title += " (Press Ctrl+G to grab)"; if (new_window_title != old_window_title) SDL_SetWindowTitle(impl->display_wnd, new_window_title.c_str()); @@ -466,6 +482,45 @@ void Display::update(std::function conver SDL_RenderClear(impl->renderer); SDL_RenderCopy(impl->renderer, impl->disp_texture, NULL, &impl->dest_rect); + bool is_grabbed = SDL_GetRelativeMouseMode(); + + // The guest draws its own cursor into the framebuffer (hardware cursor). + // When such a cursor session starts, auto-grab the host mouse so the + // pointer cannot leave the window and the guest cursor can reach every + // screen area. When the session ends, release an automatic grab again so + // the user can move the pointer to other windows. A manual grab (Ctrl+G) + // is sticky and is neither grabbed nor released automatically. + if (draw_hw_cursor) { + if (!impl->guest_cursor_drawn && !impl->manual_grab && !is_grabbed) { + this->update_mouse_grab(true); + SDL_SetRelativeMouseMode(SDL_TRUE); + is_grabbed = true; + } + } else { + if (!impl->manual_grab && is_grabbed) { + SDL_SetRelativeMouseMode(SDL_FALSE); + is_grabbed = false; + } + } + + // The guest draws its own cursor into the framebuffer. Even without a + // grab, keep the SDL host pointer hidden so the user never sees a second + // cursor that does not track the guest one. When the guest cursor is not + // drawn (e.g. firmware), show the host pointer so the user can aim and + // click. + bool want_host_cursor = !draw_hw_cursor; + if (impl->show_host_cursor != want_host_cursor) { + impl->show_host_cursor = want_host_cursor; + SDL_ShowCursor(want_host_cursor ? SDL_ENABLE : SDL_DISABLE); + } + + // Let the window title reflect the current grab/cursor state. + if (is_grabbed != impl->was_grabbed || draw_hw_cursor != impl->guest_cursor_drawn) { + impl->was_grabbed = is_grabbed; + impl->guest_cursor_drawn = draw_hw_cursor; + this->update_window_title(); + } + // draw HW cursor if enabled if (draw_hw_cursor) { impl->cursor_rect.x = cursor_x * impl->renderer_scale_x + impl->dest_rect.x;