feat: auto convert keysym to keycode bind

This commit is contained in:
DreamMaoMao 2025-10-22 09:17:52 +08:00
parent 776738cc28
commit e9565f88f6
4 changed files with 232 additions and 41 deletions

View file

@ -20,9 +20,15 @@ enum { NUM_TYPE_MINUS, NUM_TYPE_PLUS, NUM_TYPE_DEFAULT };
enum { KEY_TYPE_SYM, KEY_TYPE_CODE }; enum { KEY_TYPE_SYM, KEY_TYPE_CODE };
typedef struct {
uint32_t keycode1;
uint32_t keycode2;
uint32_t keycode3;
} MultiKeycode;
typedef struct { typedef struct {
xkb_keysym_t keysym; xkb_keysym_t keysym;
uint32_t keycode; MultiKeycode keycode;
int type; int type;
} KeySymCode; } KeySymCode;
@ -325,6 +331,8 @@ typedef struct {
char keymode[28]; char keymode[28];
struct xkb_context *ctx;
struct xkb_keymap *keymap;
} Config; } Config;
typedef int (*FuncType)(const Arg *); typedef int (*FuncType)(const Arg *);
@ -613,23 +621,120 @@ uint32_t parse_mod(const char *mod_str) {
return mod; return mod;
} }
KeySymCode parse_key(const char *key_str) { // 定义辅助函数:在 keymap 中查找 keysym 对应的多个 keycode
KeySymCode kc; static int find_keycodes_for_keysym(struct xkb_keymap *keymap, xkb_keysym_t sym,
MultiKeycode *multi_kc) {
xkb_keycode_t min_keycode = xkb_keymap_min_keycode(keymap);
xkb_keycode_t max_keycode = xkb_keymap_max_keycode(keymap);
multi_kc->keycode1 = 0;
multi_kc->keycode2 = 0;
multi_kc->keycode3 = 0;
int found_count = 0;
for (xkb_keycode_t keycode = min_keycode;
keycode <= max_keycode && found_count < 3; keycode++) {
// 使用布局0和层级0
const xkb_keysym_t *syms;
int num_syms =
xkb_keymap_key_get_syms_by_level(keymap, keycode, 0, 0, &syms);
for (int i = 0; i < num_syms; i++) {
if (syms[i] == sym) {
switch (found_count) {
case 0:
multi_kc->keycode1 = keycode;
break;
case 1:
multi_kc->keycode2 = keycode;
break;
case 2:
multi_kc->keycode3 = keycode;
break;
}
found_count++;
break;
}
}
}
return found_count;
}
void cleanup_config_keymap(void) {
if (config.keymap != NULL) {
xkb_keymap_unref(config.keymap);
config.keymap = NULL;
}
if (config.ctx != NULL) {
xkb_context_unref(config.ctx);
config.ctx = NULL;
}
}
void create_config_keymap(void) {
// 初始化 xkb 上下文和 keymap
if (config.ctx == NULL) {
config.ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
}
if (config.keymap == NULL) {
config.keymap = xkb_keymap_new_from_names(
config.ctx, &xkb_fallback_rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
}
}
KeySymCode parse_key(const char *key_str, bool isbindsym) {
KeySymCode kc = {0}; // 初始化为0
if (config.keymap == NULL || config.ctx == NULL) {
// 处理错误
kc.type = KEY_TYPE_SYM;
kc.keysym = XKB_KEY_NoSymbol;
return kc;
}
// 处理 code: 前缀的情况 // 处理 code: 前缀的情况
if (strncmp(key_str, "code:", 5) == 0) { if (strncmp(key_str, "code:", 5) == 0) {
char *endptr; char *endptr;
xkb_keycode_t keycode = (xkb_keycode_t)strtol(key_str + 5, &endptr, 10); xkb_keycode_t keycode = (xkb_keycode_t)strtol(key_str + 5, &endptr, 10);
kc.type = KEY_TYPE_CODE; kc.type = KEY_TYPE_CODE;
kc.keycode = keycode; kc.keycode.keycode1 = keycode; // 只设置第一个
kc.keycode.keycode2 = 0;
kc.keycode.keycode3 = 0;
return kc; return kc;
} }
// 普通键名直接转换 // 普通键名直接转换
xkb_keysym_t sym = xkb_keysym_from_name(key_str, XKB_KEYSYM_NO_FLAGS); xkb_keysym_t sym = xkb_keysym_from_name(key_str, XKB_KEYSYM_NO_FLAGS);
kc.type = KEY_TYPE_SYM;
kc.keycode = 0; if (isbindsym) {
kc.keysym = sym; kc.type = KEY_TYPE_SYM;
kc.keysym = sym;
return kc;
}
if (sym != XKB_KEY_NoSymbol) {
// 尝试找到对应的多个 keycode
int found_count =
find_keycodes_for_keysym(config.keymap, sym, &kc.keycode);
if (found_count > 0) {
kc.type = KEY_TYPE_CODE;
kc.keysym = sym; // 仍然保存 keysym 供参考
} else {
kc.type = KEY_TYPE_SYM;
kc.keysym = sym;
// keycode 字段保持为0
}
} else {
// 无法解析的键名
kc.type = KEY_TYPE_SYM;
kc.keysym = XKB_KEY_NoSymbol;
// keycode 字段保持为0
}
return kc; return kc;
} }
@ -1650,7 +1755,8 @@ void parse_option(Config *config, char *key, char *value) {
trim_whitespace(mod_str); trim_whitespace(mod_str);
trim_whitespace(keysym_str); trim_whitespace(keysym_str);
rule->globalkeybinding.mod = parse_mod(mod_str); rule->globalkeybinding.mod = parse_mod(mod_str);
rule->globalkeybinding.keysymcode = parse_key(keysym_str); rule->globalkeybinding.keysymcode =
parse_key(keysym_str, false);
} }
} }
token = strtok(NULL, ","); token = strtok(NULL, ",");
@ -1787,7 +1893,8 @@ void parse_option(Config *config, char *key, char *value) {
config->exec_once_count++; config->exec_once_count++;
} else if (strncmp(key, "bind", 4) == 0) { } else if (strncmp(key, "bind", 4) == 0 ||
strncmp(key, "bindsym", 7) == 0) {
config->key_bindings = config->key_bindings =
realloc(config->key_bindings, realloc(config->key_bindings,
(config->key_bindings_count + 1) * sizeof(KeyBinding)); (config->key_bindings_count + 1) * sizeof(KeyBinding));
@ -1834,7 +1941,8 @@ void parse_option(Config *config, char *key, char *value) {
} }
binding->mod = parse_mod(mod_str); binding->mod = parse_mod(mod_str);
binding->keysymcode = parse_key(keysym_str); binding->keysymcode =
parse_key(keysym_str, strncmp(key, "bindsym", 7) == 0);
binding->arg.v = NULL; binding->arg.v = NULL;
binding->arg.v2 = NULL; binding->arg.v2 = NULL;
binding->arg.v3 = NULL; binding->arg.v3 = NULL;
@ -2432,6 +2540,9 @@ void free_config(void) {
// 释放动画资源 // 释放动画资源
free_baked_points(); free_baked_points();
// 清理解析按键用的keymap
cleanup_config_keymap();
} }
void override_config(void) { void override_config(void) {
@ -2815,6 +2926,8 @@ void parse_config(void) {
config.cursor_theme = NULL; config.cursor_theme = NULL;
strcpy(config.keymode, "default"); strcpy(config.keymode, "default");
create_config_keymap();
// 获取 MANGOCONFIG 环境变量 // 获取 MANGOCONFIG 环境变量
const char *mangoconfig = getenv("MANGOCONFIG"); const char *mangoconfig = getenv("MANGOCONFIG");

View file

@ -114,6 +114,15 @@ char xkb_rules_layout[256];
char xkb_rules_variant[256]; char xkb_rules_variant[256];
char xkb_rules_options[256]; char xkb_rules_options[256];
/* keyboard */
static const struct xkb_rule_names xkb_fallback_rules = {
.layout = "us",
.variant = NULL,
.model = NULL,
.rules = NULL,
.options = NULL,
};
struct xkb_rule_names xkb_rules = { struct xkb_rule_names xkb_rules = {
/* can specify fields: rules, model, layout, variant, options */ /* can specify fields: rules, model, layout, variant, options */
/* example: /* example:

View file

@ -861,6 +861,13 @@ int switch_keyboard_layout(const Arg *arg) {
// 4. 直接修改 rules.layout保持原有逻辑 // 4. 直接修改 rules.layout保持原有逻辑
struct xkb_rule_names rules = xkb_rules; struct xkb_rule_names rules = xkb_rules;
// 验证规则是否有效
if (!check_keyboard_rules_validate(&rules)) {
wlr_log(WLR_ERROR,
"Keyboard rules validation failed, skipping layout reset");
rules = xkb_fallback_rules;
}
char *layout_buf = (char *)rules.layout; // 假设这是可修改的 char *layout_buf = (char *)rules.layout; // 假设这是可修改的
// 清空原有内容(安全方式) // 清空原有内容(安全方式)

View file

@ -690,6 +690,7 @@ static void resize_tile_client(Client *grabc, bool isdrag, int offsetx,
int offsety, unsigned int time); int offsety, unsigned int time);
static void refresh_monitors_workspaces_status(Monitor *m); static void refresh_monitors_workspaces_status(Monitor *m);
static void init_client_properties(Client *c); static void init_client_properties(Client *c);
static bool check_keyboard_rules_validate(struct xkb_rule_names *rules);
#include "data/static_keymap.h" #include "data/static_keymap.h"
#include "dispatch/bind_declare.h" #include "dispatch/bind_declare.h"
@ -2365,9 +2366,18 @@ KeyboardGroup *createkeyboardgroup(void) {
group->wlr_group = wlr_keyboard_group_create(); group->wlr_group = wlr_keyboard_group_create();
group->wlr_group->data = group; group->wlr_group->data = group;
// 4. 直接修改 rules.layout保持原有逻辑
struct xkb_rule_names rules = xkb_rules;
// 验证规则是否有效
if (!check_keyboard_rules_validate(&rules)) {
wlr_log(WLR_ERROR,
"Keyboard rules validation failed, skipping layout reset");
rules = xkb_fallback_rules;
}
/* Prepare an XKB keymap and assign it to the keyboard group. */ /* Prepare an XKB keymap and assign it to the keyboard group. */
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, if (!(keymap = xkb_keymap_new_from_names(context, &rules,
XKB_KEYMAP_COMPILE_NO_FLAGS))) XKB_KEYMAP_COMPILE_NO_FLAGS)))
die("failed to compile keymap"); die("failed to compile keymap");
@ -3226,7 +3236,9 @@ keybinding(unsigned int mods, xkb_keysym_t sym, unsigned int keycode) {
normalize_keysym(sym) == normalize_keysym(sym) ==
normalize_keysym(k->keysymcode.keysym)) || normalize_keysym(k->keysymcode.keysym)) ||
(k->keysymcode.type == KEY_TYPE_CODE && (k->keysymcode.type == KEY_TYPE_CODE &&
keycode == k->keysymcode.keycode)) && (keycode == k->keysymcode.keycode.keycode1 ||
keycode == k->keysymcode.keycode.keycode2 ||
keycode == k->keysymcode.keycode.keycode3))) &&
k->func) { k->func) {
isbreak = k->func(&k->arg); isbreak = k->func(&k->arg);
@ -3258,14 +3270,18 @@ bool keypressglobal(struct wlr_surface *last_surface,
if (!r->globalkeybinding.mod || if (!r->globalkeybinding.mod ||
(!r->globalkeybinding.keysymcode.keysym && (!r->globalkeybinding.keysymcode.keysym &&
!r->globalkeybinding.keysymcode.keycode)) !r->globalkeybinding.keysymcode.keycode.keycode1 &&
!r->globalkeybinding.keysymcode.keycode.keycode2 &&
!r->globalkeybinding.keysymcode.keycode.keycode3))
continue; continue;
/* match key only (case insensitive) ignoring mods */ /* match key only (case insensitive) ignoring mods */
if (((r->globalkeybinding.keysymcode.type == KEY_TYPE_SYM && if (((r->globalkeybinding.keysymcode.type == KEY_TYPE_SYM &&
r->globalkeybinding.keysymcode.keysym == keysym) || r->globalkeybinding.keysymcode.keysym == keysym) ||
(r->globalkeybinding.keysymcode.type == KEY_TYPE_CODE && (r->globalkeybinding.keysymcode.type == KEY_TYPE_CODE &&
r->globalkeybinding.keysymcode.keycode == keycode)) && (r->globalkeybinding.keysymcode.keycode.keycode1 == keycode ||
r->globalkeybinding.keysymcode.keycode.keycode2 == keycode ||
r->globalkeybinding.keysymcode.keycode.keycode3 == keycode))) &&
r->globalkeybinding.mod == mods) { r->globalkeybinding.mod == mods) {
wl_list_for_each(c, &clients, link) { wl_list_for_each(c, &clients, link) {
if (c && c != lastc) { if (c && c != lastc) {
@ -4526,6 +4542,46 @@ void setgaps(int oh, int ov, int ih, int iv) {
arrange(selmon, false); arrange(selmon, false);
} }
// 验证键盘规则是否有效
bool check_keyboard_rules_validate(struct xkb_rule_names *rules) {
if (!rules) {
wlr_log(WLR_ERROR, "Keyboard rules are NULL");
return false;
}
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context for validation");
return false;
}
bool valid = false;
struct xkb_keymap *test_keymap =
xkb_keymap_new_from_names(context, rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (test_keymap) {
// 检查keymap是否至少有一个布局
if (xkb_keymap_num_layouts(test_keymap) > 0) {
valid = true;
} else {
wlr_log(WLR_ERROR, "Keymap has no layouts");
}
xkb_keymap_unref(test_keymap);
} else {
wlr_log(WLR_ERROR,
"Invalid keyboard rules: rules=%s, model=%s, layout=%s, "
"variant=%s, options=%s",
rules->rules ? rules->rules : "NULL",
rules->model ? rules->model : "NULL",
rules->layout ? rules->layout : "NULL",
rules->variant ? rules->variant : "NULL",
rules->options ? rules->options : "NULL");
}
xkb_context_unref(context);
return valid;
}
void reset_keyboard_layout(void) { void reset_keyboard_layout(void) {
if (!kb_group || !kb_group->wlr_group || !seat) { if (!kb_group || !kb_group->wlr_group || !seat) {
wlr_log(WLR_ERROR, "Invalid keyboard group or seat"); wlr_log(WLR_ERROR, "Invalid keyboard group or seat");
@ -4547,6 +4603,16 @@ void reset_keyboard_layout(void) {
return; return;
} }
// Keep the same rules but just reapply them
struct xkb_rule_names rules = xkb_rules;
// 验证规则是否有效
if (!check_keyboard_rules_validate(&rules)) {
wlr_log(WLR_ERROR,
"Keyboard rules validation failed, skipping layout reset");
rules = xkb_fallback_rules;
}
// Create context // Create context
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) { if (!context) {
@ -4554,35 +4620,34 @@ void reset_keyboard_layout(void) {
return; return;
} }
// Get layout abbreviations // 现在安全地创建真正的keymap
const char **layout_ids = calloc(num_layouts, sizeof(char *));
if (!layout_ids) {
wlr_log(WLR_ERROR, "Failed to allocate layout IDs");
goto cleanup_context;
}
for (int i = 0; i < num_layouts; i++) {
layout_ids[i] =
get_layout_abbr(xkb_keymap_layout_get_name(keyboard->keymap, i));
if (!layout_ids[i]) {
wlr_log(WLR_ERROR, "Failed to get layout abbreviation");
goto cleanup_layouts;
}
}
// Keep the same rules but just reapply them
struct xkb_rule_names rules = xkb_rules;
// Create new keymap with current rules
struct xkb_keymap *new_keymap = struct xkb_keymap *new_keymap =
xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!new_keymap) { if (!new_keymap) {
wlr_log(WLR_ERROR, "Failed to create keymap for layouts: %s", // 理论上这里不应该失败,因为前面已经验证过了
rules.layout); wlr_log(WLR_ERROR,
goto cleanup_layouts; "Unexpected failure to create keymap after validation");
goto cleanup_context;
} }
// Apply the same keymap (this will reset the layout state) // 验证新keymap是否有布局
const int new_num_layouts = xkb_keymap_num_layouts(new_keymap);
if (new_num_layouts < 1) {
wlr_log(WLR_ERROR, "New keymap has no layouts");
xkb_keymap_unref(new_keymap);
goto cleanup_context;
}
// 确保当前布局索引在新keymap中有效
if (current >= new_num_layouts) {
wlr_log(WLR_INFO,
"Current layout index %u out of range for new keymap, "
"resetting to 0",
current);
current = 0;
}
// Apply the new keymap
unsigned int depressed = keyboard->modifiers.depressed; unsigned int depressed = keyboard->modifiers.depressed;
unsigned int latched = keyboard->modifiers.latched; unsigned int latched = keyboard->modifiers.latched;
unsigned int locked = keyboard->modifiers.locked; unsigned int locked = keyboard->modifiers.locked;
@ -4599,9 +4664,6 @@ void reset_keyboard_layout(void) {
// Cleanup // Cleanup
xkb_keymap_unref(new_keymap); xkb_keymap_unref(new_keymap);
cleanup_layouts:
free(layout_ids);
cleanup_context: cleanup_context:
xkb_context_unref(context); xkb_context_unref(context);
} }