]> git.sur5r.net Git - i3/i3/blobdiff - src/handlers.c
Merge pull request #1636 from Deiz/update-notices
[i3/i3] / src / handlers.c
index 312372a7f08bea0af817eb92d203b19efa130cea..532ab18966f3fc25b66a8ccc4a94b4e68fd069aa 100644 (file)
@@ -4,7 +4,7 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * handlers.c: Small handlers for various events (keypresses, focus changes,
  *             …).
@@ -21,6 +21,8 @@
 #include <libsn/sn-monitor.h>
 
 int randr_base = -1;
+int xkb_base = -1;
+int xkb_current_group;
 
 /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
    since it’d trigger an infinite loop of switching between the different windows when
@@ -58,7 +60,8 @@ bool event_is_ignored(const int sequence, const int response_type) {
             event = SLIST_NEXT(event, ignore_events);
             SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events);
             free(save);
-        } else event = SLIST_NEXT(event, ignore_events);
+        } else
+            event = SLIST_NEXT(event, ignore_events);
     }
 
     SLIST_FOREACH(event, &ignore_events, ignore_events) {
@@ -103,7 +106,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
         return;
     }
 
-    /* Focus the output on which the user moved his cursor */
+    /* Focus the output on which the user moved their cursor */
     Con *old_focused = focused;
     Con *next = con_descend_focused(output_get_content(output->con));
     /* Since we are switching outputs, this *must* be a different workspace, so
@@ -146,7 +149,7 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
         enter_child = true;
     }
 
-    /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
+    /* If not, then the user moved their cursor to the root window. In that case, we adjust c_ws */
     if (con == NULL) {
         DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
         check_crossing_screen_boundary(event->root_x, event->root_y);
@@ -163,11 +166,11 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
     if (layout == L_DEFAULT) {
         Con *child;
         TAILQ_FOREACH(child, &(con->nodes_head), nodes)
-            if (rect_contains(child->deco_rect, event->event_x, event->event_y)) {
-                LOG("using child %p / %s instead!\n", child, child->name);
-                con = child;
-                break;
-            }
+        if (rect_contains(child->deco_rect, event->event_x, event->event_y)) {
+            LOG("using child %p / %s instead!\n", child, child->name);
+            con = child;
+            break;
+        }
     }
 
 #if 0
@@ -183,6 +186,10 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
     if (config.disable_focus_follows_mouse)
         return;
 
+    /* if this container is already focused, there is nothing to do. */
+    if (con == focused)
+        return;
+
     /* Get the currently focused workspace to check if the focus change also
      * involves changing workspaces. If so, we need to call workspace_show() to
      * correctly update state and send the IPC event. */
@@ -204,7 +211,6 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) {
  *
  */
 static void handle_motion_notify(xcb_motion_notify_event_t *event) {
-
     last_timestamp = event->time;
 
     /* Skip events where the pointer was over a child window, we are only
@@ -278,7 +284,6 @@ static void handle_map_request(xcb_map_request_event_t *event) {
     add_ignore_event(event->sequence, -1);
 
     manage_window(event->window, cookie, false);
-    x_push_changes(croot);
     return;
 }
 
@@ -294,7 +299,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
     Con *con;
 
     DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
-        event->window, event->x, event->y, event->width, event->height);
+         event->window, event->x, event->y, event->width, event->height);
 
     /* For unmanaged windows, we just execute the configure request. As soon as
      * it gets mapped, we will take over anyways. */
@@ -304,12 +309,13 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
         uint32_t mask = 0;
         uint32_t values[7];
         int c = 0;
-#define COPY_MASK_MEMBER(mask_member, event_member) do { \
-        if (event->value_mask & mask_member) { \
-            mask |= mask_member; \
-            values[c++] = event->event_member; \
-        } \
-} while (0)
+#define COPY_MASK_MEMBER(mask_member, event_member) \
+    do {                                            \
+        if (event->value_mask & mask_member) {      \
+            mask |= mask_member;                    \
+            values[c++] = event->event_member;      \
+        }                                           \
+    } while (0)
 
         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
@@ -482,7 +488,6 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
 
     tree_close(con, DONT_KILL_WINDOW, false, false);
     tree_render();
-    x_push_changes(croot);
 
 ignore_end:
     /* If the client (as opposed to i3) destroyed or unmapped a window, an
@@ -528,20 +533,38 @@ static void handle_destroy_notify_event(xcb_destroy_notify_event_t *event) {
     handle_unmap_notify_event(&unmap);
 }
 
+static bool window_name_changed(i3Window *window, char *old_name) {
+    if ((old_name == NULL) && (window->name == NULL))
+        return false;
+
+    /* Either the old or the new one is NULL, but not both. */
+    if ((old_name == NULL) ^ (window->name == NULL))
+        return true;
+
+    return (strcmp(old_name, i3string_as_utf8(window->name)) != 0);
+}
+
 /*
  * Called when a window changes its title
  *
  */
 static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
