[client] input: track pointer confinement state

This commit is contained in:
Geoffrey McRae
2026-08-06 14:29:45 +10:00
parent 1baa5a3710
commit 142dc2bcef
13 changed files with 383 additions and 138 deletions

View File

@@ -196,6 +196,9 @@ static void confinedHandler(void * data,
atomic_store_explicit(&wlWm.confActive, true, memory_order_release); atomic_store_explicit(&wlWm.confActive, true, memory_order_release);
LG_UNLOCK(wlWm.surfaceLock); LG_UNLOCK(wlWm.surfaceLock);
MTRACE("conf active=1 id=%u valid=%d", proxyId(pointer), valid); MTRACE("conf active=1 id=%u valid=%d", proxyId(pointer), valid);
if (valid)
app_handleGrabEvent(true);
} }
static void unconfinedHandler(void * data, static void unconfinedHandler(void * data,
@@ -208,6 +211,9 @@ static void unconfinedHandler(void * data,
atomic_store_explicit(&wlWm.confActive, false, memory_order_release); atomic_store_explicit(&wlWm.confActive, false, memory_order_release);
LG_UNLOCK(wlWm.surfaceLock); LG_UNLOCK(wlWm.surfaceLock);
MTRACE("conf active=0 id=%u valid=%d", proxyId(pointer), valid); 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 = { static const struct zwp_confined_pointer_v1_listener confinedListener = {
@@ -215,6 +221,65 @@ static const struct zwp_confined_pointer_v1_listener confinedListener = {
.unconfined = unconfinedHandler, .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, static void lockedHandler(void * data,
struct zwp_locked_pointer_v1 * pointer) struct zwp_locked_pointer_v1 * pointer)
{ {
@@ -437,14 +502,21 @@ static const struct wl_keyboard_listener keyboardListener = {
.modifiers = keyboardModifiersHandler, .modifiers = keyboardModifiersHandler,
}; };
static void waylandCleanUpPointer(void) static void waylandCleanUpPointer(bool notify)
{ {
bool event = false;
uint32_t lockId = 0; uint32_t lockId = 0;
uint32_t confId = 0; uint32_t confId = 0;
uint64_t lockSeq = 0; uint64_t lockSeq = 0;
uint64_t confSeq = 0; uint64_t confSeq = 0;
INTERLOCKED_SECTION(wlWm.surfaceLock, 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.lockActive, false, memory_order_release);
atomic_store_explicit(&wlWm.confActive, false, memory_order_release); atomic_store_explicit(&wlWm.confActive, false, memory_order_release);
@@ -463,10 +535,6 @@ static void waylandCleanUpPointer(void)
wlWm.confinedPointer = NULL; wlWm.confinedPointer = NULL;
confSeq = app_mouseSeq(); confSeq = app_mouseSeq();
} }
});
MLOG(lockSeq, "lock destroy id=%u why=pointer", lockId);
MLOG(confSeq, "conf destroy id=%u why=pointer", confId);
if (wlWm.relativePointer) if (wlWm.relativePointer)
{ {
@@ -476,6 +544,16 @@ static void waylandCleanUpPointer(void)
wl_pointer_destroy(wlWm.pointer); wl_pointer_destroy(wlWm.pointer);
wlWm.pointer = NULL; 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 (event)
app_handleGrabEvent(false);
} }
// Seat-handling listeners. // Seat-handling listeners.
@@ -484,7 +562,7 @@ static void handlePointerCapability(uint32_t capabilities)
{ {
bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER; bool hasPointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
if (!hasPointer && wlWm.pointer) if (!hasPointer && wlWm.pointer)
waylandCleanUpPointer(); waylandCleanUpPointer(true);
else if (hasPointer && !wlWm.pointer) else if (hasPointer && !wlWm.pointer)
{ {
wlWm.pointer = wl_seat_get_pointer(wlWm.seat); wlWm.pointer = wl_seat_get_pointer(wlWm.seat);
@@ -502,6 +580,16 @@ static void handlePointerCapability(uint32_t capabilities)
if (app_isCaptureMode()) if (app_isCaptureMode())
waylandCapturePointer(); 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.warpSupport, !!wlWm.relativePointerManager,
!!wlWm.pointerConstraints); !!wlWm.pointerConstraints);
LG_LOCK(wlWm.surfaceLock);
wlWm.inputLive = true;
LG_UNLOCK(wlWm.surfaceLock);
wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wlWm.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!wlWm.xkb) if (!wlWm.xkb)
DEBUG_WARN("Failed to initialize xkb, keyboard input will not work"); DEBUG_WARN("Failed to initialize xkb, keyboard input will not work");
@@ -590,13 +682,15 @@ bool waylandInputInit(bool allowNoInput)
void waylandInputFree(void) void waylandInputFree(void)
{ {
LG_LOCK(wlWm.surfaceLock);
wlWm.inputLive = false;
LG_UNLOCK(wlWm.surfaceLock);
if (!wlWm.seat) if (!wlWm.seat)
return; return;
waylandUngrabPointer();
if (wlWm.pointer) if (wlWm.pointer)
waylandCleanUpPointer(); waylandCleanUpPointer(false);
// The only legal way the keyboard can be null is if it never existed. // The only legal way the keyboard can be null is if it never existed.
// When unplugged, the compositor must have an inert object. // When unplugged, the compositor must have an inert object.
@@ -617,59 +711,56 @@ void waylandInputFree(void)
void waylandGrabPointer(void) void waylandGrabPointer(void)
{ {
if (!wlWm.pointer || uint32_t relativeId = 0;
!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; uint32_t confId = 0;
uint64_t confSeq = 0; uint64_t confSeq = 0;
bool haveConf = false; bool haveConf = false;
bool haveLock = false; bool haveLock = false;
bool haveSync = false;
bool havePointer = false;
bool haveConstraints = false;
INTERLOCKED_SECTION(wlWm.surfaceLock, 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); wlWm.confinedPointer = createConfine(NULL);
confId = proxyId(wlWm.confinedPointer); confId = proxyId(wlWm.confinedPointer);
confSeq = app_mouseSeq(); confSeq = app_mouseSeq();
} }
haveConf = !!wlWm.confinedPointer; haveConf = !!wlWm.confinedPointer;
haveLock = !!wlWm.lockedPointer; haveLock = !!wlWm.lockedPointer;
haveSync = !!wlWm.confSync;
havePointer = !!wlWm.pointer;
haveConstraints = !!wlWm.pointerConstraints;
}); });
if (relativeId)
MTRACE("rel req id=%u why=grab", relativeId);
if (confId) if (confId)
MLOG(confSeq, "conf req id=%u why=grab", confId); MLOG(confSeq, "conf req id=%u why=grab", confId);
else 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; *traceSeq = 0;
if (!wlWm.pointer)
return 0;
if (lock)
LG_LOCK(wlWm.surfaceLock);
uint32_t confId = 0; uint32_t confId = 0;
if (wlWm.confinedPointer) if (wlWm.confinedPointer)
@@ -679,34 +770,57 @@ inline static uint32_t internalUngrabPointer(bool lock, uint64_t * traceSeq)
wlWm.confinedPointer = NULL; wlWm.confinedPointer = NULL;
atomic_store_explicit(&wlWm.confActive, false, memory_order_release); atomic_store_explicit(&wlWm.confActive, false, memory_order_release);
*traceSeq = app_mouseSeq(); *traceSeq = app_mouseSeq();
if (!queueConfSync())
DEBUG_ERROR("Failed to queue pointer release sync");
} }
if (lock) return confId;
LG_UNLOCK(wlWm.surfaceLock); }
if (!wlWm.warpSupport) static void ungrabBasic(void)
{ {
if (!wlWm.relativePointer) if (wlWm.warpSupport)
return;
uint32_t relativeId = 0;
LG_LOCK(wlWm.surfaceLock);
if (wlWm.pointer && !wlWm.relativePointer &&
wlWm.relativePointerManager)
{ {
wlWm.relativePointer = wlWm.relativePointer =
zwp_relative_pointer_manager_v1_get_relative_pointer( zwp_relative_pointer_manager_v1_get_relative_pointer(
wlWm.relativePointerManager, wlWm.pointer); wlWm.relativePointerManager, wlWm.pointer);
zwp_relative_pointer_v1_add_listener(wlWm.relativePointer, zwp_relative_pointer_v1_add_listener(wlWm.relativePointer,
&relativePointerListener, NULL); &relativePointerListener, NULL);
relativeId = proxyId(wlWm.relativePointer);
} }
LG_UNLOCK(wlWm.surfaceLock);
if (relativeId)
MTRACE("rel req id=%u why=ungrab", relativeId);
app_resyncMouseBasic(); app_resyncMouseBasic();
app_handleMouseBasic(); app_handleMouseBasic();
} }
return confId;
}
void waylandUngrabPointer(void) void waylandUngrabPointer(void)
{ {
bool notify;
uint64_t confSeq; 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); MLOG(confSeq, "conf destroy id=%u why=ungrab", confId);
ungrabBasic();
if (notify)
app_handleGrabEvent(false);
} }
void waylandCapturePointer(void) void waylandCapturePointer(void)
@@ -724,20 +838,17 @@ void waylandCapturePointer(void)
uint64_t lockSeq = 0; uint64_t lockSeq = 0;
INTERLOCKED_SECTION(wlWm.surfaceLock, INTERLOCKED_SECTION(wlWm.surfaceLock,
{ {
if (wlWm.confinedPointer) wlWm.confReq = true;
{ confId = destroyConfine(&confSeq);
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();
}
atomic_store_explicit(&wlWm.lockActive, false, memory_order_release); if (wlWm.pointer && !wlWm.lockedPointer)
{
atomic_store_explicit(&wlWm.lockActive, false,
memory_order_release);
wlWm.lockedPointer = createLock(); wlWm.lockedPointer = createLock();
lockId = proxyId(wlWm.lockedPointer); lockId = proxyId(wlWm.lockedPointer);
lockSeq = app_mouseSeq(); lockSeq = app_mouseSeq();
}
}); });
MLOG(confSeq, "conf destroy id=%u why=capture", confId); 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 the user has opted to use captureInputOnly mode.
*/ */
if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode()) if (!wlWm.warpSupport || !app_isFormatValid() || app_isCaptureOnlyMode())
confDropId = internalUngrabPointer(false, &confDropSeq);
else if (wlWm.pointer)
{ {
atomic_store_explicit(&wlWm.confActive, false, wlWm.confReq = false;
memory_order_release); confDropId = destroyConfine(&confDropSeq);
}
else
{
wlWm.confReq = true;
if (wlWm.pointer && !wlWm.confSync && !wlWm.confinedPointer)
{
wlWm.confinedPointer = createConfine(NULL); wlWm.confinedPointer = createConfine(NULL);
confReqId = proxyId(wlWm.confinedPointer); confReqId = proxyId(wlWm.confinedPointer);
confReqSeq = app_mouseSeq(); confReqSeq = app_mouseSeq();
} }
}
}); });
MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId); MLOG(lockSeq, "lock destroy id=%u why=uncapture", lockId);
MLOG(confDropSeq, "conf destroy id=%u why=uncapture", confDropId); MLOG(confDropSeq, "conf destroy id=%u why=uncapture", confDropId);
MLOG(confReqSeq, "conf req id=%u why=uncapture", confReqId); 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) bool waylandIsPointerCaptured(void)

