SetInputFocus to the i3lock window to force-close context menus (#155)

When grabbing the pointer/keyboard fails, a new code path is activated, which:

1. Uses the standards-compliant _NET_ACTIVE_WINDOW root window property to
   determine the window to restore focus to.

2. Sets the input focus to the i3lock window, thereby possibly force-closing
   open context menus (works with e.g. Google Chrome, does not work with
   e.g. thunar, gedit).

3. Upon exiting, restores focus to the window from step ① by sending a
   _NET_ACTIVE_WINDOW ClientMessage to the root window. Note that this step
   requires https://github.com/i3/i3/pull/3027 in i3 to not mess up focus.

fixes https://github.com/i3/i3lock/issues/35
pull/158/head
Michael Stapelberg 2017-10-22 22:16:34 +02:00 committed by GitHub
parent d3636246de
commit 5b4d45a8af
3 changed files with 110 additions and 15 deletions

View File

@ -35,6 +35,7 @@
#ifdef __OpenBSD__
#include <strings.h> /* explicit_bzero(3) */
#endif
#include <xcb/xcb_aux.h>
#include "i3lock.h"
#include "xcb.h"
@ -278,7 +279,8 @@ static void input_done(void) {
DEBUG("successfully authenticated\n");
clear_password_memory();
exit(0);
ev_break(EV_DEFAULT, EVBREAK_ALL);
return;
}
#else
if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) {
@ -292,7 +294,8 @@ static void input_done(void) {
pam_setcred(pam_handle, PAM_REFRESH_CRED);
pam_end(pam_handle, PAM_SUCCESS);
exit(0);
ev_break(EV_DEFAULT, EVBREAK_ALL);
return;
}
#endif
@ -1010,6 +1013,8 @@ int main(int argc, char *argv[]) {
/* Pixmap on which the image is rendered to (if any) */
xcb_pixmap_t bg_pixmap = draw_image(last_resolution);
xcb_window_t stolen_focus = find_focused_window(conn, screen->root);
/* Open the fullscreen window, already with the correct pixmap in place */
win = open_fullscreen_window(conn, screen, color, bg_pixmap);
xcb_free_pixmap(conn, bg_pixmap);
@ -1018,7 +1023,23 @@ int main(int argc, char *argv[]) {
/* Display the "locking…" message while trying to grab the pointer/keyboard. */
auth_state = STATE_AUTH_LOCK;
grab_pointer_and_keyboard(conn, screen, cursor);
if (!grab_pointer_and_keyboard(conn, screen, cursor, 1000)) {
DEBUG("stole focus from X11 window 0x%08x\n", stolen_focus);
/* Set the focus to i3lock, possibly closing context menus which would
* otherwise prevent us from grabbing keyboard/pointer.
*
* We cannot use set_focused_window because _NET_ACTIVE_WINDOW only
* works for managed windows, but i3lock uses an unmanaged window
* (override_redirect=1). */
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT /* revert_to */, win, XCB_CURRENT_TIME);
if (!grab_pointer_and_keyboard(conn, screen, cursor, 9000)) {
auth_state = STATE_I3LOCK_LOCK_FAILED;
redraw_screen();
sleep(1);
errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");
}
}
pid_t pid = fork();
/* The pid == -1 case is intentionally ignored here:
@ -1065,4 +1086,17 @@ int main(int argc, char *argv[]) {
* file descriptor becomes readable). */
ev_invoke(main_loop, xcb_check, 0);
ev_loop(main_loop, 0);
if (stolen_focus == XCB_NONE) {
return 0;
}
DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus);
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
xcb_destroy_window(conn, win);
set_focused_window(conn, screen->root, stolen_focus);
xcb_aux_sync(conn);
return 0;
}

81
xcb.c
View File

@ -163,10 +163,13 @@ xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, c
}
/*
* Repeatedly tries to grab pointer and keyboard (up to 10000 times).
* Repeatedly tries to grab pointer and keyboard (up to the specified number of
* tries).
*
* Returns true if the grab succeeded, false if not.
*
*/
void grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor) {
bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) {
xcb_grab_pointer_cookie_t pcookie;
xcb_grab_pointer_reply_t *preply;
@ -174,7 +177,6 @@ void grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb
xcb_grab_keyboard_reply_t *kreply;
const suseconds_t screen_redraw_timeout = 100000; /* 100ms */
int tries = 10000;
/* Using few variables to trigger a redraw_screen() if too many tries */
bool redrawn = false;
@ -255,14 +257,7 @@ void grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb
}
}
/* After trying for 10000 times, i3lock will display an error message
* for 2 sec prior to terminate. */
if (tries <= 0) {
auth_state = STATE_I3LOCK_LOCK_FAILED;
redraw_screen();
sleep(1);
errx(EXIT_FAILURE, "Cannot grab pointer/keyboard");
}
return (tries > 0);
}
xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) {
@ -327,3 +322,67 @@ xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_win
return cursor;
}
static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE;
void _init_net_active_window(xcb_connection_t *conn) {
if (_NET_ACTIVE_WINDOW != XCB_NONE) {
/* already initialized */
return;
}
xcb_generic_error_t *err;
xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply(
conn,
xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"),
&err);
if (atom_reply == NULL) {
fprintf(stderr, "X11 Error %d\n", err->error_code);
free(err);
return;
}
_NET_ACTIVE_WINDOW = atom_reply->atom;
free(atom_reply);
}
xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) {
xcb_window_t result = XCB_NONE;
_init_net_active_window(conn);
xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(
conn,
xcb_get_property_unchecked(
conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */),
NULL);
if (prop_reply == NULL) {
goto out;
}
if (xcb_get_property_value_length(prop_reply) == 0) {
goto out_prop;
}
if (prop_reply->type != XCB_ATOM_WINDOW) {
goto out_prop;
}
result = *((xcb_window_t *)xcb_get_property_value(prop_reply));
out_prop:
free(prop_reply);
out:
return result;
}
void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) {
xcb_client_message_event_t ev;
memset(&ev, '\0', sizeof(xcb_client_message_event_t));
_init_net_active_window(conn);
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window;
ev.type = _NET_ACTIVE_WINDOW;
ev.format = 32;
ev.data.data32[0] = 2; /* 2 = pager */
xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
xcb_flush(conn);
}

4
xcb.h
View File

@ -9,7 +9,9 @@ extern xcb_screen_t *screen;
xcb_visualtype_t *get_root_visual_type(xcb_screen_t *s);
xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color);
xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap);
void grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor);
bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries);
xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice);
xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root);
void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window);
#endif