-                                xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+                                     xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
     Con *con;
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return false;
 
+    char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
+
     window_update_name(con->window, prop, false);
 
     x_push_changes(croot);
 
+    if (window_name_changed(con->window, old_name))
+        ipc_send_window_event("title", con);
+
+    FREE(old_name);
+
     return true;
 }
 
@@ -551,15 +574,22 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
  *
  */
 static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
-                                xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
+                                            xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
     Con *con;
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return false;
 
+    char *old_name = (con->window->name != NULL ? sstrdup(i3string_as_utf8(con->window->name)) : NULL);
+
     window_update_name_legacy(con->window, prop, false);
 
     x_push_changes(croot);
 
+    if (window_name_changed(con->window, old_name))
+        ipc_send_window_event("title", con);
+
+    FREE(old_name);
+
     return true;
 }
 
@@ -621,6 +651,19 @@ static void handle_expose_event(xcb_expose_event_t *event) {
     return;
 }
 
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
+#define _NET_WM_MOVERESIZE_MOVE 8           /* movement only */
+#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9  /* size via keyboard */
+#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
+#define _NET_WM_MOVERESIZE_CANCEL 11        /* cancel operation */
+
 /*
  * Handle client messages (EWMH)
  *
@@ -628,7 +671,7 @@ static void handle_expose_event(xcb_expose_event_t *event) {
 static void handle_client_message(xcb_client_message_event_t *event) {
     /* If this is a startup notification ClientMessage, the library will handle
      * it and call our monitor_event() callback. */
-    if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t*)event))
+    if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t *)event))
         return;
 
     LOG("ClientMessage for window 0x%08x\n", event->window);
@@ -669,7 +712,11 @@ static void handle_client_message(xcb_client_message_event_t *event) {
 
         tree_render();
     } else if (event->type == A__NET_ACTIVE_WINDOW) {
+        if (event->format != 32)
+            return;
+
         DLOG("_NET_ACTIVE_WINDOW: Window 0x%08x should be activated\n", event->window);
+
         Con *con = con_by_window_id(event->window);
         if (con == NULL) {
             DLOG("Could not get window for client message\n");
@@ -677,8 +724,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         }
 
         Con *ws = con_get_workspace(con);
-        if (!workspace_is_visible(ws)) {
-            DLOG("Workspace not visible, ignoring _NET_ACTIVE_WINDOW\n");
+
+        if (ws == NULL) {
+            DLOG("Window is not being managed, ignoring _NET_ACTIVE_WINDOW\n");
             return;
         }
 
@@ -687,10 +735,27 @@ static void handle_client_message(xcb_client_message_event_t *event) {
             return;
         }
 
-        if (ws != con_get_workspace(focused))
+        /* data32[0] indicates the source of the request (application or pager) */
+        if (event->data.data32[0] == 2) {
+            /* Always focus the con if it is from a pager, because this is most
+             * likely from some user action */
+            DLOG("This request came from a pager. Focusing con = %p\n", con);
             workspace_show(ws);
+            con_focus(con);
+        } else {
+            /* Request is from an application. */
+
+            if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+                DLOG("Focusing con = %p\n", con);
+                workspace_show(ws);
+                con_focus(con);
+            } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+                DLOG("Marking con = %p urgent\n", con);
+                con_set_urgency(con, true);
+            } else
+                DLOG("Ignoring request for con = %p", con);
+        }
 
