$OpenBSD: patch-src_bindings_c,v 1.4 2015/12/18 15:53:09 dcoppa Exp $

Bugfix: add keymap fall back (_XKB_RULES_NAMES, then defaults)

Use sasprintf()

Fix memleak in translate_keysyms

Bugfix: correctly compare modifier mask when identifying keybindings

Bugfix: set group mask 1 by default, correctly compare modifiers

--- src/bindings.c.orig	Wed Sep 30 08:55:10 2015
+++ src/bindings.c	Fri Dec 18 13:58:58 2015
@@ -164,16 +164,34 @@ static Binding *get_binding(i3_event_state_mask_t stat
         }
     }
 
+    const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000);
+    const uint32_t modifiers_state = (state_filtered & 0x0000FFFF);
     TAILQ_FOREACH(bind, bindings, bindings) {
-        DLOG("binding with event_state_mask 0x%x, state_filtered 0x%x, match: %s\n",
-             bind->event_state_mask, state_filtered,
-             ((state_filtered & bind->event_state_mask) == bind->event_state_mask) ? "yes" : "no");
+        const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000);
+        /* modifiers_mask is a special case: a value of 0 does not mean "match all",
+         * but rather "match exactly when no modifiers are present". */
+        const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF);
+        const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask);
+        bool mods_match;
+        if (modifiers_mask == 0) {
+            /* Verify no modifiers are pressed. A bitwise AND would lead to
+             * false positives, see issue #2002. */
+            mods_match = (modifiers_state == 0);
+        } else {
+            mods_match = ((modifiers_state & modifiers_mask) == modifiers_mask);
+        }
+        const bool state_matches = (groups_match && mods_match);
+
+        DLOG("binding groups_match = %s, mods_match = %s, state_matches = %s\n",
+             (groups_match ? "yes" : "no"),
+             (mods_match ? "yes" : "no"),
+             (state_matches ? "yes" : "no"));
         /* First compare the state_filtered (unless this is a
          * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
          * event) */
         if (bind->input_type != input_type)
             continue;
-        if ((state_filtered & bind->event_state_mask) != bind->event_state_mask &&
+        if (!state_matches &&
             (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
              !is_release))
             continue;
@@ -396,6 +414,7 @@ void translate_keysyms(void) {
     }
 
     xkb_state_unref(dummy_state);
+    xkb_state_unref(dummy_state_no_shift);
 }
 
 /*
@@ -603,7 +622,7 @@ CommandResult *run_binding(Binding *bind, Con *con) {
 
     if (result->parse_error) {
         char *pageraction;
-        sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
+        sasprintf(&pageraction, "/usr/bin/less \"%s\"\n", errorfilename);
         char *argv[] = {
             NULL, /* will be replaced by the executable path */
             "-f",
@@ -626,6 +645,77 @@ CommandResult *run_binding(Binding *bind, Con *con) {
     return result;
 }
 
+static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) {
+    xcb_intern_atom_reply_t *atom_reply;
+    size_t content_max_words = 256;
+
+    xcb_window_t root = root_screen->root;
+
+    atom_reply = xcb_intern_atom_reply(
+        conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL);
+    if (atom_reply == NULL)
+        return -1;
+
+    xcb_get_property_cookie_t prop_cookie;
+    xcb_get_property_reply_t *prop_reply;
+    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                             XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
+    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+    if (prop_reply == NULL) {
+        free(atom_reply);
+        return -1;
+    }
+    if (xcb_get_property_value_length(prop_reply) > 0 && prop_reply->bytes_after > 0) {
+        /* We received an incomplete value. Ask again but with a properly
+         * adjusted size. */
+        content_max_words += ceil(prop_reply->bytes_after / 4.0);
+        /* Repeat the request, with adjusted size */
+        free(prop_reply);
+        prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
+                                                 XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words);
+        prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
+        if (prop_reply == NULL) {
+            free(atom_reply);
+            return -1;
+        }
+    }
+    if (xcb_get_property_value_length(prop_reply) == 0) {
+        free(atom_reply);
+        free(prop_reply);
+        return -1;
+    }
+
+    const char *walk = (const char *)xcb_get_property_value(prop_reply);
+    int remaining = xcb_get_property_value_length(prop_reply);
+    for (int i = 0; i < 5 && remaining > 0; i++) {
+        const int len = strnlen(walk, remaining);
+        remaining -= len;
+        switch (i) {
+            case 0:
+                sasprintf((char **)&(xkb_names->rules), "%.*s", len, walk);
+                break;
+            case 1:
+                sasprintf((char **)&(xkb_names->model), "%.*s", len, walk);
+                break;
+            case 2:
+                sasprintf((char **)&(xkb_names->layout), "%.*s", len, walk);
+                break;
+            case 3:
+                sasprintf((char **)&(xkb_names->variant), "%.*s", len, walk);
+                break;
+            case 4:
+                sasprintf((char **)&(xkb_names->options), "%.*s", len, walk);
+                break;
+        }
+        DLOG("component %d of _XKB_RULES_NAMES is \"%.*s\"\n", i, len, walk);
+        walk += (len + 1);
+    }
+
+    free(atom_reply);
+    free(prop_reply);
+    return 0;
+}
+
 /*
  * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
  *
@@ -638,12 +728,40 @@ bool load_keymap(void) {
         }
     }
 
-    struct xkb_keymap *new_keymap;
-    const int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
-    DLOG("device_id = %d\n", device_id);
-    if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
-        ELOG("xkb_x11_keymap_new_from_device failed\n");
-        return false;
+    struct xkb_keymap *new_keymap = NULL;
+    int32_t device_id;
+    if (xkb_supported && (device_id = xkb_x11_get_core_keyboard_device_id(conn)) > -1) {
+        if ((new_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
+            ELOG("xkb_x11_keymap_new_from_device failed\n");
+            return false;
+        }
+    } else {
+        /* Likely there is no XKB support on this server, possibly because it
+         * is a VNC server. */
+        LOG("No XKB / core keyboard device? Assembling keymap from local RMLVO.\n");
+        struct xkb_rule_names names = {
+            .rules = NULL,
+            .model = NULL,
+            .layout = NULL,
+            .variant = NULL,
+            .options = NULL};
+        if (fill_rmlvo_from_root(&names) == -1) {
+            ELOG("Could not get _XKB_RULES_NAMES atom from root window, falling back to defaults.\n");
+            if ((new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0)) == NULL) {
+                ELOG("xkb_keymap_new_from_names(NULL) failed\n");
+                return false;
+            }
+        }
+        new_keymap = xkb_keymap_new_from_names(xkb_context, &names, 0);
+        free((char *)names.rules);
+        free((char *)names.model);
+        free((char *)names.layout);
+        free((char *)names.variant);
+        free((char *)names.options);
+        if (new_keymap == NULL) {
+            ELOG("xkb_keymap_new_from_names(RMLVO) failed\n");
+            return false;
+        }
     }
     xkb_keymap_unref(xkb_keymap);
     xkb_keymap = new_keymap;
