ephyr: Add -host-grab to set custom grab shortcut

Allows for calling Xephyr with `-host-grab [keys]` to customize the
keyboard shortcut for grabbing/releasing keyboard and mouse input.
Fully backwards compatible:
Omitting `-host-grab` defaults to ctrl+shift.
`-no-host-grab` acts the same as before.

Closes: #134

Signed-off-by: Steven Van Dorp <steven@vandorp.lu>
This commit is contained in:
Steven Van Dorp 2025-06-18 09:25:47 +02:00 committed by Enrico Weigelt
parent 61accf16e2
commit b393d5fc02
4 changed files with 230 additions and 65 deletions

View File

@ -61,7 +61,12 @@ typedef struct _EphyrInputPrivate {
Bool EphyrWantGrayScale = 0; Bool EphyrWantGrayScale = 0;
Bool EphyrWantResize = 0; Bool EphyrWantResize = 0;
Bool EphyrWantNoHostGrab = 0;
static xcb_mod_mask_t EphyrKeybindToggleHostGrabModMask;
static uint32_t EphyrKeybindToggleHostGrabKey;
static char const* EphyrTitleHostGrabKeyComboHint;
static uint8_t EphyrTitleHostGrabKeyComboHintLen;
static Bool EphyrHostGrabSet = FALSE;
Bool Bool
ephyrInitialize(KdCardInfo * card, EphyrPriv * priv) ephyrInitialize(KdCardInfo * card, EphyrPriv * priv)
@ -649,6 +654,114 @@ ephyrCreateColormap(ColormapPtr pmap)
return fbInitializeColormap(pmap); return fbInitializeColormap(pmap);
} }
Bool
ephyrSetGrabShortcut(char const* const desc)
{
if (desc == NULL || !strcmp(desc, "NULL")) {
EphyrKeybindToggleHostGrabModMask = 0;
EphyrKeybindToggleHostGrabKey = 0;
EphyrTitleHostGrabKeyComboHint = NULL;
EphyrTitleHostGrabKeyComboHintLen = 0;
}
else {
const uint8_t fixed_bound = 255;
char buf[16];
uint8_t j = 0;
for (uint8_t i = 0;; ++i) {
assert(i < fixed_bound);
char const c = desc[i];
if (c == 0 || (j != 0 && c == '+')) {
buf[j] = 0;
if (j == 1) {
EphyrKeybindToggleHostGrabKey = buf[0];
}
else if (!strcmp(buf, "ctrl")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_CONTROL;
}
else if (!strcmp(buf, "shift")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_SHIFT;
}
else if (!strcmp(buf, "lock")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_LOCK;
}
else if (!strcmp(buf, "mod1")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_1;
}
else if (!strcmp(buf, "mod2")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_2;
}
else if (!strcmp(buf, "mod3")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_3;
}
else if (!strcmp(buf, "mod4")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_4;
}
else if (!strcmp(buf, "mod5")) {
EphyrKeybindToggleHostGrabModMask |= XCB_MOD_MASK_5;
}
else {
ErrorF("ephyr: -host-grab: "
"Unrecognized key: '%s'\n", buf);
return FALSE;
}
if (c == 0) break;
j = 0;
}
else {
buf[j] = c;
++j;
assert(j < sizeof(buf));
}
}
EphyrTitleHostGrabKeyComboHint = desc;
EphyrTitleHostGrabKeyComboHintLen = strlen(desc);
}
EphyrHostGrabSet = TRUE;
return TRUE;
}
static void
ephyrPrintGrabShortcut(char* const out, size_t const out_size,
Bool const currently_grabbed)
{
if (
(
EphyrKeybindToggleHostGrabModMask == 0 &&
EphyrKeybindToggleHostGrabKey == 0
) || (
EphyrTitleHostGrabKeyComboHint == 0 ||
EphyrTitleHostGrabKeyComboHint == 0
)
) {
/* grabbing disabled */
out[0] = '\0';
return;
}
char const* const suffix = currently_grabbed
? " releases mouse and keyboard)"
: " grabs mouse and keyboard)";
size_t const suffix_len = strlen(suffix);
assert(out_size > 1 + EphyrTitleHostGrabKeyComboHintLen + suffix_len + 1);
assert(out != NULL);
out[0] = '(';
memcpy(out + 1, EphyrTitleHostGrabKeyComboHint, EphyrTitleHostGrabKeyComboHintLen);
memcpy(out + EphyrTitleHostGrabKeyComboHintLen + 1, suffix, suffix_len + 1);
}
static void
ephyrUpdateWindowTitle(KdScreenInfo* const screen, Bool const currently_grabbed)
{
char title_buf[128];
ephyrPrintGrabShortcut(title_buf, sizeof(title_buf), currently_grabbed);
hostx_set_win_title(screen, title_buf);
}
Bool Bool
ephyrInitScreen(ScreenPtr pScreen) ephyrInitScreen(ScreenPtr pScreen)
{ {
@ -657,11 +770,10 @@ ephyrInitScreen(ScreenPtr pScreen)
EPHYR_LOG("pScreen->myNum:%d\n", pScreen->myNum); EPHYR_LOG("pScreen->myNum:%d\n", pScreen->myNum);
hostx_set_screen_number(screen, pScreen->myNum); hostx_set_screen_number(screen, pScreen->myNum);
if (EphyrWantNoHostGrab) { if (!EphyrHostGrabSet) {
hostx_set_win_title(screen, "xephyr"); ephyrSetGrabShortcut("ctrl+shift");
} else {
hostx_set_win_title(screen, "(ctrl+shift grabs mouse and keyboard)");
} }
ephyrUpdateWindowTitle(screen, FALSE);
pScreen->CreateColormap = ephyrCreateColormap; pScreen->CreateColormap = ephyrCreateColormap;
#ifdef XV #ifdef XV
@ -1019,71 +1131,78 @@ ephyrProcessKeyPress(xcb_generic_event_t *xev)
static void static void
ephyrProcessKeyRelease(xcb_generic_event_t *xev) ephyrProcessKeyRelease(xcb_generic_event_t *xev)
{ {
xcb_connection_t *conn = hostx_get_xcbconn();
xcb_key_release_event_t *key = (xcb_key_release_event_t *)xev; xcb_key_release_event_t *key = (xcb_key_release_event_t *)xev;
static xcb_key_symbols_t *keysyms; if (EphyrKeybindToggleHostGrabModMask != 0 ||
static int grabbed_screen = -1; EphyrKeybindToggleHostGrabKey != 0) {
int mod1_down = ephyrUpdateGrabModifierState(key->state);
if (!keysyms) xcb_connection_t *conn = hostx_get_xcbconn();
keysyms = xcb_key_symbols_alloc(conn); static xcb_key_symbols_t *keysyms;
static int grabbed_screen = -1;
if (!EphyrWantNoHostGrab && if (!keysyms)
(((xcb_key_symbols_get_keysym(keysyms, key->detail, 0) == XK_Shift_L keysyms = xcb_key_symbols_alloc(conn);
|| xcb_key_symbols_get_keysym(keysyms, key->detail, 0) == XK_Shift_R)
&& (key->state & XCB_MOD_MASK_CONTROL)) ||
((xcb_key_symbols_get_keysym(keysyms, key->detail, 0) == XK_Control_L
|| xcb_key_symbols_get_keysym(keysyms, key->detail, 0) == XK_Control_R)
&& (key->state & XCB_MOD_MASK_SHIFT)))) {
KdScreenInfo *screen = screen_from_window(key->event);
assert(screen);
EphyrScrPriv *scrpriv = screen->driver;
if (grabbed_screen != -1) { int const keysym =
xcb_ungrab_keyboard(conn, XCB_TIME_CURRENT_TIME); xcb_key_symbols_get_keysym(keysyms, key->detail, 0);
xcb_ungrab_pointer(conn, XCB_TIME_CURRENT_TIME);
grabbed_screen = -1; if (
hostx_set_win_title(screen, (
"(ctrl+shift grabs mouse and keyboard)"); (key->state & EphyrKeybindToggleHostGrabModMask) ==
} EphyrKeybindToggleHostGrabModMask
else if (!mod1_down) { ) && (
/* Attempt grab */ /* NOTE: mod-key keysyms are > 0xfe00. We do this so when the
xcb_grab_keyboard_cookie_t kbgrabc = shortcut is only mod-keys (e.g. ctrl+shift) and the user
xcb_grab_keyboard(conn, releases any other key, input doesn't get grabbed */
TRUE, (EphyrKeybindToggleHostGrabKey == 0 && keysym > 0xfe00) ||
scrpriv->win, keysym == EphyrKeybindToggleHostGrabKey
XCB_TIME_CURRENT_TIME, )
XCB_GRAB_MODE_ASYNC, ) {
XCB_GRAB_MODE_ASYNC); KdScreenInfo *screen = screen_from_window(key->event);
xcb_grab_keyboard_reply_t *kbgrabr; assert(screen);
xcb_grab_pointer_cookie_t pgrabc = EphyrScrPriv *scrpriv = screen->driver;
xcb_grab_pointer(conn,
TRUE, if (grabbed_screen != -1) {
scrpriv->win, xcb_ungrab_keyboard(conn, XCB_TIME_CURRENT_TIME);
0,
XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_ASYNC,
scrpriv->win,
XCB_NONE,
XCB_TIME_CURRENT_TIME);
xcb_grab_pointer_reply_t *pgrabr;
kbgrabr = xcb_grab_keyboard_reply(conn, kbgrabc, NULL);
if (!kbgrabr || kbgrabr->status != XCB_GRAB_STATUS_SUCCESS) {
xcb_discard_reply(conn, pgrabc.sequence);
xcb_ungrab_pointer(conn, XCB_TIME_CURRENT_TIME); xcb_ungrab_pointer(conn, XCB_TIME_CURRENT_TIME);
} else { grabbed_screen = -1;
pgrabr = xcb_grab_pointer_reply(conn, pgrabc, NULL); }
if (!pgrabr || pgrabr->status != XCB_GRAB_STATUS_SUCCESS) else {
{ /* Attempt grab */
xcb_ungrab_keyboard(conn, xcb_grab_keyboard_cookie_t kbgrabc =
XCB_TIME_CURRENT_TIME); xcb_grab_keyboard(conn,
} else { TRUE,
grabbed_screen = scrpriv->mynum; scrpriv->win,
hostx_set_win_title XCB_TIME_CURRENT_TIME,
(screen, XCB_GRAB_MODE_ASYNC,
"(ctrl+shift releases mouse and keyboard)"); XCB_GRAB_MODE_ASYNC);
xcb_grab_keyboard_reply_t *kbgrabr;
xcb_grab_pointer_cookie_t pgrabc =
xcb_grab_pointer(conn,
TRUE,
scrpriv->win,
0,
XCB_GRAB_MODE_ASYNC,
XCB_GRAB_MODE_ASYNC,
scrpriv->win,
XCB_NONE,
XCB_TIME_CURRENT_TIME);
xcb_grab_pointer_reply_t *pgrabr;
kbgrabr = xcb_grab_keyboard_reply(conn, kbgrabc, NULL);
if (!kbgrabr || kbgrabr->status != XCB_GRAB_STATUS_SUCCESS) {
xcb_discard_reply(conn, pgrabc.sequence);
xcb_ungrab_pointer(conn, XCB_TIME_CURRENT_TIME);
} else {
pgrabr = xcb_grab_pointer_reply(conn, pgrabc, NULL);
if (!pgrabr || pgrabr->status != XCB_GRAB_STATUS_SUCCESS)
{
xcb_ungrab_keyboard(conn,
XCB_TIME_CURRENT_TIME);
} else {
grabbed_screen = scrpriv->mynum;
}
} }
} }
ephyrUpdateWindowTitle(screen, grabbed_screen != -1);
} }
} }

View File

@ -167,6 +167,14 @@ Bool
Bool Bool
ephyrCreateColormap(ColormapPtr pmap); ephyrCreateColormap(ColormapPtr pmap);
/**
* @param desc examples: "ctrl+shift", "ctrl+mod1+a", "a",
* NULL (disables host grab)
* @return TRUE if success, otherwise FALSE
*/
Bool
ephyrSetGrabShortcut(char const* const desc);
#ifdef RANDR #ifdef RANDR
Bool Bool
ephyrRandRGetInfo(ScreenPtr pScreen, Rotation * rotations); ephyrRandRGetInfo(ScreenPtr pScreen, Rotation * rotations);

View File

@ -38,7 +38,6 @@
extern Window EphyrPreExistingHostWin; extern Window EphyrPreExistingHostWin;
extern Bool EphyrWantGrayScale; extern Bool EphyrWantGrayScale;
extern Bool EphyrWantResize; extern Bool EphyrWantResize;
extern Bool EphyrWantNoHostGrab;
extern Bool kdHasPointer; extern Bool kdHasPointer;
extern Bool kdHasKbd; extern Bool kdHasKbd;
extern Bool ephyr_glamor, ephyr_glamor_gles2, ephyr_glamor_skip_present; extern Bool ephyr_glamor, ephyr_glamor_gles2, ephyr_glamor_skip_present;
@ -144,6 +143,8 @@ ddxUseMsg(void)
ErrorF ErrorF
("-title [title] set the window title in the WM_NAME property\n"); ("-title [title] set the window title in the WM_NAME property\n");
ErrorF("-no-host-grab Disable grabbing the keyboard and mouse.\n"); ErrorF("-no-host-grab Disable grabbing the keyboard and mouse.\n");
ErrorF
("-host-grab [keys] set shortcut to grab the keyboard and mouse (default: ctrl+shift)\n");
ErrorF("\n"); ErrorF("\n");
} }
@ -343,9 +344,21 @@ ddxProcessArgument(int argc, char **argv, int i)
} }
/* end Xnest compat */ /* end Xnest compat */
else if (!strcmp(argv[i], "-no-host-grab")) { else if (!strcmp(argv[i], "-no-host-grab")) {
EphyrWantNoHostGrab = 1; ephyrSetGrabShortcut(NULL);
return 1; return 1;
} }
else if (!strcmp(argv[i], "-host-grab")) {
if (i + 1 >= argc) {
ErrorF(
"ephyr: -host-grab requires an argument e.g. ctrl+shift+x\n");
exit(1);
}
else if (!ephyrSetGrabShortcut(argv[i + 1])) {
/* specific error message is printed in ephyrSetGrabShortcut */
exit(1);
}
return 2;
}
else if (!strcmp(argv[i], "-sharevts") || else if (!strcmp(argv[i], "-sharevts") ||
!strcmp(argv[i], "-novtswitch")) { !strcmp(argv[i], "-novtswitch")) {
return 1; return 1;

View File

@ -67,6 +67,31 @@ window. By default, the Xephyr window has a fixed size.
.TP 8 .TP 8
.B \-no\-host\-grab .B \-no\-host\-grab
Disable grabbing the keyboard and mouse. Disable grabbing the keyboard and mouse.
.TP 8
.BI \-host\-grab " keys"
Set the keyboard shortcut for Xephyr to grab keyboard and mouse
input. Possible values for mod-keys are: ctrl, shift, lock,
mod1, mod2, mod3, mod4, mod5. Up to one ascii character (lower-case) can
be used by itself or in conjunction with mod-keys. Keys are concatenated
with
.I +\fP. If omitted, defaults to
.I ctrl+shift\fP.
Examples:
.RS
.IP \[bu] 2
.I ctrl+mod1
.IP \[bu] 2
.I ctrl+shift++
(note that the
.I +
at the end is interpreted as the ascii character '+')
.IP \[bu] 2
.I a
(mod-keys are optional, this will grab/release whenever the
.I a
key is pressed)
.RE
.SH "SIGNALS" .SH "SIGNALS"
Send a SIGUSR1 to the server (e.g. pkill \-USR1 Xephyr) to Send a SIGUSR1 to the server (e.g. pkill \-USR1 Xephyr) to
toggle the debugging mode. toggle the debugging mode.