View File

@@ -321,6 +321,7 @@ struct LG_DisplayServerOps LGDS_Wayland =
.setPointer = waylandSetPointer, .setPointer = waylandSetPointer,
.grabPointer = waylandGrabPointer, .grabPointer = waylandGrabPointer,
.ungrabPointer = waylandUngrabPointer, .ungrabPointer = waylandUngrabPointer,
.isPointerGrabbed = waylandIsPointerGrabbed,
.capturePointer = waylandCapturePointer, .capturePointer = waylandCapturePointer,
.uncapturePointer = waylandUncapturePointer, .uncapturePointer = waylandUncapturePointer,
.isPointerCaptured = waylandIsPointerCaptured, .isPointerCaptured = waylandIsPointerCaptured,

View File

@@ -195,6 +195,9 @@ struct WaylandDSState
struct zwp_relative_pointer_v1 * relativePointer; struct zwp_relative_pointer_v1 * relativePointer;
struct zwp_confined_pointer_v1 * confinedPointer; struct zwp_confined_pointer_v1 * confinedPointer;
struct zwp_locked_pointer_v1 * lockedPointer; struct zwp_locked_pointer_v1 * lockedPointer;
struct wl_callback * confSync;
bool confReq;
bool inputLive;
bool showPointer; bool showPointer;
uint32_t pointerEnterSerial; uint32_t pointerEnterSerial;
@@ -379,6 +382,7 @@ void waylandUngrabKeyboard(void);
void waylandUngrabPointer(void); void waylandUngrabPointer(void);
void waylandCapturePointer(void); void waylandCapturePointer(void);
void waylandUncapturePointer(void); void waylandUncapturePointer(void);
bool waylandIsPointerGrabbed(void);
bool waylandIsPointerCaptured(void); bool waylandIsPointerCaptured(void);
void waylandRealignPointer(void); void waylandRealignPointer(void);
void waylandWarpPointer(int x, int y, bool exiting); void waylandWarpPointer(int x, int y, bool exiting);

