From 2df0b58cb0526c2c71e86e94fe2ec2605605d05c Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Sat, 22 Nov 2025 22:59:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20xkb=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 3 + src/CMakeLists.txt => CMakeLists.txt | 54 +++++-- Makefile | 2 +- build-utils/atoms-extern.sh | 17 +++ build-utils/atoms-intern.sh | 39 +++++ src/atoms-list.txt | 1 + src/atoms.c | 23 +++ src/atoms.h | 5 + src/event.c | 8 +- src/wm.c | 9 +- src/wm.h | 8 ++ src/xkb.c | 207 +++++++++++++++++++++++++++ src/xkb.h | 7 + 13 files changed, 365 insertions(+), 18 deletions(-) rename src/CMakeLists.txt => CMakeLists.txt (54%) create mode 100755 build-utils/atoms-extern.sh create mode 100755 build-utils/atoms-intern.sh create mode 100644 src/atoms-list.txt create mode 100644 src/atoms.c create mode 100644 src/atoms.h create mode 100644 src/xkb.c create mode 100644 src/xkb.h diff --git a/.editorconfig b/.editorconfig index baf0da6..d637f26 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,3 +18,6 @@ indent_size = 4 [Makefile] indent_style = tab + +[atoms-list.txt] +insert_final_newline = true \ No newline at end of file diff --git a/src/CMakeLists.txt b/CMakeLists.txt similarity index 54% rename from src/CMakeLists.txt rename to CMakeLists.txt index 4e8cf7e..b4f7e83 100644 --- a/src/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.24) set(APP_NAME "zdwm") -set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") add_compile_definitions(LOG_FILE="$ENV{HOME}/${APP_NAME}-log.txt") @@ -20,18 +20,20 @@ project(${APP_NAME} ) add_executable(${APP_NAME} - wm.c - utils.c - buffer.c - backtrace.c - monitor.c - client.c - text.c - color.c - xcursor.c - xwindow.c - event.c - action.c + ${SOURCE_DIR}/wm.c + ${SOURCE_DIR}/utils.c + ${SOURCE_DIR}/buffer.c + ${SOURCE_DIR}/backtrace.c + ${SOURCE_DIR}/monitor.c + ${SOURCE_DIR}/client.c + ${SOURCE_DIR}/text.c + ${SOURCE_DIR}/color.c + ${SOURCE_DIR}/xcursor.c + ${SOURCE_DIR}/xwindow.c + ${SOURCE_DIR}/event.c + ${SOURCE_DIR}/action.c + ${SOURCE_DIR}/xkb.c + ${SOURCE_DIR}/atoms.c ) # use C23 @@ -50,6 +52,8 @@ pkg_check_modules(deps REQUIRED xcb-randr xcb-xfixes xcb-xinerama + xcb-xkb + xkbcommon-x11 cairo cairo-xcb @@ -92,3 +96,27 @@ configure_file("${SOURCE_DIR}/app.h.in" ESCAPE_QUOTES @ONLY ) + +set(ATOM_LIST_FILE "${SOURCE_DIR}/atoms-list.txt") +add_custom_command( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build-utils/atoms-extern.sh + ARGS ${ATOM_LIST_FILE} > ${BUILD_DIR}/atoms-extern.h + OUTPUT ${BUILD_DIR}/atoms-extern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${ATOM_LIST_FILE} + COMMENT "Generating atoms-extern.h" + VERBATIM +) +add_custom_command( + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build-utils/atoms-intern.sh + ARGS ${ATOM_LIST_FILE} > ${BUILD_DIR}/atoms-intern.h + OUTPUT ${BUILD_DIR}/atoms-intern.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${ATOM_LIST_FILE} + COMMENT "Generating atoms-intern.h" + VERBATIM +) +add_custom_target(generate_atoms + DEPENDS ${BUILD_DIR}/atoms-extern.h ${BUILD_DIR}/atoms-intern.h +) +add_dependencies(${APP_NAME} generate_atoms) diff --git a/Makefile b/Makefile index 3278586..cb0a5e6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ MAKEFILE_ABS_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) MAKEFILE_DIR := $(dir $(MAKEFILE_ABS_PATH)) PWD_DIR := $(CURDIR)/ BUILD_DIR := $(addprefix $(MAKEFILE_DIR),build) -SRC_DIR := $(addprefix $(MAKEFILE_DIR),src/) +SRC_DIR := $(addprefix $(MAKEFILE_DIR),/) TARGET_NAME := zdwm TARGET := $(addprefix $(MAKEFILE_DIR),$(TARGET_NAME)) diff --git a/build-utils/atoms-extern.sh b/build-utils/atoms-extern.sh new file mode 100755 index 0000000..afcdc3c --- /dev/null +++ b/build-utils/atoms-extern.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +echo "/* This file is autogenerated by $0 - do not edit */" +echo +echo "#include " +echo + +while read atom +do + # 去掉首位空格 + atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//') + + # 跳过空行 + if [[ -n "$atom_clean" ]]; then + echo "extern xcb_atom_t ${atom_clean};" + fi +done < "$1" diff --git a/build-utils/atoms-intern.sh b/build-utils/atoms-intern.sh new file mode 100755 index 0000000..062d92b --- /dev/null +++ b/build-utils/atoms-intern.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +echo "/* This file is autogenerated by $0 - do not edit */" +echo +echo "#include " +echo + +while read atom +do + # 去掉首位空格 + atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//') + + # 跳过空行 + if [[ -n "$atom_clean" ]]; then + echo "xcb_atom_t ${atom_clean};" + fi +done < "$1" + +echo +echo "typedef struct atom_item_t {" +echo " const char *name;" +echo " size_t len;" +echo " xcb_atom_t *atom;" +echo "} atom_item_t;" +echo +echo "static atom_item_t ATOM_LIST[] = {" + +while read atom +do + # 去掉首位空格 + atom_clean=$(echo "$atom" | sed 's/^[[:space:]]*//;s/[[:space:]]$//') + + # 跳过空行 + if [[ -n "$atom_clean" ]]; then + echo " {\"${atom_clean}\", sizeof(\"${atom_clean}\") - 1, &${atom_clean}}," + fi +done < "$1" + +echo "};" diff --git a/src/atoms-list.txt b/src/atoms-list.txt new file mode 100644 index 0000000..b4aa89b --- /dev/null +++ b/src/atoms-list.txt @@ -0,0 +1 @@ +_XKB_RULES_NAMES diff --git a/src/atoms.c b/src/atoms.c new file mode 100644 index 0000000..9456363 --- /dev/null +++ b/src/atoms.c @@ -0,0 +1,23 @@ +#include "atoms.h" + +#include + +#include "atoms-intern.h" +#include "utils.h" + +void atoms_init(xcb_connection_t *conn) { + xcb_intern_atom_cookie_t cookies[countof(ATOM_LIST)]; + for (int i = 0; i < countof(ATOM_LIST); i++) { + atom_item_t atom = ATOM_LIST[i]; + cookies[i] = xcb_intern_atom_unchecked(conn, false, atom.len, atom.name); + } + + xcb_intern_atom_reply_t *reply = nullptr; + for (int i = 0; i < countof(ATOM_LIST); i++) { + reply = xcb_intern_atom_reply(conn, cookies[i], nullptr); + if (reply) { + *ATOM_LIST[i].atom = reply->atom; + p_delete(&reply); + } + } +} diff --git a/src/atoms.h b/src/atoms.h new file mode 100644 index 0000000..cd92758 --- /dev/null +++ b/src/atoms.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void atoms_init(xcb_connection_t *conn); diff --git a/src/event.c b/src/event.c index 9b4c6a8..091602e 100644 --- a/src/event.c +++ b/src/event.c @@ -12,6 +12,7 @@ #include "types.h" #include "utils.h" #include "wm.h" +#include "xkb.h" static void button_press(xcb_button_press_event_t *ev) { click_area_t click_area = click_none; @@ -64,13 +65,18 @@ static void handle_xcb_event(xcb_generic_event_t *event) { #define EVENT(type, callback) \ case type: \ callback((void *)event); \ - break + return EVENT(XCB_BUTTON_PRESS, button_press); EVENT(XCB_KEY_PRESS, key_press); #undef EVENT } + + /* 处理 XKB 事件 */ + if (wm.event_base_xkb != 0 && event_type == wm.event_base_xkb) { + xkb_handle_event(event); + } } static gboolean xcb_event_loop(GIOChannel *channel, GIOCondition condition, diff --git a/src/wm.c b/src/wm.c index 3fce2d9..1f9d13d 100644 --- a/src/wm.c +++ b/src/wm.c @@ -17,7 +17,7 @@ #include #include -#include "action.h" +#include "atoms.h" #include "backtrace.h" #include "buffer.h" #include "color.h" @@ -28,6 +28,7 @@ #include "types.h" #include "utils.h" #include "xcursor.h" +#include "xkb.h" #include "xwindow.h" static void wm_setup_signal(void); @@ -268,6 +269,7 @@ void wm_clean(void) { } xcursor_clean(); + xkb_free(); xcb_ungrab_key(wm.xcb_conn, XCB_GRAB_ANY, wm.screen->root, modifier_any); xcb_key_symbols_free(wm.key_symbols); xcb_disconnect(wm.xcb_conn); @@ -299,6 +301,8 @@ static void wm_setup(void) { ¶ms); wm_setup_keybindings(); + atoms_init(wm.xcb_conn); + xkb_init(); xcb_flush(wm.xcb_conn); } @@ -358,12 +362,11 @@ int main(int argc, char *argv[]) { wm_check_xcb_extensions(); wm_detect_monitor(); + setup_event_loop(); wm_setup(); debug_show_monitor_list(); - setup_event_loop(); - if (wm.loop == nullptr) { wm.loop = g_main_loop_new(nullptr, FALSE); g_main_loop_run(wm.loop); diff --git a/src/wm.h b/src/wm.h index 784448a..0fb22ad 100644 --- a/src/wm.h +++ b/src/wm.h @@ -2,6 +2,7 @@ #include #include +#include #include "types.h" @@ -25,6 +26,13 @@ typedef struct wm_t { bool have_xfixes; bool have_xinerama; bool have_randr; + bool have_xkb; + + uint8_t event_base_xkb; + bool xkb_reload_keymap; + bool xkb_update_pending; + struct xkb_context *xkb_ctx; + struct xkb_state *xkb_state; color_set_t color_set; } wm_t; diff --git a/src/xkb.c b/src/xkb.c new file mode 100644 index 0000000..5cd7a2f --- /dev/null +++ b/src/xkb.c @@ -0,0 +1,207 @@ +#include "xkb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); + printf("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); + + /* 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); + break; + } + } +} diff --git a/src/xkb.h b/src/xkb.h new file mode 100644 index 0000000..0ff32f0 --- /dev/null +++ b/src/xkb.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void xkb_init(void); +void xkb_free(void); +void xkb_handle_event(xcb_generic_event_t *event);