From 7b36f7990d35ef529bed68be78d2e71893f6d327 Mon Sep 17 00:00:00 2001 From: Reza Jelveh Date: Sun, 5 Jul 2026 09:42:30 +0800 Subject: [PATCH] feat: add per keyboard layouts --- assets/config.conf | 4 ++ meson.build | 1 + src/config/parse_config.h | 106 +++++++++++++++++++++++++++++++++++++ src/dispatch/bind_define.h | 2 + src/mango.c | 31 ++++++++++- 5 files changed, 142 insertions(+), 2 deletions(-) diff --git a/assets/config.conf b/assets/config.conf index 6c1f94b7..ddb1c43f 100644 --- a/assets/config.conf +++ b/assets/config.conf @@ -83,6 +83,10 @@ repeat_delay=600 numlockon=0 xkb_rules_layout=us +# per-device keyboard rules: override xkb layout for specific keyboards +# keyboardrule=name:ErgoDox EZ,xkb_layout:us,xkb_variant:colemak +# keyboardrule=name:Keychron,xkb_rules:evdev,xkb_layout:us,xkb_variant:dvorak + # Trackpad # need relogin to make it apply disable_trackpad=0 diff --git a/meson.build b/meson.build index 4bf3f176..8d0a78cb 100644 --- a/meson.build +++ b/meson.build @@ -68,6 +68,7 @@ c_args = [ '-g', '-Wno-unused-function', '-DWLR_USE_UNSTABLE', + '-D_GNU_SOURCE', '-D_POSIX_C_SOURCE=200809L', '-DVERSION="@0@"'.format(version_with_hash), '-DSYSCONFDIR="@0@"'.format('/etc'), diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 733990bd..c53af809 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -180,6 +180,15 @@ typedef struct { int32_t noanim; } ConfigLayerRule; +typedef struct { + char *name; + char *xkb_rules; + char *xkb_model; + char *xkb_layout; + char *xkb_variant; + char *xkb_options; +} ConfigKeyboardRule; + typedef struct { int32_t animations; int32_t layer_animations; @@ -334,6 +343,9 @@ typedef struct { ConfigLayerRule *layer_rules; // 动态数组 int32_t layer_rules_count; // 数量 + ConfigKeyboardRule *keyboard_rules; + int32_t keyboard_rules_count; + ConfigWinRule *window_rules; int32_t window_rules_count; @@ -400,6 +412,18 @@ bool apply_rule_to_state(Monitor *m, const ConfigMonitorRule *rule, struct wlr_output_state *state, int vrr, int custom); bool monitor_matches_rule(Monitor *m, const ConfigMonitorRule *rule); +// Find matching keyboard rule by device name (substring match, case-insensitive) +static inline ConfigKeyboardRule *find_keyboard_rule(Config *cfg, const char *device_name) { + if (!device_name || !cfg->keyboard_rules) + return NULL; + for (int32_t i = 0; i < cfg->keyboard_rules_count; i++) { + if (cfg->keyboard_rules[i].name && + strcasestr(device_name, cfg->keyboard_rules[i].name)) + return &cfg->keyboard_rules[i]; + } + return NULL; +} + // Helper function to trim whitespace from start and end of a string void trim_whitespace(char *str) { if (str == NULL || *str == '\0') @@ -2240,6 +2264,67 @@ bool parse_option(Config *config, char *key, char *value) { config->layer_rules_count++; return !parse_error; + } else if (strcmp(key, "keyboardrule") == 0) { + config->keyboard_rules = + realloc(config->keyboard_rules, + (config->keyboard_rules_count + 1) * sizeof(ConfigKeyboardRule)); + if (!config->keyboard_rules) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Failed to allocate " + "memory for keyboard rules\n"); + return false; + } + + ConfigKeyboardRule *rule = &config->keyboard_rules[config->keyboard_rules_count]; + memset(rule, 0, sizeof(ConfigKeyboardRule)); + + char *saveptr; + char *param = strtok_r(value, ",", &saveptr); + bool parse_error = false; + while (param != NULL) { + trim_whitespace(param); + char *eq = strchr(param, ':'); + if (eq != NULL) { + *eq = '\0'; + char *sub_key = param; + char *sub_value = eq + 1; + trim_whitespace(sub_key); + trim_whitespace(sub_value); + if (strcmp(sub_key, "name") == 0) { + rule->name = strdup(sub_value); + } else if (strcmp(sub_key, "xkb_rules") == 0) { + rule->xkb_rules = strdup(sub_value); + } else if (strcmp(sub_key, "xkb_model") == 0) { + rule->xkb_model = strdup(sub_value); + } else if (strcmp(sub_key, "xkb_layout") == 0) { + rule->xkb_layout = strdup(sub_value); + } else if (strcmp(sub_key, "xkb_variant") == 0) { + rule->xkb_variant = strdup(sub_value); + } else if (strcmp(sub_key, "xkb_options") == 0) { + rule->xkb_options = strdup(sub_value); + } else { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Unknown keyboardrule " + "parameter: %s\n", sub_key); + } + *eq = ':'; + } else { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m Malformed keyboardrule " + "parameter: %s\n", param); + parse_error = true; + } + param = strtok_r(NULL, ",", &saveptr); + } + + if (rule->name == NULL) { + fprintf(stderr, + "\033[1m\033[31m[ERROR]:\033[33m keyboardrule requires name\n"); + parse_error = true; + } + + config->keyboard_rules_count++; + return !parse_error; } else if (strcmp(key, "windowrule") == 0) { config->window_rules = realloc(config->window_rules, @@ -3261,6 +3346,27 @@ void free_config(void) { config.layer_rules_count = 0; } + // keyboard_rules + if (config.keyboard_rules) { + for (int32_t i = 0; i < config.keyboard_rules_count; i++) { + if (config.keyboard_rules[i].name) + free((void *)config.keyboard_rules[i].name); + if (config.keyboard_rules[i].xkb_rules) + free((void *)config.keyboard_rules[i].xkb_rules); + if (config.keyboard_rules[i].xkb_model) + free((void *)config.keyboard_rules[i].xkb_model); + if (config.keyboard_rules[i].xkb_layout) + free((void *)config.keyboard_rules[i].xkb_layout); + if (config.keyboard_rules[i].xkb_variant) + free((void *)config.keyboard_rules[i].xkb_variant); + if (config.keyboard_rules[i].xkb_options) + free((void *)config.keyboard_rules[i].xkb_options); + } + free(config.keyboard_rules); + config.keyboard_rules = NULL; + config.keyboard_rules_count = 0; + } + // 释放 env if (config.env) { for (int32_t i = 0; i < config.env_count; i++) { diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index ffcee760..d6f1e432 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -1041,6 +1041,8 @@ int32_t switch_keyboard_layout(const Arg *arg) { if (id->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { continue; } + if (id->has_custom_keymap) + continue; struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data; diff --git a/src/mango.c b/src/mango.c index 63402c11..be8f864c 100644 --- a/src/mango.c +++ b/src/mango.c @@ -274,6 +274,7 @@ typedef struct { struct libinput_device *libinput_device; struct wl_listener destroy_listener; void *device_data; + bool has_custom_keymap; } InputDevice; typedef struct { @@ -3043,6 +3044,7 @@ void createidleinhibitor(struct wl_listener *listener, void *data) { void createkeyboard(struct wlr_keyboard *keyboard) { struct libinput_device *device = NULL; + bool has_custom = false; if (wlr_input_device_is_libinput(&keyboard->base) && (device = wlr_libinput_get_device_handle(&keyboard->base))) { @@ -3057,10 +3059,33 @@ void createkeyboard(struct wlr_keyboard *keyboard) { &input_dev->destroy_listener); wl_list_insert(&inputdevices, &input_dev->link); + + ConfigKeyboardRule *rule = + find_keyboard_rule(&config, libinput_device_get_name(device)); + if (rule && (rule->xkb_layout || rule->xkb_variant || rule->xkb_options)) { + struct xkb_rule_names rules = { + .rules = rule->xkb_rules ? rule->xkb_rules : config.xkb_rules.rules, + .model = rule->xkb_model ? rule->xkb_model : config.xkb_rules.model, + .layout = rule->xkb_layout, + .variant = rule->xkb_variant, + .options = rule->xkb_options ? rule->xkb_options : config.xkb_rules.options, + }; + struct xkb_context *ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *custom_keymap = + xkb_keymap_new_from_names(ctx, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (custom_keymap) { + wlr_keyboard_set_keymap(keyboard, custom_keymap); + xkb_keymap_unref(custom_keymap); + has_custom = true; + input_dev->has_custom_keymap = true; + } + xkb_context_unref(ctx); + } } - /* Set the keymap to match the group keymap */ - wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + if (!has_custom) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); @@ -5724,6 +5749,8 @@ void reset_keyboard_layout(void) { if (id->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { continue; } + if (id->has_custom_keymap) + continue; struct wlr_keyboard *tkb = (struct wlr_keyboard *)id->device_data;