View File

@@ -341,6 +341,8 @@ static bool x11Init(const LG_DSInitParams params)
XSetIOErrorHandler(x11IOErrorHandler); XSetIOErrorHandler(x11IOErrorHandler);
memset(&x11, 0, sizeof(x11)); memset(&x11, 0, sizeof(x11));
atomic_init(&x11.captureActive, false);
atomic_init(&x11.pointerGrabbed, false);
x11.xValuator = -1; x11.xValuator = -1;
x11.yValuator = -1; x11.yValuator = -1;
x11.display = XOpenDisplay(NULL); x11.display = XOpenDisplay(NULL);
@@ -1508,7 +1510,7 @@ static void x11XInputEvent(XGenericEventCookie *cookie)
XIDeviceEvent *device = cookie->data; XIDeviceEvent *device = cookie->data;
app_updateCursorPos(device->event_x, device->event_y); 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); app_handleMouseRelative(0.0, 0.0, 0.0, 0.0);
return; return;
} }
@@ -1886,7 +1888,7 @@ static void x11PrintGrabError(const char * type, int dev, Status ret)
static void x11GrabPointer(void) static void x11GrabPointer(void)
{ {
if (x11.pointerGrabbed) if (atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire))
return; return;
unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 }; unsigned char mask_bits[XIMaskLen(XI_LASTEVENT)] = { 0 };
@@ -1935,26 +1937,36 @@ static void x11GrabPointer(void)
return; return;
} }
x11.pointerGrabbed = true; atomic_store_explicit(&x11.pointerGrabbed, true, memory_order_release);
} }
static void x11UngrabPointer(void) static void x11UngrabPointer(void)
{ {
atomic_store_explicit(&x11.captureActive, false, memory_order_release); 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; return;
}
XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime); XIUngrabDevice(x11.display, x11.pointerDev, CurrentTime);
XSync(x11.display, False); 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) static void x11CapturePointer(void)
{ {
x11GrabPointer(); x11GrabPointer();
atomic_store_explicit(&x11.captureActive, x11.pointerGrabbed, atomic_store_explicit(&x11.captureActive,
atomic_load_explicit(&x11.pointerGrabbed, memory_order_acquire),
memory_order_release); memory_order_release);
} }
@@ -2152,6 +2164,7 @@ struct LG_DisplayServerOps LGDS_X11 =
.setPointer = x11SetPointer, .setPointer = x11SetPointer,
.grabPointer = x11GrabPointer, .grabPointer = x11GrabPointer,
.ungrabPointer = x11UngrabPointer, .ungrabPointer = x11UngrabPointer,
.isPointerGrabbed = x11IsPointerGrabbed,
.capturePointer = x11CapturePointer, .capturePointer = x11CapturePointer,
.uncapturePointer = x11UncapturePointer, .uncapturePointer = x11UncapturePointer,
.isPointerCaptured = x11IsPointerCaptured, .isPointerCaptured = x11IsPointerCaptured,