-        con_focus(con);
         tree_render();
     } else if (event->type == A_I3_SYNC) {
         xcb_window_t window = event->data.data32[0];
@@ -707,7 +772,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         ev->data.data32[0] = window;
         ev->data.data32[1] = rnd;
 
-        xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev);
+        xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev);
         xcb_flush(conn);
         free(reply);
     } else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) {
@@ -729,17 +794,98 @@ static void handle_client_message(xcb_client_message_event_t *event) {
         Rect r = {
             config.default_border_width, /* left */
             config.default_border_width, /* right */
-            config.font.height + 5, /* top */
-            config.default_border_width /* bottom */
+            config.font.height + 5,      /* top */
+            config.default_border_width  /* bottom */
         };
         xcb_change_property(
-                conn,
-                XCB_PROP_MODE_REPLACE,
-                event->window,
-                A__NET_FRAME_EXTENTS,
-                XCB_ATOM_CARDINAL, 32, 4,
-                &r);
+            conn,
+            XCB_PROP_MODE_REPLACE,
+            event->window,
+            A__NET_FRAME_EXTENTS,
+            XCB_ATOM_CARDINAL, 32, 4,
+            &r);
         xcb_flush(conn);
+    } else if (event->type == A__NET_CURRENT_DESKTOP) {
+        /* This request is used by pagers and bars to change the current
+         * desktop likely as a result of some user action. We interpret this as
+         * a request to focus the given workspace. See
+         * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008
+         * */
+        Con *output;
+        uint32_t idx = 0;
+        DLOG("Request to change current desktop to index %d\n", event->data.data32[0]);
+
+        TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+            Con *ws;
+            TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+                if (STARTS_WITH(ws->name, "__"))
+                    continue;
+
+                if (idx == event->data.data32[0]) {
+                    /* data32[1] is a timestamp used to prevent focus race conditions */
+                    if (event->data.data32[1])
+                        last_timestamp = event->data.data32[1];
+
+                    DLOG("Handling request to focus workspace %s\n", ws->name);
+
+                    workspace_show(ws);
+                    tree_render();
+
+                    return;
+                }
+
+                ++idx;
+            }
+        }
+    } else if (event->type == A__NET_CLOSE_WINDOW) {
+        /*
+         * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW
+         * client message request to the root window.
+         * http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472668896
+         */
+        Con *con = con_by_window_id(event->window);
+        if (con) {
+            DLOG("Handling _NET_CLOSE_WINDOW request (con = %p)\n", con);
+
+            if (event->data.data32[0])
+                last_timestamp = event->data.data32[0];
+
+            tree_close(con, KILL_WINDOW, false, false);
+            tree_render();
+        } else {
+            DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
+        }
+    } else if (event->type == A__NET_WM_MOVERESIZE) {
+        /*
+         * Client-side decorated Gtk3 windows emit this signal when being
+         * dragged by their GtkHeaderBar
+         */
+        Con *con = con_by_window_id(event->window);
+        if (!con || !con_is_floating(con)) {
+            DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
+            return;
+        }
+        DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
+        uint32_t direction = event->data.data32[2];
+        uint32_t x_root = event->data.data32[0];
+        uint32_t y_root = event->data.data32[1];
+        /* construct fake xcb_button_press_event_t */
+        xcb_button_press_event_t fake = {
+            .root_x = x_root,
+            .root_y = y_root,
+            .event_x = x_root - (con->rect.x),
+            .event_y = y_root - (con->rect.y)};
+        switch (direction) {
+            case _NET_WM_MOVERESIZE_MOVE:
+                floating_drag_window(con->parent, &fake);
+                break;
+            case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT:
+                floating_resize_window(con->parent, false, &fake);
+                break;
+            default:
+                DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
+                break;
+        }
     } else {
         DLOG("unhandled clientmessage\n");
         return;
@@ -764,7 +910,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
  *
  */
 static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                        xcb_atom_t name, xcb_get_property_reply_t *reply) {
+                                xcb_atom_t name, xcb_get_property_reply_t *reply) {
     Con *con = con_by_window_id(window);
     if (con == NULL) {
         DLOG("Received WM_NORMAL_HINTS for unknown client\n");
@@ -773,7 +919,7 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
 
     xcb_size_hints_t size_hints;
 
-        //CLIENT_LOG(client);
+    //CLIENT_LOG(client);
 
     /* If the hints were already in this event, use them, if not, request them */
     if (reply != NULL)
@@ -853,7 +999,8 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         aspect_ratio = min_aspect;
     } else if ((width / height) > max_aspect) {
         aspect_ratio = max_aspect;
-    } else goto render_and_return;
+    } else
+        goto render_and_return;
 
     if (fabs(con->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
         con->aspect_ratio = aspect_ratio;
@@ -872,7 +1019,7 @@ render_and_return:
  *
  */
 static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                  xcb_atom_t name, xcb_get_property_reply_t *reply) {
+                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
     Con *con = con_by_window_id(window);
     if (con == NULL) {
         DLOG("Received WM_HINTS for unknown client\n");
@@ -897,7 +1044,7 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
  *
  */
 static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                         xcb_atom_t name, xcb_get_property_reply_t *prop) {
+                                 xcb_atom_t name, xcb_get_property_reply_t *prop) {
     Con *con;
 
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
@@ -907,7 +1054,8 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
 
     if (prop == NULL) {
         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32), NULL);
+                                                                       false, window, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 32),
+                                      NULL);
         if (prop == NULL)
             return false;
     }
