+
+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.
+ *
+ */
+bool load_keymap(void) {
+ if (xkb_context == NULL) {
+ if ((xkb_context = xkb_context_new(0)) == NULL) {
+ ELOG("Could not create xkbcommon context\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;
+
+ return true;
+}
+
+/*
+ * Returns true if the current config has any binding to a scroll wheel button
+ * (4 or 5) which is a whole-window binding.
+ * We need this to figure out whether we should grab all buttons or just 1-3
+ * when managing a window. See #2049.
+ *
+ */
+bool bindings_should_grab_scrollwheel_buttons(void) {
+ Binding *bind;
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* We are only interested in whole window mouse bindings. */
+ if (bind->input_type != B_MOUSE || !bind->whole_window)
+ continue;
+
+ char *endptr;
+ long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
+ if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) {
+ ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n");
+ continue;
+ }
+
+ /* If the binding is for either scrollwheel button, we need to grab everything. */
+ if (button == 4 || button == 5) {
+ return true;
+ }
+ }
+
+ return false;
+}