View File

@@ -94,7 +94,7 @@ struct X11DSState
int yValuator; int yValuator;
_Atomic(bool) captureActive; _Atomic(bool) captureActive;
bool pointerGrabbed; _Atomic(bool) pointerGrabbed;
bool keyboardGrabbed; bool keyboardGrabbed;
bool entered; bool entered;
bool focused; bool focused;

View File

@@ -84,6 +84,7 @@ void app_handleKeyRelease(int scancode);
void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super); void app_handleKeyboardModifiers(bool ctrl, bool shift, bool alt, bool super);
void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock); void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock);
void app_handleEnterEvent(bool entered); void app_handleEnterEvent(bool entered);
void app_handleGrabEvent(bool active);
void app_handleFocusEvent(bool focused); void app_handleFocusEvent(bool focused);
void app_handleCloseEvent(void); void app_handleCloseEvent(void);
void app_handleRenderEvent(const uint64_t timeUs); void app_handleRenderEvent(const uint64_t timeUs);

View File

@@ -207,6 +207,7 @@ struct LG_DisplayServerOps
/* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */ /* (un)grabPointer is used to toggle cursor tracking/confine in normal mode */
void (*grabPointer)(void); void (*grabPointer)(void);
void (*ungrabPointer)(void); void (*ungrabPointer)(void);
bool (*isPointerGrabbed)(void);
/* (un)capturePointer is used do toggle special cursor tracking in capture mode */ /* (un)capturePointer is used do toggle special cursor tracking in capture mode */
void (*capturePointer)(void); void (*capturePointer)(void);
void (*uncapturePointer)(void); void (*uncapturePointer)(void);
@@ -290,6 +291,7 @@ struct LG_DisplayServerOps
DEBUG_ASSERT((x)->setPointer ); \ DEBUG_ASSERT((x)->setPointer ); \
DEBUG_ASSERT((x)->grabPointer ); \ DEBUG_ASSERT((x)->grabPointer ); \
DEBUG_ASSERT((x)->ungrabPointer ); \ DEBUG_ASSERT((x)->ungrabPointer ); \
DEBUG_ASSERT((x)->isPointerGrabbed ); \
DEBUG_ASSERT((x)->capturePointer ); \ DEBUG_ASSERT((x)->capturePointer ); \
DEBUG_ASSERT((x)->uncapturePointer ); \ DEBUG_ASSERT((x)->uncapturePointer ); \
DEBUG_ASSERT((x)->isPointerCaptured ); \ DEBUG_ASSERT((x)->isPointerCaptured ); \