@@ -923,14 +1071,15 @@ static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t sta
  *
  */
 static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
-                        xcb_atom_t name, xcb_get_property_reply_t *prop) {
+                                       xcb_atom_t name, xcb_get_property_reply_t *prop) {
     Con *con;
     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
         return false;
 
     if (prop == NULL) {
         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
-                                false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32), NULL);
+                                                                       false, window, A_WM_CLIENT_LEADER, XCB_ATOM_WINDOW, 0, 32),
+                                      NULL);
         if (prop == NULL)
             return false;
     }
@@ -991,6 +1140,111 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
     return;
 }
 
+/*
+ * Handles the WM_CLASS property for assignments and criteria selection.
+ *
+ */
+static bool handle_class_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                                xcb_atom_t name, xcb_get_property_reply_t *prop) {
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
+        return false;
+
+    if (prop == NULL) {
+        prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
+                                                                       false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32),
+                                      NULL);
+
+        if (prop == NULL)
+            return false;
+    }
+
+    window_update_class(con->window, prop, false);
+
+    return true;
+}
+
+/*
+ * Handles the _NET_WM_STRUT_PARTIAL property for allocating space for dock clients.
+ *
+ */
+static bool handle_strut_partial_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
+                                        xcb_atom_t name, xcb_get_property_reply_t *prop) {
+    DLOG("strut partial change for window 0x%08x\n", window);
+
+    Con *con;
+    if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
+        return false;
+    }
+
+    if (prop == NULL) {
+        xcb_generic_error_t *err = NULL;
+        xcb_get_property_cookie_t strut_cookie = xcb_get_property(conn, false, window, A__NET_WM_STRUT_PARTIAL,
+                                                                  XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX);
+        prop = xcb_get_property_reply(conn, strut_cookie, &err);
+
+        if (err != NULL) {
+            DLOG("got error when getting strut partial property: %d\n", err->error_code);
+            free(err);
+            return false;
+        }
+
+        if (prop == NULL) {
+            return false;
+        }
+    }
+
+    DLOG("That is con %p / %s\n", con, con->name);
+
+    window_update_strut_partial(con->window, prop);
+
+    /* we only handle this change for dock clients */
+    if (con->parent == NULL || con->parent->type != CT_DOCKAREA) {
+        return true;
+    }
+
+    Con *search_at = croot;
+    Con *output = con_get_output(con);
+    if (output != NULL) {
+        DLOG("Starting search at output %s\n", output->name);
+        search_at = output;
+    }
+
+    /* find out the desired position of this dock window */
+    if (con->window->reserved.top > 0 && con->window->reserved.bottom == 0) {
+        DLOG("Top dock client\n");
+        con->window->dock = W_DOCK_TOP;
+    } else if (con->window->reserved.top == 0 && con->window->reserved.bottom > 0) {
+        DLOG("Bottom dock client\n");
+        con->window->dock = W_DOCK_BOTTOM;
+    } else {
+        DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n");
+        if (con->geometry.y < (search_at->rect.height / 2)) {
+            DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n",
+                 con->geometry.y, (search_at->rect.height / 2));
+            con->window->dock = W_DOCK_TOP;
+        } else {
+            DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n",
+                 con->geometry.y, (search_at->rect.height / 2));
+            con->window->dock = W_DOCK_BOTTOM;
+        }
+    }
+
+    /* find the dockarea */
+    Con *dockarea = con_for_window(search_at, con->window, NULL);
+    assert(dockarea != NULL);
+
+    /* attach the dock to the dock area */
+    con_detach(con);
+    con->parent = dockarea;
+    TAILQ_INSERT_HEAD(&(dockarea->focus_head), con, focused);
+    TAILQ_INSERT_HEAD(&(dockarea->nodes_head), con, nodes);
+
+    tree_render();
+
+    return true;
+}
+
 /* Returns false if the event could not be processed (e.g. the window could not
  * be found), true otherwise */
 typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
