初始化 xkb 扩展

This commit is contained in:
2025-11-22 22:59:15 +08:00
parent e5927094d1
commit 2df0b58cb0
13 changed files with 365 additions and 18 deletions

View File

@@ -18,3 +18,6 @@ indent_size = 4
[Makefile]
indent_style = tab
[atoms-list.txt]
insert_final_newline = true

View File

@@ -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)

View File

@@ -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))

17
build-utils/atoms-extern.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
echo "/* This file is autogenerated by $0 - do not edit */"
echo
echo "#include <xcb/xproto.h>"
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"

39
build-utils/atoms-intern.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/sh
echo "/* This file is autogenerated by $0 - do not edit */"
echo
echo "#include <xcb/xproto.h>"
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 "};"

1
src/atoms-list.txt Normal file
View File

@@ -0,0 +1 @@
_XKB_RULES_NAMES

23
src/atoms.c Normal file
View File

@@ -0,0 +1,23 @@
#include "atoms.h"
#include <xcb/xproto.h>
#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);
}
}
}

5
src/atoms.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <xcb/xcb.h>
void atoms_init(xcb_connection_t *conn);

View File

@@ -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,

View File

@@ -17,7 +17,7 @@
#include <xcb/xinerama.h>
#include <xcb/xproto.h>
#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) {
&params);
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);

View File

@@ -2,6 +2,7 @@
#include <stdint.h>
#include <xcb/xcb_keysyms.h>
#include <xkbcommon/xkbcommon.h>
#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;

207
src/xkb.c Normal file
View File

@@ -0,0 +1,207 @@
#include "xkb.h"
#include <assert.h>
#include <glib.h>
#include <stddef.h>
#include <stdint.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);
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;
}
}
}

7
src/xkb.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <xcb/xcb.h>
void xkb_init(void);
void xkb_free(void);
void xkb_handle_event(xcb_generic_event_t *event);