View File

@@ -469,7 +469,7 @@ void app_handleButtonPress(int button)
return; return;
} }
if (!core_inputEnabled() || !g_cursor.inView) if (!core_inputEnabled() || !g_cursor.inView || !g_cursor.viewReq)
return; return;
if (!purespice_mousePress(button)) if (!purespice_mousePress(button))
@@ -674,6 +674,11 @@ void app_handleMouseRelative(double normx, double normy,
core_handleMouseNormal(normx, 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 // 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 // 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 // with a 1:1 mouse movement patch applied in the guest. For anything fancy, use

View File

@@ -66,28 +66,21 @@ bool core_inputEnabled(void)
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); ((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 if (active && !g_cursor.viewReq)
* 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)
{ {
bool inView = isInView(); MTRACE("view active skip=req");
// 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; return;
g_cursor.inView = inView;
} }
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) g_cursor.draw = (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : g_cursor.inView; ? true : g_cursor.inView;
g_cursor.redraw = true; g_cursor.redraw = true;
@@ -98,9 +91,6 @@ void core_invalidatePointer(bool detectInView)
if (g_params.hideMouse) if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_NONE); g_state.ds->setPointer(LG_POINTER_NONE);
if (warpSupport != LG_DS_WARP_NONE && !g_params.captureInputOnly)
g_state.ds->grabPointer();
if (g_params.grabKeyboardOnFocus) if (g_params.grabKeyboardOnFocus)
g_state.ds->grabKeyboard(); g_state.ds->grabKeyboard();
} }
@@ -109,36 +99,96 @@ void core_invalidatePointer(bool detectInView)
if (g_params.hideMouse) if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_SQUARE); g_state.ds->setPointer(LG_POINTER_SQUARE);
if (warpSupport != LG_DS_WARP_NONE)
g_state.ds->ungrabPointer();
g_state.ds->ungrabKeyboard(); g_state.ds->ungrabKeyboard();
} }
g_cursor.warpState = WARP_STATE_ON; 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", MTRACE("invalidate detect=%d inWin=%d req=%d active=%d grab=%d "
enable, g_cursor.inView, g_state.focused, g_cursor.inWindow, "warp=%d", detectInView, g_cursor.inWindow, g_cursor.viewReq,
g_cursor.grab); g_cursor.inView, g_cursor.grab, g_cursor.warpState);
// if the state has not changed, don't do anything else if (!detectInView)
if (g_cursor.inView == enable)
{ {
MTRACE("view skip=same value=%d", enable); applyView(g_cursor.inView, true);
return; 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) if (enable && !g_state.focused)
{ {
MTRACE("view skip=focus"); MTRACE("view skip=focus");
return; return;
} }
g_cursor.inView = enable; enum LG_DSWarpSupport warpSupport = LG_DS_WARP_NONE;
core_invalidatePointer(false); 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) void core_setGrab(bool enable)
@@ -202,6 +252,9 @@ void core_setGrabQuiet(bool enable)
g_state.ds->uncapturePointer(); 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 /* if exiting capture when input on capture only we need to align the local
* cursor to the guest's location before it is shown. */ * cursor to the guest's location before it is shown. */
if (g_params.captureInputOnly || !g_params.hideMouse) if (g_params.captureInputOnly || !g_params.hideMouse)
@@ -507,12 +560,13 @@ void core_handleGuestMouseUpdate(void)
} }
const bool overlay = app_isOverlayMode(); 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 " 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.guest.y, localPos.x, localPos.y, g_cursor.inWindow,
g_cursor.inView); g_cursor.inView, g_cursor.viewReq);
return; return;
} }
@@ -588,6 +642,13 @@ void core_handleMouseNormal(double ex, double ey)
if (!core_inputEnabled()) if (!core_inputEnabled())
return; return;
if (g_cursor.viewReq != g_cursor.inView)
{
if (g_cursor.viewReq)
core_setCursorInView(true);
return;
}
/* scale the movement to the guest */ /* scale the movement to the guest */
if (g_cursor.useScale && g_params.scaleMouseInput) if (g_cursor.useScale && g_params.scaleMouseInput)
{ {
@@ -600,7 +661,9 @@ void core_handleMouseNormal(double ex, double ey)
const bool inView = isInView(); const bool inView = isInView();
if (!g_cursor.inView) if (!g_cursor.inView)
{ {
if (inView) if (g_cursor.viewReq)
return;
else if (inView)
g_cursor.realign = true; g_cursor.realign = true;
else /* nothing to do if we are outside the viewport */ else /* nothing to do if we are outside the viewport */
return; return;
@@ -691,6 +754,9 @@ fallback:
ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx); ex += guest.x - (g_cursor.guest.x + g_cursor.guest.hx);
ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy); ey += guest.y - (g_cursor.guest.y + g_cursor.guest.hy);
core_setCursorInView(true); core_setCursorInView(true);
if (!g_cursor.inView)
return;
} }
g_cursor.realign = false; g_cursor.realign = false;