@@ -1002,14 +1256,15 @@ struct property_handler_t {
 };
 
 static struct property_handler_t property_handlers[] = {
-    { 0, 128, handle_windowname_change },
-    { 0, UINT_MAX, handle_hints },
-    { 0, 128, handle_windowname_change_legacy },
-    { 0, UINT_MAX, handle_normal_hints },
-    { 0, UINT_MAX, handle_clientleader_change },
-    { 0, UINT_MAX, handle_transient_for },
-    { 0, 128, handle_windowrole_change }
-};
+    {0, 128, handle_windowname_change},
+    {0, UINT_MAX, handle_hints},
+    {0, 128, handle_windowname_change_legacy},
+    {0, UINT_MAX, handle_normal_hints},
+    {0, UINT_MAX, handle_clientleader_change},
+    {0, UINT_MAX, handle_transient_for},
+    {0, 128, handle_windowrole_change},
+    {0, 128, handle_class_change},
+    {0, UINT_MAX, handle_strut_partial_change}};
 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
 
 /*
@@ -1018,7 +1273,6 @@ static struct property_handler_t property_handlers[] = {
  *
  */
 void property_handlers_init(void) {
-
     sn_monitor_context_new(sndisplay, conn_screen, startup_monitor_event, NULL, NULL);
 
     property_handlers[0].atom = A__NET_WM_NAME;
@@ -1028,6 +1282,8 @@ void property_handlers_init(void) {
     property_handlers[4].atom = A_WM_CLIENT_LEADER;
     property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR;
     property_handlers[6].atom = A_WM_WINDOW_ROLE;
+    property_handlers[7].atom = XCB_ATOM_WM_CLASS;
+    property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
 }
 
 static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@@ -1063,70 +1319,118 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom)
  *
  */
 void handle_event(int type, xcb_generic_event_t *event) {
+    DLOG("event type %d, xkb_base %d\n", type, xkb_base);
     if (randr_base > -1 &&
         type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
         handle_screen_change(event);
         return;
     }
 
+    if (xkb_base > -1 && type == xkb_base) {
+        DLOG("xkb event, need to handle it.\n");
+
+        xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event;
+        if (state->xkbType == XCB_XKB_NEW_KEYBOARD_NOTIFY) {
+            DLOG("xkb new keyboard notify, sequence %d, time %d\n", state->sequence, state->time);
+            xcb_key_symbols_free(keysyms);
+            keysyms = xcb_key_symbols_alloc(conn);
+            ungrab_all_keys(conn);
+            translate_keysyms();
+            grab_all_keys(conn, false);
+        } else if (state->xkbType == XCB_XKB_MAP_NOTIFY) {
+            if (event_is_ignored(event->sequence, type)) {
+                DLOG("Ignoring map notify event for sequence %d.\n", state->sequence);
+            } else {
+                DLOG("xkb map notify, sequence %d, time %d\n", state->sequence, state->time);
+                add_ignore_event(event->sequence, type);
+                xcb_key_symbols_free(keysyms);
+                keysyms = xcb_key_symbols_alloc(conn);
+                ungrab_all_keys(conn);
+                translate_keysyms();
+                grab_all_keys(conn, false);
+            }
+        } else if (state->xkbType == XCB_XKB_STATE_NOTIFY) {
+            DLOG("xkb state group = %d\n", state->group);
+
+            /* See The XKB Extension: Library Specification, section 14.1 */
+            /* We check if the current group (each group contains
+             * two levels) has been changed. Mode_switch activates
+             * group XCB_XKB_GROUP_2 */
+            if (xkb_current_group == state->group)
+                return;
+            xkb_current_group = state->group;
+            if (state->group == XCB_XKB_GROUP_1) {
+                DLOG("Mode_switch disabled\n");
+                ungrab_all_keys(conn);
+                grab_all_keys(conn, false);
+            } else {
+                DLOG("Mode_switch enabled\n");
+                grab_all_keys(conn, true);
+            }
+        }
+
+        return;
+    }
+
     switch (type) {
         case XCB_KEY_PRESS:
         case XCB_KEY_RELEASE:
-            handle_key_press((xcb_key_press_event_t*)event);
+            handle_key_press((xcb_key_press_event_t *)event);
             break;
 
         case XCB_BUTTON_PRESS:
-            handle_button_press((xcb_button_press_event_t*)event);
+        case XCB_BUTTON_RELEASE:
+            handle_button_press((xcb_button_press_event_t *)event);
             break;
 
         case XCB_MAP_REQUEST:
-            handle_map_request((xcb_map_request_event_t*)event);
+            handle_map_request((xcb_map_request_event_t *)event);
             break;
 
         case XCB_UNMAP_NOTIFY:
-            handle_unmap_notify_event((xcb_unmap_notify_event_t*)event);
+            handle_unmap_notify_event((xcb_unmap_notify_event_t *)event);
             break;
 
         case XCB_DESTROY_NOTIFY:
-            handle_destroy_notify_event((xcb_destroy_notify_event_t*)event);
+            handle_destroy_notify_event((xcb_destroy_notify_event_t *)event);
             break;
 
         case XCB_EXPOSE:
-            handle_expose_event((xcb_expose_event_t*)event);
+            handle_expose_event((xcb_expose_event_t *)event);
             break;
 
         case XCB_MOTION_NOTIFY:
-            handle_motion_notify((xcb_motion_notify_event_t*)event);
+            handle_motion_notify((xcb_motion_notify_event_t *)event);
             break;
 
-        /* Enter window = user moved his mouse over the window */
+        /* Enter window = user moved their mouse over the window */
         case XCB_ENTER_NOTIFY:
-            handle_enter_notify((xcb_enter_notify_event_t*)event);
+            handle_enter_notify((xcb_enter_notify_event_t *)event);
             break;
 
         /* Client message are sent to the root window. The only interesting
          * client message for us is _NET_WM_STATE, we honour
          * _NET_WM_STATE_FULLSCREEN and _NET_WM_STATE_DEMANDS_ATTENTION */
         case XCB_CLIENT_MESSAGE:
-            handle_client_message((xcb_client_message_event_t*)event);
+            handle_client_message((xcb_client_message_event_t *)event);
             break;
 
         /* Configure request = window tried to change size on its own */
         case XCB_CONFIGURE_REQUEST:
-            handle_configure_request((xcb_configure_request_event_t*)event);
+            handle_configure_request((xcb_configure_request_event_t *)event);
             break;
 
         /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
         case XCB_MAPPING_NOTIFY:
-            handle_mapping_notify((xcb_mapping_notify_event_t*)event);
+            handle_mapping_notify((xcb_mapping_notify_event_t *)event);
             break;
 
         case XCB_FOCUS_IN:
-            handle_focus_in((xcb_focus_in_event_t*)event);
+            handle_focus_in((xcb_focus_in_event_t *)event);
             break;
 
         case XCB_PROPERTY_NOTIFY: {
-            xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
+            xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event;
             last_timestamp = e->time;
             property_notify(e->state, e->window, e->atom);
             break;