]> git.sur5r.net Git - i3/i3lock/commitdiff
SetInputFocus to the i3lock window to force-close context menus (#155)
authorMichael Stapelberg <stapelberg@users.noreply.github.com>
Sun, 22 Oct 2017 20:16:34 +0000 (22:16 +0200)
committerGitHub <noreply@github.com>
Sun, 22 Oct 2017 20:16:34 +0000 (22:16 +0200)
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

i3lock.c
xcb.c
xcb.h

index 853862566778165189c54d40041c4393d1f26f15..1bb7b0c8b40c91f1a25e4b3fa4520d323bd08a12 100644 (file)
--- a/i3lock.c
+++ b/i3lock.c
@@ -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;
 }
diff --git a/xcb.c b/xcb.c
index c38440414766b4f8b6b9d19166329ffae2bc0dc5..27a8daedb64a60771bc54319082234c684456f33 100644 (file)
--- a/xcb.c
+++ b/xcb.c
@@ -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);
+}
diff --git a/xcb.h b/xcb.h
index ffef42f95db9af2730c7beb0c9e24c04e366f4b4..3555c58610b715c3d21a4b8b474093145ecd6570 100644 (file)
--- a/xcb.h
+++ b/xcb.h
@@ -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