View File

@@ -26,6 +26,7 @@
bool core_inputEnabled(void); bool core_inputEnabled(void);
void core_invalidatePointer(bool detectInView); void core_invalidatePointer(bool detectInView);
void core_setCursorInView(bool enable); void core_setCursorInView(bool enable);
void core_handleGrabEvent(bool active);
void core_setGrab(bool enable); void core_setGrab(bool enable);
void core_setGrabQuiet(bool enable); void core_setGrabQuiet(bool enable);
bool core_warpPointer(int x, int y, bool exiting); bool core_warpPointer(int x, int y, bool exiting);

View File

@@ -131,6 +131,7 @@ static void lgInit(void)
g_cursor.scale.y = 1.0; g_cursor.scale.y = 1.0;
g_cursor.draw = false; g_cursor.draw = false;
g_cursor.inView = false; g_cursor.inView = false;
g_cursor.viewReq = false;
g_cursor.guest.valid = false; g_cursor.guest.valid = false;
// if spice is not in use, hide the local cursor // if spice is not in use, hide the local cursor

View File

@@ -295,6 +295,9 @@ struct CursorState
/* true if the cursor is currently in the guest view area */ /* true if the cursor is currently in the guest view area */
bool inView; 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 */ /* true if the guest should be realigned to the host when next drawn */
bool realign; bool realign;

