diff --git a/client/displayservers/Wayland/input.c b/client/displayservers/Wayland/input.c index 30238149..525b56b4 100644 --- a/client/displayservers/Wayland/input.c +++ b/client/displayservers/Wayland/input.c @@ -196,6 +196,9 @@ static void confinedHandler(void * data, atomic_store_explicit(&wlWm.confActive, true, memory_order_release); LG_UNLOCK(wlWm.surfaceLock); MTRACE("conf active=1 id=%u valid=%d", proxyId(pointer), valid); + + if (valid) + app_handleGrabEvent(true); } static void unconfinedHandler(void * data, @@ -208,6 +211,9 @@ static void unconfinedHandler(void * data, atomic_store_explicit(&wlWm.confActive, false, memory_order_release); LG_UNLOCK(wlWm.surfaceLock); MTRACE("conf active=0 id=%u valid=%d", proxyId(pointer), valid); + + if (valid) + app_handleGrabEvent(false); } static const struct zwp_confined_pointer_v1_listener confinedListener = { @@ -215,6 +221,65 @@ static const struct zwp_confined_pointer_v1_listener confinedListener = { .unconfined = unconfinedHandler, }; +static struct zwp_confined_pointer_v1 * createConfine( + struct wl_region * region); +static bool queueConfSync(void); + +static void confSyncHandler(void * data, struct wl_callback * callback, + uint32_t serial) +{ + bool notify = false; + bool valid = false; + const uint32_t id = proxyId(callback); + uint32_t confId = 0; + uint64_t confSeq = 0; + + LG_LOCK(wlWm.surfaceLock); + if (callback == wlWm.confSync) + { + valid = true; + wl_callback_destroy(callback); + wlWm.confSync = NULL; + atomic_store_explicit(&wlWm.confActive, false, + memory_order_release); + notify = wlWm.inputLive; + + if (wlWm.inputLive && wlWm.confReq && wlWm.pointer && + wlWm.pointerConstraints && !wlWm.confinedPointer && + !wlWm.lockedPointer) + { + wlWm.confinedPointer = createConfine(NULL); + confId = proxyId(wlWm.confinedPointer); + confSeq = app_mouseSeq(); + } + } + LG_UNLOCK(wlWm.surfaceLock); + + MTRACE("conf sync id=%u serial=%u valid=%d notify=%d", id, serial, + valid, notify); + MLOG(confSeq, "conf req id=%u why=sync", confId); + + if (notify) + app_handleGrabEvent(false); +} + +static const struct wl_callback_listener confSyncListener = { + .done = confSyncHandler, +}; + +static bool queueConfSync(void) +{ + if (wlWm.confSync) + return true; + + wlWm.confSync = wl_display_sync(wlWm.display); + if (!wlWm.confSync) + return false; + + wl_callback_add_listener(wlWm.confSync, &confSyncListener, NULL); + return true; +} + static void lockedHandler(void * data, struct zwp_locked_pointer_v1 * pointer) { @@ -437,14 +502,21 @@ static const struct wl_keyboard_listener keyboardListener = { .modifiers = keyboardModifiersHandler, }; -static void waylandCleanUpPointer(void) +static void waylandCleanUpPointer(bool notify) { + bool event = false; uint32_t lockId = 0; uint32_t confId = 0; uint64_t lockSeq = 0; uint64_t confSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { + if (wlWm.confSync) + { + wl_callback_destroy(wlWm.confSync); + wlWm.confSync = NULL; + } + atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); atomic_store_explicit(&wlWm.confActive, false, memory_order_release); @@ -463,19 +535,25 @@ static void waylandCleanUpPointer(void) wlWm.confinedPointer = NULL; confSeq = app_mouseSeq(); } + + if (wlWm.relativePointer) + { + zwp_relative_pointer_v1_destroy(wlWm.relativePointer); + wlWm.relativePointer = NULL; + } + + wl_pointer_destroy(wlWm.pointer); + wlWm.pointer = NULL; + wlWm.pointerInSurface = false; + + event = notify && wlWm.inputLive; }); MLOG(lockSeq, "lock destroy id=%u why=pointer", lockId); MLOG(confSeq, "conf destroy id=%u why=pointer", confId); - if (wlWm.relativePointer) - { - zwp_relative_pointer_v1_destroy(wlWm.relativePointer); - wlWm.relativePointer = NULL; - } - - wl_pointer_destroy(wlWm.pointer); - wlWm.pointer = NULL; + if (event) + app_handleGrabEvent(false); } // Seat-handling listeners. @@ -484,7 +562,7 @@ static void handlePointerCapability(uint32_t capabilities) { bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; if (!hasPointer && wlWm.pointer) - waylandCleanUpPointer(); + waylandCleanUpPointer(true); else if (hasPointer && !wlWm.pointer) { wlWm.pointer = wl_seat_get_pointer(wlWm.seat); @@ -502,6 +580,16 @@ static void handlePointerCapability(uint32_t capabilities) if (app_isCaptureMode()) waylandCapturePointer(); + else + { + bool confReq; + INTERLOCKED_SECTION(wlWm.surfaceLock, + { + confReq = wlWm.confReq; + }); + if (confReq) + waylandGrabPointer(); + } } } @@ -578,6 +666,10 @@ bool waylandInputInit(bool allowNoInput) wlWm.warpSupport, !!wlWm.relativePointerManager, !!wlWm.pointerConstraints); + LG_LOCK(wlWm.surfaceLock); + wlWm.inputLive = true; + LG_UNLOCK(wlWm.surfaceLock); + wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wlWm.xkb) DEBUG_WARN("Failed to initialize xkb, keyboard input will not work"); @@ -590,13 +682,15 @@ bool waylandInputInit(bool allowNoInput) void waylandInputFree(void) { + LG_LOCK(wlWm.surfaceLock); + wlWm.inputLive = false; + LG_UNLOCK(wlWm.surfaceLock); + if (!wlWm.seat) return; - waylandUngrabPointer(); - if (wlWm.pointer) - waylandCleanUpPointer(); + waylandCleanUpPointer(false); // The only legal way the keyboard can be null is if it never existed. // When unplugged, the compositor must have an inert object. @@ -617,59 +711,56 @@ void waylandInputFree(void) void waylandGrabPointer(void) { - if (!wlWm.pointer || - !wlWm.relativePointerManager || !wlWm.pointerConstraints) - { - MTRACE("grab drop pointer=%d relMgr=%d constraints=%d", - !!wlWm.pointer, !!wlWm.relativePointerManager, - !!wlWm.pointerConstraints); - return; - } - - if (!wlWm.warpSupport && !wlWm.relativePointer) - { - struct zwp_relative_pointer_v1 * relative = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wlWm.relativePointerManager, wlWm.pointer); - zwp_relative_pointer_v1_add_listener(relative, - &relativePointerListener, NULL); - const uint32_t relativeId = proxyId(relative); - wlWm.relativePointer = relative; - MTRACE("rel req id=%u why=grab", relativeId); - } - - uint32_t confId = 0; - uint64_t confSeq = 0; - bool haveConf = false; - bool haveLock = false; + uint32_t relativeId = 0; + uint32_t confId = 0; + uint64_t confSeq = 0; + bool haveConf = false; + bool haveLock = false; + bool haveSync = false; + bool havePointer = false; + bool haveConstraints = false; INTERLOCKED_SECTION(wlWm.surfaceLock, { - if (!wlWm.confinedPointer && !wlWm.lockedPointer) + wlWm.confReq = true; + + if (wlWm.pointer && !wlWm.warpSupport && !wlWm.relativePointer && + wlWm.relativePointerManager) + { + wlWm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wlWm.relativePointerManager, wlWm.pointer); + zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + &relativePointerListener, NULL); + relativeId = proxyId(wlWm.relativePointer); + } + + if (wlWm.pointer && wlWm.pointerConstraints && + !wlWm.confinedPointer && !wlWm.lockedPointer && !wlWm.confSync) { - atomic_store_explicit(&wlWm.confActive, false, - memory_order_release); wlWm.confinedPointer = createConfine(NULL); confId = proxyId(wlWm.confinedPointer); confSeq = app_mouseSeq(); } - haveConf = !!wlWm.confinedPointer; - haveLock = !!wlWm.lockedPointer; + haveConf = !!wlWm.confinedPointer; + haveLock = !!wlWm.lockedPointer; + haveSync = !!wlWm.confSync; + havePointer = !!wlWm.pointer; + haveConstraints = !!wlWm.pointerConstraints; }); + if (relativeId) + MTRACE("rel req id=%u why=grab", relativeId); + if (confId) MLOG(confSeq, "conf req id=%u why=grab", confId); else - MTRACE("conf skip conf=%d lock=%d", haveConf, haveLock); + MTRACE("conf skip pointer=%d constraints=%d conf=%d lock=%d sync=%d", + havePointer, haveConstraints, haveConf, haveLock, haveSync); } -inline static uint32_t internalUngrabPointer(bool lock, uint64_t * traceSeq) +inline static uint32_t destroyConfine(uint64_t * traceSeq) { *traceSeq = 0; - if (!wlWm.pointer) - return 0; - - if (lock) - LG_LOCK(wlWm.surfaceLock); uint32_t confId = 0; if (wlWm.confinedPointer) @@ -679,34 +770,57 @@ inline static uint32_t internalUngrabPointer(bool lock, uint64_t * traceSeq) wlWm.confinedPointer = NULL; atomic_store_explicit(&wlWm.confActive, false, memory_order_release); *traceSeq = app_mouseSeq(); - } - if (lock) - LG_UNLOCK(wlWm.surfaceLock); - - if (!wlWm.warpSupport) - { - if (!wlWm.relativePointer) - { - wlWm.relativePointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - wlWm.relativePointerManager, wlWm.pointer); - zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, - &relativePointerListener, NULL); - } - - app_resyncMouseBasic(); - app_handleMouseBasic(); + if (!queueConfSync()) + DEBUG_ERROR("Failed to queue pointer release sync"); } return confId; } +static void ungrabBasic(void) +{ + if (wlWm.warpSupport) + return; + + uint32_t relativeId = 0; + LG_LOCK(wlWm.surfaceLock); + if (wlWm.pointer && !wlWm.relativePointer && + wlWm.relativePointerManager) + { + wlWm.relativePointer = + zwp_relative_pointer_manager_v1_get_relative_pointer( + wlWm.relativePointerManager, wlWm.pointer); + zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, + &relativePointerListener, NULL); + relativeId = proxyId(wlWm.relativePointer); + } + LG_UNLOCK(wlWm.surfaceLock); + + if (relativeId) + MTRACE("rel req id=%u why=ungrab", relativeId); + + app_resyncMouseBasic(); + app_handleMouseBasic(); +} + void waylandUngrabPointer(void) { + bool notify; uint64_t confSeq; - const uint32_t confId = internalUngrabPointer(true, &confSeq); + uint32_t confId; + + LG_LOCK(wlWm.surfaceLock); + wlWm.confReq = false; + confId = destroyConfine(&confSeq); + notify = !wlWm.confSync && wlWm.inputLive; + LG_UNLOCK(wlWm.surfaceLock); + MLOG(confSeq, "conf destroy id=%u why=ungrab", confId); + ungrabBasic(); + + if (notify) + app_handleGrabEvent(false); } void waylandCapturePointer(void) @@ -724,20 +838,17 @@ void waylandCapturePointer(void) uint64_t lockSeq = 0; INTERLOCKED_SECTION(wlWm.surfaceLock, { - if (wlWm.confinedPointer) - { - confId = proxyId(wlWm.confinedPointer); - zwp_confined_pointer_v1_destroy(wlWm.confinedPointer); - wlWm.confinedPointer = NULL; - atomic_store_explicit(&wlWm.confActive, false, - memory_order_release); - confSeq = app_mouseSeq(); - } + wlWm.confReq = true; + confId = destroyConfine(&confSeq); - atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); - wlWm.lockedPointer = createLock(); - lockId = proxyId(wlWm.lockedPointer); - lockSeq = app_mouseSeq(); + if (wlWm.pointer && !wlWm.lockedPointer) + { + atomic_store_explicit(&wlWm.lockActive, false, + memory_order_release); + wlWm.lockedPointer = createLock(); + lockId = proxyId(wlWm.lockedPointer); + lockSeq = app_mouseSeq(); + } }); MLOG(confSeq, "conf destroy id=%u why=capture", confId); @@ -772,20 +883,31 @@ void waylandUncapturePointer(void) * - if the user has opted to use captureInputOnly mode. */ if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode()) - confDropId = internalUngrabPointer(false, &confDropSeq); - else if (wlWm.pointer) { - atomic_store_explicit(&wlWm.confActive, false, - memory_order_release); - wlWm.confinedPointer = createConfine(NULL); - confReqId = proxyId(wlWm.confinedPointer); - confReqSeq = app_mouseSeq(); + wlWm.confReq = false; + confDropId = destroyConfine(&confDropSeq); + } + else + { + wlWm.confReq = true; + if (wlWm.pointer && !wlWm.confSync && !wlWm.confinedPointer) + { + wlWm.confinedPointer = createConfine(NULL); + confReqId = proxyId(wlWm.confinedPointer); + confReqSeq = app_mouseSeq(); + } } }); MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId); MLOG(confDropSeq, "conf destroy id=%u why=uncapture", confDropId); MLOG(confReqSeq, "conf req id=%u why=uncapture", confReqId); + ungrabBasic(); +} + +bool waylandIsPointerGrabbed(void) +{ + return atomic_load_explicit(&wlWm.confActive, memory_order_acquire); } bool waylandIsPointerCaptured(void) diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index 56563cb3..11d38527 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -321,6 +321,7 @@ struct LG_DisplayServerOps LGDS_Wayland = .setPointer = waylandSetPointer, .grabPointer = waylandGrabPointer, .ungrabPointer = waylandUngrabPointer, + .isPointerGrabbed = waylandIsPointerGrabbed, .capturePointer = waylandCapturePointer, .uncapturePointer = waylandUncapturePointer, .isPointerCaptured = waylandIsPointerCaptured, diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index 9fdec4fb..f3992f30 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -195,6 +195,9 @@ struct WaylandDSState struct zwp_relative_pointer_v1 * relativePointer; struct zwp_confined_pointer_v1 * confinedPointer; struct zwp_locked_pointer_v1 * lockedPointer; + struct wl_callback * confSync; + bool confReq; + bool inputLive; bool showPointer; uint32_t pointerEnterSerial; @@ -379,6 +382,7 @@ void waylandUngrabKeyboard(void); void waylandUngrabPointer(void); void waylandCapturePointer(void); void waylandUncapturePointer(void); +bool waylandIsPointerGrabbed(void); bool waylandIsPointerCaptured(void); void waylandRealignPointer(void); void waylandWarpPointer(int x, int y, bool exiting); diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index ad25ea52..85f75cf3 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -341,6 +341,8 @@ static bool x11Init(const LG_DSInitParams params) XSetIOErrorHandler(x11IOErrorHandler); memset(&x11, 0, sizeof(x11)); + atomic_init(&x11.captureActive, false); + atomic_init(&x11.pointerGrabbed, false); x11.xValuator = -1; x11.yValuator = -1; x11.display = XOpenDisplay(NULL); @@ -1508,7 +1510,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie) XIDeviceEvent *device = cookie->data; app_updateCursorPos(device->event_x, device->event_y); - if (!x11.pointerGrabbed) + if (!atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire)) app_handleMouseRelative(0.0, 0.0, 0.0, 0.0); return; } @@ -1886,7 +1888,7 @@ static void x11PrintGrabError(const char * type, int dev, Status ret) static void x11GrabPointer(void) { - if (x11.pointerGrabbed) + if (atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire)) return; unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 }; @@ -1935,26 +1937,36 @@ static void x11GrabPointer(void) return; } - x11.pointerGrabbed = true; + atomic_store_explicit(&x11.pointerGrabbed, true, memory_order_release); } static void x11UngrabPointer(void) { atomic_store_explicit(&x11.captureActive, false, memory_order_release); - if (!x11.pointerGrabbed) + if (!atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire)) + { + app_handleGrabEvent(false); return; + } XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime); XSync(x11.display, False); - x11.pointerGrabbed = false; + atomic_store_explicit(&x11.pointerGrabbed, false, memory_order_release); + app_handleGrabEvent(false); +} + +static bool x11IsPointerGrabbed(void) +{ + return atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire); } static void x11CapturePointer(void) { x11GrabPointer(); - atomic_store_explicit(&x11.captureActive, x11.pointerGrabbed, + atomic_store_explicit(&x11.captureActive, + atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire), memory_order_release); } @@ -2152,6 +2164,7 @@ struct LG_DisplayServerOps LGDS_X11 = .setPointer = x11SetPointer, .grabPointer = x11GrabPointer, .ungrabPointer = x11UngrabPointer, + .isPointerGrabbed = x11IsPointerGrabbed, .capturePointer = x11CapturePointer, .uncapturePointer = x11UncapturePointer, .isPointerCaptured = x11IsPointerCaptured, diff --git a/client/displayservers/X11/x11.h b/client/displayservers/X11/x11.h index e7edc1f4..35711a3d 100644 --- a/client/displayservers/X11/x11.h +++ b/client/displayservers/X11/x11.h @@ -94,7 +94,7 @@ struct X11DSState int yValuator; _Atomic(bool) captureActive; - bool pointerGrabbed; + _Atomic(bool) pointerGrabbed; bool keyboardGrabbed; bool entered; bool focused; diff --git a/client/include/app.h b/client/include/app.h index 19ade83d..be956fa2 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -84,6 +84,7 @@ void app_handleKeyRelease(int scancode); void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super); void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock); void app_handleEnterEvent(bool entered); +void app_handleGrabEvent(bool active); void app_handleFocusEvent(bool focused); void app_handleCloseEvent(void); void app_handleRenderEvent(const uint64_t timeUs); diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h index abb83c04..957f7247 100644 --- a/client/include/interface/displayserver.h +++ b/client/include/interface/displayserver.h @@ -207,6 +207,7 @@ struct LG_DisplayServerOps /* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */ void (*grabPointer)(void); void (*ungrabPointer)(void); + bool (*isPointerGrabbed)(void); /* (un)capturePointer is used do toggle special cursor tracking in capture mode */ void (*capturePointer)(void); void (*uncapturePointer)(void); @@ -290,6 +291,7 @@ struct LG_DisplayServerOps DEBUG_ASSERT((x)->setPointer ); \ DEBUG_ASSERT((x)->grabPointer ); \ DEBUG_ASSERT((x)->ungrabPointer ); \ + DEBUG_ASSERT((x)->isPointerGrabbed ); \ DEBUG_ASSERT((x)->capturePointer ); \ DEBUG_ASSERT((x)->uncapturePointer ); \ DEBUG_ASSERT((x)->isPointerCaptured ); \ diff --git a/client/src/app.c b/client/src/app.c index a217efe9..b49d8b72 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -469,7 +469,7 @@ void app_handleButtonPress(int button) return; } - if (!core_inputEnabled() || !g_cursor.inView) + if (!core_inputEnabled() || !g_cursor.inView || !g_cursor.viewReq) return; if (!purespice_mousePress(button)) @@ -674,6 +674,11 @@ void app_handleMouseRelative(double normx, double normy, core_handleMouseNormal(normx, normy); } +void app_handleGrabEvent(bool active) +{ + core_handleGrabEvent(active); +} + // On some display servers normal cursor logic does not work due to the lack of // cursor warp support. Instead, we attempt a best-effort emulation which works // with a 1:1 mouse movement patch applied in the guest. For anything fancy, use diff --git a/client/src/core.c b/client/src/core.c index 6bce64ae..924d0de1 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -66,28 +66,21 @@ bool core_inputEnabled(void) ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); } -void core_invalidatePointer(bool detectInView) +static void applyView(bool active, bool force) { - /* if the display server does not support warp, then we can not operate in - * always relative mode and we should not grab the pointer */ - enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; - app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); - - MTRACE("invalidate detect=%d inWin=%d inView=%d grab=%d warp=%d " - "support=%d", detectInView, g_cursor.inWindow, g_cursor.inView, - g_cursor.grab, g_cursor.warpState, warpSupport); - - if (detectInView) + if (active && !g_cursor.viewReq) { - bool inView = isInView(); - // do not allow the view to become active if any mouse buttons are being held, - // this fixes issues with meta window resizing. - if (inView && g_cursor.buttons) - return; - - g_cursor.inView = inView; + MTRACE("view active skip=req"); + return; } + if (!force && g_cursor.inView == active) + return; + + MTRACE("view active=%d old=%d req=%d force=%d", active, + g_cursor.inView, g_cursor.viewReq, force); + + g_cursor.inView = active; g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly) ? true : g_cursor.inView; g_cursor.redraw = true; @@ -98,9 +91,6 @@ void core_invalidatePointer(bool detectInView) if (g_params.hideMouse) g_state.ds->setPointer(LG_POINTER_NONE); - if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly) - g_state.ds->grabPointer(); - if (g_params.grabKeyboardOnFocus) g_state.ds->grabKeyboard(); } @@ -109,36 +99,96 @@ void core_invalidatePointer(bool detectInView) if (g_params.hideMouse) g_state.ds->setPointer(LG_POINTER_SQUARE); - if (warpSupport != LG_DS_WARP_NONE) - g_state.ds->ungrabPointer(); - g_state.ds->ungrabKeyboard(); } g_cursor.warpState = WARP_STATE_ON; } -void core_setCursorInView(bool enable) +void core_invalidatePointer(bool detectInView) { - MTRACE("view req=%d old=%d focus=%d inWin=%d grab=%d", - enable, g_cursor.inView, g_state.focused, g_cursor.inWindow, - g_cursor.grab); + MTRACE("invalidate detect=%d inWin=%d req=%d active=%d grab=%d " + "warp=%d", detectInView, g_cursor.inWindow, g_cursor.viewReq, + g_cursor.inView, g_cursor.grab, g_cursor.warpState); - // if the state has not changed, don't do anything else - if (g_cursor.inView == enable) + if (!detectInView) { - MTRACE("view skip=same value=%d", enable); + applyView(g_cursor.inView, true); return; } + const bool inView = isInView(); + + /* do not allow the view to become active if any mouse buttons are being + * held, this fixes issues with meta window resizing. */ + if (inView && g_cursor.buttons) + return; + + core_setCursorInView(inView); +} + +void core_setCursorInView(bool enable) +{ + MTRACE("view req=%d oldReq=%d active=%d focus=%d inWin=%d grab=%d", + enable, g_cursor.viewReq, g_cursor.inView, g_state.focused, + g_cursor.inWindow, g_cursor.grab); + if (enable && !g_state.focused) { MTRACE("view skip=focus"); return; } - g_cursor.inView = enable; - core_invalidatePointer(false); + enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; + app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); + + const bool immediate = g_cursor.grab || g_params.captureInputOnly || + warpSupport == LG_DS_WARP_NONE; + + if (g_cursor.viewReq == enable) + { + if (immediate) + applyView(enable, false); + else if (enable && !g_cursor.inView) + { + g_state.ds->grabPointer(); + applyView(g_state.ds->isPointerGrabbed(), false); + } + else + MTRACE("view skip=same value=%d", enable); + return; + } + + g_cursor.viewReq = enable; + + if (immediate) + { + applyView(enable, false); + return; + } + + if (enable) + { + g_state.ds->grabPointer(); + applyView(g_state.ds->isPointerGrabbed(), false); + } + else + g_state.ds->ungrabPointer(); +} + +void core_handleGrabEvent(bool active) +{ + MTRACE("conf active=%d req=%d old=%d grab=%d", active, + g_cursor.viewReq, g_cursor.inView, g_cursor.grab); + + enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE; + app_getProp(LG_DS_WARP_SUPPORT, &warpSupport); + + if (g_cursor.grab || g_params.captureInputOnly || + warpSupport == LG_DS_WARP_NONE) + return; + + applyView(active, false); } void core_setGrab(bool enable) @@ -202,6 +252,9 @@ void core_setGrabQuiet(bool enable) g_state.ds->uncapturePointer(); + if (!g_params.captureInputOnly && warpSupport != LG_DS_WARP_NONE) + applyView(g_state.ds->isPointerGrabbed(), false); + /* if exiting capture when input on capture only we need to align the local * cursor to the guest's location before it is shown. */ if (g_params.captureInputOnly || !g_params.hideMouse) @@ -507,12 +560,13 @@ void core_handleGuestMouseUpdate(void) } const bool overlay = app_isOverlayMode(); - if (overlay || !g_cursor.inView) + if (overlay || !g_cursor.inView || !g_cursor.viewReq) { MTRACE("guest drop=%s guest=%d,%d local=%.3f,%.3f inWin=%d " - "inView=%d", overlay ? "overlay" : "view", g_cursor.guest.x, + "inView=%d req=%d", overlay ? "overlay" : "view", + g_cursor.guest.x, g_cursor.guest.y, localPos.x, localPos.y, g_cursor.inWindow, - g_cursor.inView); + g_cursor.inView, g_cursor.viewReq); return; } @@ -588,6 +642,13 @@ void core_handleMouseNormal(double ex, double ey) if (!core_inputEnabled()) return; + if (g_cursor.viewReq != g_cursor.inView) + { + if (g_cursor.viewReq) + core_setCursorInView(true); + return; + } + /* scale the movement to the guest */ if (g_cursor.useScale && g_params.scaleMouseInput) { @@ -600,7 +661,9 @@ void core_handleMouseNormal(double ex, double ey) const bool inView = isInView(); if (!g_cursor.inView) { - if (inView) + if (g_cursor.viewReq) + return; + else if (inView) g_cursor.realign = true; else /* nothing to do if we are outside the viewport */ return; @@ -691,6 +754,9 @@ fallback: ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx); ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy); core_setCursorInView(true); + + if (!g_cursor.inView) + return; } g_cursor.realign = false; diff --git a/client/src/core.h b/client/src/core.h index c234ed86..3ee8c201 100644 --- a/client/src/core.h +++ b/client/src/core.h @@ -26,6 +26,7 @@ bool core_inputEnabled(void); void core_invalidatePointer(bool detectInView); void core_setCursorInView(bool enable); +void core_handleGrabEvent(bool active); void core_setGrab(bool enable); void core_setGrabQuiet(bool enable); bool core_warpPointer(int x, int y, bool exiting); diff --git a/client/src/main.c b/client/src/main.c index bee74a32..071d4644 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -131,6 +131,7 @@ static void lgInit(void) g_cursor.scale.y = 1.0; g_cursor.draw = false; g_cursor.inView = false; + g_cursor.viewReq = false; g_cursor.guest.valid = false; // if spice is not in use, hide the local cursor diff --git a/client/src/main.h b/client/src/main.h index 0c713f99..0a252b2d 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -295,6 +295,9 @@ struct CursorState /* true if the cursor is currently in the guest view area */ bool inView; + /* true if the cursor should be confined to the guest view area */ + bool viewReq; + /* true if the guest should be realigned to the host when next drawn */ bool realign; diff --git a/client/tests/mouse_test.c b/client/tests/mouse_test.c index b4e5e83d..47e7e581 100644 --- a/client/tests/mouse_test.c +++ b/client/tests/mouse_test.c @@ -130,7 +130,15 @@ static void ungrab(void) push(EV_UNGRAB, 0, 0, false); m.conf.req = false; if (!m.conf.hold) + { m.conf.on = false; + core_handleGrabEvent(false); + } +} + +static bool grabbed(void) +{ + return m.conf.on; } static void capture(void) @@ -186,6 +194,7 @@ static struct LG_DisplayServerOps ds = { .ungrabKeyboard = keyNoop, .grabPointer = grab, .ungrabPointer = ungrab, + .isPointerGrabbed = grabbed, .capturePointer = capture, .uncapturePointer = uncapture, .isPointerCaptured = captured, @@ -245,8 +254,10 @@ static void reset(void) memset(&g_cursor, 0, sizeof(g_cursor)); memset(&g_params, 0, sizeof(g_params)); - m.support = LG_DS_WARP_SURFACE; - m.valid = true; + m.support = LG_DS_WARP_SURFACE; + m.valid = true; + m.conf.req = true; + m.conf.on = true; g_state.ds = &ds; g_state.focused = true; @@ -269,6 +280,7 @@ static void reset(void) g_cursor.inWindow = true; g_cursor.inView = true; + g_cursor.viewReq = true; g_cursor.warpState = WARP_STATE_ON; g_cursor.guest.valid = true; g_cursor.scale = (struct DoublePoint) { 1.0, 1.0 }; @@ -315,8 +327,6 @@ static void startExit(void) { reset(); setLocal(109, 50); - m.conf.req = true; - m.conf.on = true; m.conf.hold = true; core_handleMouseNormal(2, 0); @@ -343,17 +353,29 @@ static void startConf(void) { reset(); setLocal(50, 50); - g_cursor.inView = false; + m.conf.req = false; + m.conf.on = false; + g_cursor.inView = false; + g_cursor.viewReq = false; core_handleMouseNormal(0, 0); } +static void setConf(bool active) +{ + m.conf.on = active; + core_handleGrabEvent(active); +} + static void testConfWait(void) { startConf(); CHECK(m.conf.req); CHECK(!m.conf.on); CHECK(!g_cursor.inView); + + setConf(true); + CHECK(g_cursor.inView); } static void testConfGuest(void) @@ -362,6 +384,10 @@ static void testConfGuest(void) core_handleGuestMouseUpdate(); CHECK(m.conf.req); CHECK(count(EV_GUEST) == 0); + + setConf(true); + core_handleGuestMouseUpdate(); + CHECK(count(EV_GUEST) == 1); } static void testCapWait(void)