[client] input: fix automatic keyboard capture

Treat autoCapture as keyboard ownership while the pointer is inside the
guest viewport. Predict the next local position for both absolute and
relative motion, then release the keyboard before the pointer crosses
an edge.

Keep capture, focus, and deferred evdev transitions consistent while
retaining the latest local position as the prediction baseline.
This commit is contained in:
Geoffrey McRae
2026-08-11 14:42:01 +10:00
parent d20904e3af
commit 77b661194a
9 changed files with 327 additions and 72 deletions

View File

@@ -130,17 +130,22 @@ bool app_isOverlayMode(void)
void app_updateCursorPos(double x, double y) void app_updateCursorPos(double x, double y)
{ {
const bool overlay = app_isOverlayMode();
if (overlay)
{
g_cursor.pos.x = x; g_cursor.pos.x = x;
g_cursor.pos.y = y; g_cursor.pos.y = y;
g_cursor.valid = true; g_cursor.valid = true;
g_cursor.motionValid = false;
}
else
core_handleMousePosition(x, y);
MTRACE("pos pos=%.3f,%.3f inWin=%d inView=%d grab=%d", MTRACE("pos pos=%.3f,%.3f inWin=%d inView=%d grab=%d",
x, y, g_cursor.inWindow, g_cursor.inView, g_cursor.grab); x, y, g_cursor.inWindow, g_cursor.inView, g_cursor.grab);
if (app_isOverlayMode()) if (overlay)
g_state.io->MousePos = (ImVec2) { x, y }; g_state.io->MousePos = (ImVec2) { x, y };
else
core_handleMouseAbsolute();
} }
void app_updateMouseState(void) void app_updateMouseState(void)
@@ -221,12 +226,20 @@ void app_handleFocusEvent(bool focused)
return; return;
g_state.focused = focused; g_state.focused = focused;
g_cursor.motionValid = false;
const bool inputEnabled = core_inputEnabled();
// release any imgui buttons/keys if we lost focus // release any imgui buttons/keys if we lost focus
if (!focused && app_isOverlayMode()) if (!focused && app_isOverlayMode())
core_resetOverlayInputState(); core_resetOverlayInputState();
if (!core_inputEnabled()) if (!focused)
{
core_setGrabQuiet(false);
core_setCursorInView(false);
}
if (!inputEnabled)
{ {
if (!focused && g_params.minimizeOnFocusLoss && app_getFullscreen()) if (!focused && g_params.minimizeOnFocusLoss && app_getFullscreen())
g_state.ds->minimize(); g_state.ds->minimize();
@@ -235,9 +248,6 @@ void app_handleFocusEvent(bool focused)
if (!focused) if (!focused)
{ {
core_setGrabQuiet(false);
core_setCursorInView(false);
if (g_params.releaseKeysOnFocusLoss) if (g_params.releaseKeysOnFocusLoss)
lgInput_releaseKeys(); lgInput_releaseKeys();
@@ -262,6 +272,8 @@ void app_handleEnterEvent(bool entered)
MTRACE("enter set=%d inWin=%d inView=%d grab=%d", MTRACE("enter set=%d inWin=%d inView=%d grab=%d",
entered, g_cursor.inWindow, g_cursor.inView, g_cursor.grab); entered, g_cursor.inWindow, g_cursor.inView, g_cursor.grab);
g_cursor.motionValid = false;
if (entered) if (entered)
{ {
g_cursor.inWindow = true; g_cursor.inWindow = true;

View File

@@ -412,7 +412,7 @@ static struct Option options[] =
{ {
.module = "input", .module = "input",
.name = "autoCapture", .name = "autoCapture",
.description = "Try to keep the mouse captured when needed", .description = "Grab the keyboard in the guest view and release it before exit",
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = false .value.x_bool = false
}, },

View File

@@ -137,6 +137,34 @@ bool core_inputEnabled(void)
((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly);
} }
static void updateKeyboardGrab(void)
{
const bool capture = g_cursor.grab && g_params.grabKeyboard;
const bool view = g_cursor.inView && g_params.grabKeyboardOnFocus;
const bool automatic = !g_cursor.grab &&
g_cursor.autoCaptureActive && !g_params.captureInputOnly;
const bool active = g_state.focused && core_inputEnabled() &&
!app_isOverlayMode() && (capture || view || automatic);
if (active)
g_state.ds->grabKeyboard();
else
g_state.ds->ungrabKeyboard();
}
static void setAutoCapture(bool active)
{
active = active && g_params.autoCapture && g_cursor.inView &&
!g_cursor.grab && !g_params.captureInputOnly;
if (g_cursor.autoCaptureActive == active)
return;
MTRACE("auto capture active=%d old=%d", active,
g_cursor.autoCaptureActive);
g_cursor.autoCaptureActive = active;
updateKeyboardGrab();
}
static void applyView(bool active, bool force) static void applyView(bool active, bool force)
{ {
if (active && !g_cursor.viewReq) if (active && !g_cursor.viewReq)
@@ -146,33 +174,36 @@ static void applyView(bool active, bool force)
} }
if (!force && g_cursor.inView == active) if (!force && g_cursor.inView == active)
{
updateKeyboardGrab();
return; return;
}
MTRACE("view active=%d old=%d req=%d force=%d", active, MTRACE("view active=%d old=%d req=%d force=%d", active,
g_cursor.inView, g_cursor.viewReq, force); g_cursor.inView, g_cursor.viewReq, force);
g_cursor.inView = active; 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;
g_cursor.motionValid = false;
g_cursor.autoCaptureActive = active && g_params.autoCapture &&
!g_cursor.grab && !g_params.captureInputOnly;
g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF; g_cursor.warpState = g_cursor.inView ? WARP_STATE_ON : WARP_STATE_OFF;
if (g_cursor.inView) if (g_cursor.inView)
{ {
if (g_params.hideMouse) if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_NONE); g_state.ds->setPointer(LG_POINTER_NONE);
if (g_params.grabKeyboardOnFocus)
g_state.ds->grabKeyboard();
} }
else else
{ {
if (g_params.hideMouse) if (g_params.hideMouse)
g_state.ds->setPointer(LG_POINTER_SQUARE); g_state.ds->setPointer(LG_POINTER_SQUARE);
g_state.ds->ungrabKeyboard();
} }
updateKeyboardGrab();
g_cursor.warpState = WARP_STATE_ON; g_cursor.warpState = WARP_STATE_ON;
} }
@@ -277,9 +308,9 @@ void core_setGrabQuiet(bool enable)
return; return;
cancelSurfaceExit("capture"); cancelSurfaceExit("capture");
g_cursor.grab = enable;
g_cursor.acc.x = 0.0; g_cursor.acc.x = 0.0;
g_cursor.acc.y = 0.0; g_cursor.acc.y = 0.0;
g_cursor.motionValid = false;
/* if the display server does not support warp we need to ungrab the pointer /* if the display server does not support warp we need to ungrab the pointer
* here instead of in the move handler */ * here instead of in the move handler */
@@ -288,8 +319,11 @@ void core_setGrabQuiet(bool enable)
if (enable) if (enable)
{ {
core_setCursorInView(true);
g_state.ignoreInput = false; g_state.ignoreInput = false;
g_cursor.grab = true;
g_cursor.autoCaptureActive = false;
updateKeyboardGrab();
core_setCursorInView(true);
/* ensure the local mouse is inside the window before we capture, this fixes /* ensure the local mouse is inside the window before we capture, this fixes
* odd UI behaviour if the user is using focus follows mouse and the window * odd UI behaviour if the user is using focus follows mouse and the window
@@ -302,29 +336,21 @@ void core_setGrabQuiet(bool enable)
core_warpPointer(local.x, local.y, true); core_warpPointer(local.x, local.y, true);
} }
if (g_params.grabKeyboard)
g_state.ds->grabKeyboard();
g_state.ds->capturePointer(); g_state.ds->capturePointer();
} }
else else
{ {
if (g_params.grabKeyboard)
{
if (!g_params.grabKeyboardOnFocus ||
!g_state.focused || g_params.captureInputOnly)
g_state.ds->ungrabKeyboard();
}
g_state.ds->uncapturePointer(); g_state.ds->uncapturePointer();
g_cursor.grab = false;
g_cursor.autoCaptureActive = g_params.autoCapture &&
g_cursor.inView && !g_params.captureInputOnly;
updateKeyboardGrab();
if (warpSupport == LG_DS_WARP_NONE) if (warpSupport == LG_DS_WARP_NONE)
core_handleMouseAbsolute(); core_handleMouseAbsolute();
else else
{ {
if (!g_params.captureInputOnly) g_cursor.warpState = WARP_STATE_ON;
applyView(g_state.ds->isPointerGrabbed(), false);
core_alignToGuest(); core_alignToGuest();
} }
} }
@@ -369,6 +395,7 @@ bool core_warpPointer(int x, int y, bool exiting)
MTRACE("warp send target=%d,%d exit=%d pos=%.3f,%.3f", MTRACE("warp send target=%d,%d exit=%d pos=%.3f,%.3f",
x, y, exiting, g_cursor.pos.x, g_cursor.pos.y); x, y, exiting, g_cursor.pos.x, g_cursor.pos.y);
g_cursor.motionValid = false;
g_state.ds->warpPointer(x, y, exiting); g_state.ds->warpPointer(x, y, exiting);
return true; return true;
} }
@@ -397,6 +424,7 @@ void core_onWindowSizeChanged(unsigned width, unsigned height)
void core_updatePositionInfo(void) void core_updatePositionInfo(void)
{ {
g_cursor.motionValid = false;
cancelExit("geometry"); cancelExit("geometry");
if (g_params.setGuestRes && if (g_params.setGuestRes &&
@@ -679,13 +707,12 @@ void core_handleGuestMouseUpdate(void)
); );
} }
void core_handleMouseAbsolute(void) static void handleMouseAbsolute(bool inputEnabled, bool absolute)
{ {
if (g_cursor.grab || !g_cursor.inWindow || !g_cursor.valid || if (g_cursor.grab || !g_cursor.inWindow || !g_cursor.valid ||
!g_state.haveSrcSize || !g_state.posInfoValid || !g_state.haveSrcSize || !g_state.posInfoValid ||
!g_state.focused || g_cursor.realigning || !g_state.focused || g_cursor.realigning ||
app_isOverlayMode() || !core_inputEnabled() || app_isOverlayMode() || !inputEnabled || !absolute)
!lgInput_supports(LG_INPUT_SUPPORT_MOUSE_ABSOLUTE))
return; return;
const bool inView = isInView(); const bool inView = isInView();
@@ -711,6 +738,79 @@ void core_handleMouseAbsolute(void)
g_cursor.realign = false; g_cursor.realign = false;
} }
void core_handleMouseAbsolute(void)
{
const bool inputEnabled = core_inputEnabled();
const bool absolute = inputEnabled &&
lgInput_supports(LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
handleMouseAbsolute(inputEnabled, absolute);
}
void core_handleMousePosition(double x, double y)
{
bool allowPrediction = g_cursor.motionValid && !g_cursor.realign;
const double ex = allowPrediction ? x - g_cursor.pos.x : 0.0;
const double ey = allowPrediction ? y - g_cursor.pos.y : 0.0;
g_cursor.pos.x = x;
g_cursor.pos.y = y;
g_cursor.valid = true;
g_cursor.motionValid = true;
const bool inputEnabled = core_inputEnabled();
const bool absolute = inputEnabled &&
lgInput_supports(LG_INPUT_SUPPORT_MOUSE_ABSOLUTE);
const bool canTrack = !g_cursor.grab && g_cursor.inWindow &&
g_state.haveSrcSize && g_state.posInfoValid &&
g_state.dstRect.valid && g_state.focused &&
!app_isOverlayMode() && inputEnabled;
if (canTrack && !g_cursor.realigning)
{
const bool inView = isInView();
if (g_cursor.viewReq != inView || g_cursor.inView != inView)
{
core_setCursorInView(inView);
allowPrediction = false;
g_cursor.motionValid = true;
}
}
handleMouseAbsolute(inputEnabled, absolute);
if (!canTrack)
{
g_cursor.motionValid = false;
setAutoCapture(false);
return;
}
if (g_cursor.realigning)
{
g_cursor.motionValid = false;
return;
}
bool active = g_cursor.inView && g_cursor.viewReq;
if (!allowPrediction || !g_params.autoCapture ||
!active || g_cursor.buttons)
{
setAutoCapture(active);
return;
}
const double nextX = x + ex;
const double nextY = y + ey;
active =
nextX >= g_state.dstRect.x &&
nextX < g_state.dstRect.x + g_state.dstRect.w &&
nextY >= g_state.dstRect.y &&
nextY < g_state.dstRect.y + g_state.dstRect.h;
MTRACE("auto capture move=%.3f,%.3f next=%.3f,%.3f inView=%d",
ex, ey, nextX, nextY, active);
setAutoCapture(active);
}
void core_handleMouseGrabbed(double ex, double ey) void core_handleMouseGrabbed(double ex, double ey)
{ {
const bool active = g_cursor.grab && g_state.ds->isPointerCaptured(); const bool active = g_cursor.grab && g_state.ds->isPointerCaptured();
@@ -911,11 +1011,6 @@ fallback:
testExit = false; testExit = false;
} }
/* if we are in "autoCapture" and the delta was large don't test for exit */
if (g_params.autoCapture &&
(fabs(ex) > 20.0 / g_cursor.scale.x || fabs(ey) > 20.0 / g_cursor.scale.y))
testExit = false;
/* if any buttons are held we should not allow exit to happen */ /* if any buttons are held we should not allow exit to happen */
if (g_cursor.buttons) if (g_cursor.buttons)
testExit = false; testExit = false;
@@ -989,6 +1084,9 @@ fallback:
} }
} }
if (g_params.autoCapture && testExit)
setAutoCapture(!didExit);
if (absolute) if (absolute)
return; return;
@@ -998,25 +1096,11 @@ fallback:
if (x == 0 && y == 0) if (x == 0 && y == 0)
return; return;
if (g_params.autoCapture)
{
g_cursor.delta.x += x;
g_cursor.delta.y += y;
if (fabs(g_cursor.delta.x) > 50.0 || fabs(g_cursor.delta.y) > 50.0)
{
g_cursor.delta.x = 0;
g_cursor.delta.y = 0;
}
}
else
{
/* assume the mouse will move to the location we attempt to move it to so we /* assume the mouse will move to the location we attempt to move it to so we
* avoid warp out of window issues. The cursorThread will correct this if * avoid warp out of window issues. The cursorThread will correct this if
* wrong after the movement has ocurred on the guest */ * wrong after the movement has ocurred on the guest */
g_cursor.guest.x += x; g_cursor.guest.x += x;
g_cursor.guest.y += y; g_cursor.guest.y += y;
}
MTRACE("motion delta=%d,%d guest=%d,%d exit=%d test=%d warp=%d", MTRACE("motion delta=%d,%d guest=%d,%d exit=%d test=%d warp=%d",
x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit, x, y, g_cursor.guest.x, g_cursor.guest.y, didExit, testExit,

View File

@@ -40,6 +40,7 @@ bool core_startFrameThread(void);
void core_stopFrameThread(void); void core_stopFrameThread(void);
void core_handleGuestMouseUpdate(void); void core_handleGuestMouseUpdate(void);
void core_handleMouseAbsolute(void); void core_handleMouseAbsolute(void);
void core_handleMousePosition(double x, double y);
void core_handleMouseGrabbed(double ex, double ey); void core_handleMouseGrabbed(double ex, double ey);
void core_handleMouseNormal(double ex, double ey); void core_handleMouseNormal(double ex, double ey);
void core_resetOverlayInputState(void); void core_resetOverlayInputState(void);

View File

@@ -381,6 +381,7 @@ void evdev_stop(void)
void evdev_grabKeyboard(void) void evdev_grabKeyboard(void)
{ {
state.pending = PENDING_NONE;
if (state.grabbed) if (state.grabbed)
return; return;
@@ -405,6 +406,7 @@ void evdev_grabKeyboard(void)
void evdev_ungrabKeyboard(void) void evdev_ungrabKeyboard(void)
{ {
state.pending = PENDING_NONE;
if (!state.grabbed) if (!state.grabbed)
return; return;

View File

@@ -316,12 +316,15 @@ struct CursorState
/* true if the position is valid */ /* true if the position is valid */
bool valid; bool valid;
/* true if the last local position can be used to predict motion */
bool motionValid;
/* true if auto capture currently requests the keyboard */
bool autoCaptureActive;
/* the button state */ /* the button state */
unsigned int buttons; unsigned int buttons;
/* the delta since last warp when in auto capture mode */
struct DoublePoint delta;
/* the scale factor for the mouse sensitiviy */ /* the scale factor for the mouse sensitiviy */
int sens; int sens;

View File

@@ -121,6 +121,7 @@ set(MOUSE_CASES
exit-immediate exit-immediate
exit-guest exit-guest
absolute-sync absolute-sync
auto-capture
exit-reentry exit-reentry
view-immediate view-immediate
capture-pending capture-pending

View File

@@ -42,6 +42,8 @@ enum EvType
EV_GUEST, EV_GUEST,
EV_MOTION, EV_MOTION,
EV_VALID, EV_VALID,
EV_KEY_GRAB,
EV_KEY_UNGRAB,
}; };
struct Ev struct Ev
@@ -68,6 +70,7 @@ static struct
struct Trap lock; struct Trap lock;
bool valid; bool valid;
bool captureGrab; bool captureGrab;
bool keyboardGrabbed;
struct Ev ev[256]; struct Ev ev[256];
unsigned int count; unsigned int count;
} }
@@ -119,8 +122,22 @@ static void setPointer(LG_DSPointer pointer)
(void)pointer; (void)pointer;
} }
static void keyNoop(void) static void keyGrab(void)
{ {
if (m.keyboardGrabbed)
return;
m.keyboardGrabbed = true;
push(EV_KEY_GRAB, 0, 0, false);
}
static void keyUngrab(void)
{
if (!m.keyboardGrabbed)
return;
m.keyboardGrabbed = false;
push(EV_KEY_UNGRAB, 0, 0, false);
} }
static void grab(void) static void grab(void)
@@ -209,8 +226,8 @@ static struct LG_DisplayServerOps ds = {
.name = "test", .name = "test",
.guestPointerUpdated = guest, .guestPointerUpdated = guest,
.setPointer = setPointer, .setPointer = setPointer,
.grabKeyboard = keyNoop, .grabKeyboard = keyGrab,
.ungrabKeyboard = keyNoop, .ungrabKeyboard = keyUngrab,
.grabPointer = grab, .grabPointer = grab,
.ungrabPointer = ungrab, .ungrabPointer = ungrab,
.isPointerGrabbed = grabbed, .isPointerGrabbed = grabbed,
@@ -511,6 +528,141 @@ static void testAbsoluteSync(void)
lgInput_setFallback(&inputOps, NULL); lgInput_setFallback(&inputOps, NULL);
} }
static void testAutoCapture(void)
{
static const enum LG_DSWarpSupport supports[] = {
LG_DS_WARP_SURFACE,
LG_DS_WARP_SCREEN,
};
for (unsigned int i = 0; i < sizeof(supports) / sizeof(supports[0]); ++i)
{
reset();
m.support = supports[i];
g_params.autoCapture = true;
g_cursor.inView = false;
g_cursor.viewReq = false;
setLocal(50, 50);
core_invalidatePointer(true);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
setLocal(109, 50);
m.count = 0;
core_handleMouseNormal(1, 0);
CHECK(!m.keyboardGrabbed);
CHECK(count(EV_KEY_UNGRAB) == 1);
CHECK(g_cursor.exit);
CHECK(!g_cursor.inView);
CHECK(!g_cursor.viewReq);
setLocal(108, 50);
m.count = 0;
core_handleMouseNormal(-1, 0);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
CHECK(!g_cursor.exit);
CHECK(g_cursor.inView);
CHECK(g_cursor.viewReq);
}
bool absolute = true;
lgInput_setFallback(&inputOps, &absolute);
reset();
g_params.autoCapture = true;
g_cursor.inView = false;
g_cursor.viewReq = false;
g_cursor.valid = true;
setLocal(50, 50);
core_handleMousePosition(50, 50);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
g_cursor.motionValid = false;
core_handleMousePosition(108, 50);
m.count = 0;
core_handleMousePosition(109, 50);
CHECK(!m.keyboardGrabbed);
CHECK(count(EV_KEY_UNGRAB) == 1);
CHECK(g_cursor.inView);
CHECK(g_cursor.viewReq);
m.count = 0;
core_handleMousePosition(108, 50);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
CHECK(g_cursor.inView);
CHECK(g_cursor.viewReq);
reset();
g_params.autoCapture = true;
g_cursor.inView = false;
g_cursor.viewReq = false;
g_cursor.valid = true;
g_cursor.motionValid = true;
setLocal(9, 50);
core_handleMousePosition(109, 50);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
CHECK(g_cursor.motionValid);
m.count = 0;
core_handleMousePosition(109.5, 50);
CHECK(!m.keyboardGrabbed);
CHECK(count(EV_KEY_UNGRAB) == 1);
reset();
g_params.autoCapture = true;
g_cursor.inView = false;
g_cursor.viewReq = false;
g_cursor.valid = true;
g_state.posInfoValid = false;
setLocal(50, 50);
core_handleMousePosition(50, 50);
CHECK(!m.keyboardGrabbed);
CHECK(!g_cursor.inView);
CHECK(!g_cursor.viewReq);
g_state.posInfoValid = true;
g_cursor.realigning = true;
core_handleMousePosition(50, 50);
CHECK(!m.keyboardGrabbed);
CHECK(!g_cursor.inView);
CHECK(!g_cursor.viewReq);
lgInput_setFallback(&inputOps, NULL);
reset();
g_params.grabKeyboard = true;
g_state.ignoreInput = true;
core_setGrabQuiet(true);
CHECK(!g_state.ignoreInput);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 1);
reset();
m.support = LG_DS_WARP_SCREEN;
m.captureGrab = true;
m.conf.req = false;
m.conf.on = false;
g_params.autoCapture = true;
g_params.grabKeyboard = true;
g_cursor.autoCaptureActive = true;
keyGrab();
core_setGrabQuiet(true);
m.count = 0;
core_setGrabQuiet(false);
CHECK(m.keyboardGrabbed);
CHECK(count(EV_KEY_GRAB) == 0);
CHECK(count(EV_KEY_UNGRAB) == 0);
}
static void testExitReentry(void) static void testExitReentry(void)
{ {
startExit(); startExit();
@@ -624,7 +776,6 @@ static void testCapAlign(void)
m.lock.on = true; m.lock.on = true;
g_cursor.guest.x += 10; g_cursor.guest.x += 10;
g_cursor.warpState = WARP_STATE_ON;
m.count = 0; m.count = 0;
core_setGrabQuiet(false); core_setGrabQuiet(false);
@@ -925,6 +1076,7 @@ static const struct Test tests[] = {
{ "exit-immediate" , testExitImmediate}, { "exit-immediate" , testExitImmediate},
{ "exit-guest" , testExitGuest }, { "exit-guest" , testExitGuest },
{ "absolute-sync" , testAbsoluteSync }, { "absolute-sync" , testAbsoluteSync },
{ "auto-capture" , testAutoCapture },
{ "exit-reentry" , testExitReentry }, { "exit-reentry" , testExitReentry },
{ "view-immediate" , testViewImmediate}, { "view-immediate" , testViewImmediate},
{ "capture-pending" , testCapWait }, { "capture-pending" , testCapWait },

View File

@@ -328,7 +328,7 @@ All command line options
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
| input:mouseTrace | | no | Enable mouse input diagnostics | | input:mouseTrace | | no | Enable mouse input diagnostics |
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
| input:autoCapture | | no | Try to keep the mouse captured when needed | | input:autoCapture | | no | Grab the keyboard in the guest view and release it before exit |
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+
| input:captureOnly | | no | Only enable input via SPICE if in capture mode | | input:captureOnly | | no | Only enable input via SPICE if in capture mode |
+------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+ +------------------------------+-------+---------------------+----------------------------------------------------------------------------------------------------------+