Files
zdwm/src/xkb.c

217 lines
6.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "xkb.h"
#include <assert.h>
#include <glib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xkb.h>
#include <xcb/xproto.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xkbcommon/xkbcommon.h>
#include "atoms-extern.h"
#include "default_config.h"
#include "utils.h"
#include "wm.h"
#include "xwindow.h"
static bool xkb_fill_rule_names_from_root(struct xkb_rule_names *xkb_names) {
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(
wm.xcb_conn, false, wm.screen->root, _XKB_RULES_NAMES,
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
xcb_get_property_reply_t *reply =
xcb_get_property_reply(wm.xcb_conn, cookie, nullptr);
if (!reply) return false;
if (reply->value_len == 0) {
p_delete(&reply);
return false;
}
const char *walk = xcb_get_property_value(reply);
size_t remaining = xcb_get_property_value_length(reply);
for (int i = 0; i < 5 && remaining > 0; i++) {
size_t len = strnlen(walk, remaining);
switch (i) {
case 0:
xkb_names->rules = strndup(walk, len);
break;
case 1:
xkb_names->model = strndup(walk, len);
break;
case 2:
xkb_names->layout = strndup(walk, len);
break;
case 3:
xkb_names->variant = strndup(walk, len);
break;
case 4:
xkb_names->options = strndup(walk, len);
break;
}
remaining -= len + 1;
walk = &walk[len + 1];
}
p_delete(&reply);
return true;
}
static void xkb_fill_state(void) {
int32_t device_id = -1;
if (wm.have_xkb) {
device_id = xkb_x11_get_core_keyboard_device_id(wm.xcb_conn);
if (device_id == -1) warn("Failed get XKB device id");
}
if (device_id == -1) {
struct xkb_rule_names names = {nullptr, nullptr, nullptr, nullptr, nullptr};
if (!xkb_fill_rule_names_from_root(&names)) {
warn(
"Could not get _XKB_RULES_NAMES from root window, falling back to "
"defaults.");
}
struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(
wm.xkb_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
wm.xkb_state = xkb_state_new(xkb_keymap);
if (!wm.xkb_state) fatal("Failed create XKB state");
xkb_keymap_unref(xkb_keymap);
p_delete(&names.rules);
p_delete(&names.model);
p_delete(&names.layout);
p_delete(&names.variant);
p_delete(&names.options);
} else {
struct xkb_keymap *xkb_keymap = xkb_x11_keymap_new_from_device(
wm.xkb_ctx, wm.xcb_conn, device_id, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!xkb_keymap) fatal("Failed get XKB keymap from device");
wm.xkb_state =
xkb_x11_state_new_from_device(xkb_keymap, wm.xcb_conn, device_id);
if (!wm.xkb_state) fatal("Failed get XKB state from device");
xkb_keymap_unref(xkb_keymap);
}
}
static void xkb_init_keymap(void) {
wm.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!wm.xkb_ctx) fatal("Cannot get XKB context");
xkb_fill_state();
}
#define DEVICE_SPEC XCB_XKB_ID_USE_CORE_KBD
void xkb_init(void) {
wm.xkb_update_pending = false;
wm.xkb_reload_keymap = false;
wm.have_xkb = xkb_x11_setup_xkb_extension(
wm.xcb_conn, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nullptr, nullptr, &wm.event_base_xkb,
nullptr);
logger("xkb base event: %u\n", wm.event_base_xkb);
if (!wm.have_xkb) {
warn("XKB not found or not supported");
xkb_init_keymap();
return;
}
xcb_xkb_per_client_flags_cookie_t cookie = xcb_xkb_per_client_flags(
wm.xcb_conn, DEVICE_SPEC, XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, 0, 0, 0);
xcb_discard_reply(wm.xcb_conn, cookie.sequence);
uint16_t xkb_event = XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY;
uint16_t map_parts =
XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS |
XCB_XKB_MAP_PART_MODIFIER_MAP | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
XCB_XKB_MAP_PART_KEY_ACTIONS | XCB_XKB_MAP_PART_KEY_BEHAVIORS |
XCB_XKB_MAP_PART_VIRTUAL_MODS | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
xcb_xkb_select_events(wm.xcb_conn, DEVICE_SPEC, xkb_event, 0, xkb_event,
map_parts, map_parts, nullptr);
xkb_init_keymap();
}
void xkb_free(void) {
if (wm.have_xkb) {
xcb_xkb_select_events(wm.xcb_conn, DEVICE_SPEC, 0, 0, 0, 0, 0, nullptr);
}
xkb_state_unref(wm.xkb_state);
xkb_context_unref(wm.xkb_ctx);
wm.xkb_state = nullptr;
wm.xkb_ctx = nullptr;
}
static void xkb_reload_keymap(void) {
assert(wm.have_xkb);
xcb_key_symbols_free(wm.key_symbols);
wm.key_symbols = xcb_key_symbols_alloc(wm.xcb_conn);
xwindow_grab_keys(wm.screen->root, key_list, countof(key_list));
xkb_state_unref(wm.xkb_state);
xkb_fill_state();
}
static gboolean xkb_refresh(gpointer ignore) {
wm.xkb_update_pending = false;
if (wm.xkb_reload_keymap) xkb_reload_keymap();
wm.xkb_reload_keymap = false;
return G_SOURCE_REMOVE;
}
static void xkb_schedule_refresh(void) {
if (wm.xkb_update_pending) return;
wm.xkb_update_pending = true;
g_idle_add_full(G_PRIORITY_LOW, xkb_refresh, nullptr, nullptr);
}
void xkb_handle_event(xcb_generic_event_t *event) {
assert(wm.have_xkb);
logger("xkb event type: %u\n", event->pad0);
/* XKB 中没有对应所有事件的泛型类型xcb_generic_event_t 类型中的 pad0
* 字段对应 XKB 事件中的 xkbType 字段,故用该字段判断 XKB 事件类型 */
switch (event->pad0) {
case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
wm.xkb_reload_keymap = true;
xkb_schedule_refresh();
break;
}
case XCB_XKB_MAP_NOTIFY: {
wm.xkb_reload_keymap = true;
xkb_schedule_refresh();
break;
}
case XCB_XKB_STATE_NOTIFY: {
xcb_xkb_state_notify_event_t *state_notify_event = (void *)event;
xkb_state_update_mask(
wm.xkb_state, state_notify_event->baseMods,
state_notify_event->latchedMods, state_notify_event->lockedMods,
state_notify_event->baseGroup, state_notify_event->latchedGroup,
state_notify_event->lockedGroup);
logger("changed: %u\n",
state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE);
if (state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE) {
xkb_schedule_refresh();
}
break;
}
}
}