View File

@@ -130,7 +130,15 @@ static void ungrab(void)
push(EV_UNGRAB, 0, 0, false); push(EV_UNGRAB, 0, 0, false);
m.conf.req = false; m.conf.req = false;
if (!m.conf.hold) if (!m.conf.hold)
{
m.conf.on = false; m.conf.on = false;
core_handleGrabEvent(false);
}
}
static bool grabbed(void)
{
return m.conf.on;
} }
static void capture(void) static void capture(void)
@@ -186,6 +194,7 @@ static struct LG_DisplayServerOps ds = {
.ungrabKeyboard = keyNoop, .ungrabKeyboard = keyNoop,
.grabPointer = grab, .grabPointer = grab,
.ungrabPointer = ungrab, .ungrabPointer = ungrab,
.isPointerGrabbed = grabbed,
.capturePointer = capture, .capturePointer = capture,
.uncapturePointer = uncapture, .uncapturePointer = uncapture,
.isPointerCaptured = captured, .isPointerCaptured = captured,
@@ -247,6 +256,8 @@ static void reset(void)
m.support = LG_DS_WARP_SURFACE; m.support = LG_DS_WARP_SURFACE;
m.valid = true; m.valid = true;
m.conf.req = true;
m.conf.on = true;
g_state.ds = &ds; g_state.ds = &ds;
g_state.focused = true; g_state.focused = true;
@@ -269,6 +280,7 @@ static void reset(void)
g_cursor.inWindow = true; g_cursor.inWindow = true;
g_cursor.inView = true; g_cursor.inView = true;
g_cursor.viewReq = true;
g_cursor.warpState = WARP_STATE_ON; g_cursor.warpState = WARP_STATE_ON;
g_cursor.guest.valid = true; g_cursor.guest.valid = true;
g_cursor.scale = (struct DoublePoint) { 1.0, 1.0 }; g_cursor.scale = (struct DoublePoint) { 1.0, 1.0 };
@@ -315,8 +327,6 @@ static void startExit(void)
{ {
reset(); reset();
setLocal(109, 50); setLocal(109, 50);
m.conf.req = true;
m.conf.on = true;
m.conf.hold = true; m.conf.hold = true;
core_handleMouseNormal(2, 0); core_handleMouseNormal(2, 0);
@@ -343,17 +353,29 @@ static void startConf(void)
{ {
reset(); reset();
setLocal(50, 50); setLocal(50, 50);
m.conf.req = false;
m.conf.on = false;
g_cursor.inView = false; g_cursor.inView = false;
g_cursor.viewReq = false;
core_handleMouseNormal(0, 0); core_handleMouseNormal(0, 0);
} }
static void setConf(bool active)
{
m.conf.on = active;
core_handleGrabEvent(active);
}
static void testConfWait(void) static void testConfWait(void)
{ {
startConf(); startConf();
CHECK(m.conf.req); CHECK(m.conf.req);
CHECK(!m.conf.on); CHECK(!m.conf.on);
CHECK(!g_cursor.inView); CHECK(!g_cursor.inView);
setConf(true);
CHECK(g_cursor.inView);
} }
static void testConfGuest(void) static void testConfGuest(void)
@@ -362,6 +384,10 @@ static void testConfGuest(void)
core_handleGuestMouseUpdate(); core_handleGuestMouseUpdate();
CHECK(m.conf.req); CHECK(m.conf.req);
CHECK(count(EV_GUEST) == 0); CHECK(count(EV_GUEST) == 0);
setConf(true);
core_handleGuestMouseUpdate();
CHECK(count(EV_GUEST) == 1);
} }
static void testCapWait(void) static void testCapWait(void)