Add per-device keyboard configuration

a30c2cbf6244e2c5b4cebf4c2d109f1b5e3ca143
Alexis Sellier committed ago 1 parent c1910527
config.def.h +14 -8
75 75
     { "eDP-1",    2,    WL_OUTPUT_TRANSFORM_NORMAL,   -1,  -1 }, */
76 76
    { nullptr, 1.0, WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 },
77 77
    /* Keep at least one fallback rule. */
78 78
};
79 79
80 -
/* XKB keyboard layout shared by physical keyboards. */
81 -
static const struct xkb_rule_names xkb_rules = {
82 -
    /* XKB layout name. */
83 -
    .layout  = "us",
84 -
    /* Layout variant; altgr-intl provides international characters via AltGr. */
85 -
    .variant = nullptr,
86 -
    /* Comma-separated XKB options, or nullptr for none. */
87 -
    .options = nullptr,
80 +
/* Keyboard rules match the exact device name. The final rule must have a null
81 +
 * name and applies to keyboards without a named rule. */
82 +
static const keyboard_rule_t keyboardrules[] = {
83 +
    {
84 +
        .name = nullptr,
85 +
        .xkb = {
86 +
            /* XKB layout name. */
87 +
            .layout  = "us",
88 +
            /* Layout variant; altgr-intl provides international characters via AltGr. */
89 +
            .variant = nullptr,
90 +
            /* Comma-separated XKB options, or nullptr for none. */
91 +
            .options = nullptr,
92 +
        },
93 +
    },
88 94
};
89 95
90 96
/* Repeat a held key this many times per second. */
91 97
static const int repeat_rate  = 40;
92 98
/* Wait this many milliseconds before a held key starts repeating. */
swm.c +119 -58
431 431
    float                    scale;
432 432
    enum wl_output_transform rr;
433 433
    int                      x, y;
434 434
} monitor_rule_t;
435 435
436 +
/* XKB configuration for a named physical keyboard. */
437 +
typedef struct {
438 +
    const char           *name; /* Null matches keyboards without a named rule. */
439 +
    struct xkb_rule_names xkb;  /* Key map for keyboards that match this rule. */
440 +
} keyboard_rule_t;
441 +
436 442
/* Active pointer constraint. */
437 443
typedef struct {
438 444
    struct wlr_pointer_constraint_v1 *constraint;
439 445
    struct wl_listener                destroy;
440 446
} pointer_constraint_t;
491 497
static void              popup_commit(struct wl_listener *listener, void *data);
492 498
static void              popup_destroy(struct wl_listener *listener, void *data);
493 499
static void              create_decoration(struct wl_listener *listener, void *data);
494 500
static void              create_idle_inhibitor(struct wl_listener *listener, void *data);
495 501
static void              create_keyboard(struct wlr_keyboard *keyboard);
496 -
static keyboard_group_t *create_keyboard_group(struct wlr_virtual_keyboard_v1 *virtual_keyboard);
497 -
static void              create_layer_surface(struct wl_listener *listener, void *data);
498 -
static void              create_lock_surface(struct wl_listener *listener, void *data);
499 -
static void              create_monitor(struct wl_listener *listener, void *data);
500 -
static void              create_notify(struct wl_listener *listener, void *data);
501 -
static void              create_pointer(struct wlr_pointer *pointer);
502 -
static void              create_pointer_constraint(struct wl_listener *listener, void *data);
503 -
static void              create_popup(struct wl_listener *listener, void *data);
504 -
static void              cursor_constrain(struct wlr_pointer_constraint_v1 *constraint);
505 -
static void              cursor_frame(struct wl_listener *listener, void *data);
506 -
static void              cursor_warp_to_hint(void);
507 -
static void              destroy_decoration(struct wl_listener *listener, void *data);
508 -
static void              destroy_drag_icon(struct wl_listener *listener, void *data);
509 -
static void              destroy_idle_inhibitor(struct wl_listener *listener, void *data);
510 -
static void              layer_surface_destroy_notify(struct wl_listener *listener, void *data);
511 -
static void              destroy_lock(session_lock_t *lock, int unlocked);
512 -
static void              destroy_lock_surface(struct wl_listener *listener, void *data);
513 -
static void              destroy_notify(struct wl_listener *listener, void *data);
514 -
static void              destroy_pointer_constraint(struct wl_listener *listener, void *data);
515 -
static void              destroy_session_lock(struct wl_listener *listener, void *data);
516 -
static void              destroy_keyboard_group(struct wl_listener *listener, void *data);
517 -
static monitor_t        *direction_to_monitor(enum wlr_direction dir);
518 -
static void              cycle_layout(const arg_t *arg);
519 -
static void              cycle_workspace(const arg_t *arg);
520 -
static void              focus_client(client_t *c, int lift);
521 -
static client_t         *focus_close(client_t *c);
522 -
static void              focus_main(const arg_t *arg);
523 -
static void              focus_urgent(const arg_t *arg);
524 -
static void              ftl_activate_notify(struct wl_listener *listener, void *data);
525 -
static void              ftl_capture_request_notify(struct wl_listener *listener, void *data);
526 -
static void              ftl_close_notify(struct wl_listener *listener, void *data);
527 -
static void              ftl_fullscreen_notify(struct wl_listener *listener, void *data);
528 -
static void              ftl_sync(client_t *c);
529 -
static void              input_method_commit_notify(struct wl_listener *listener, void *data);
530 -
static void              input_method_create_notify(struct wl_listener *listener, void *data);
531 -
static void              input_method_destroy_notify(struct wl_listener *listener, void *data);
502 +
static keyboard_group_t *create_keyboard_group(
503 +
    struct wlr_virtual_keyboard_v1 *virtual_keyboard, const struct xkb_rule_names *xkb
504 +
);
505 +
static void       create_layer_surface(struct wl_listener *listener, void *data);
506 +
static void       create_lock_surface(struct wl_listener *listener, void *data);
507 +
static void       create_monitor(struct wl_listener *listener, void *data);
508 +
static void       create_notify(struct wl_listener *listener, void *data);
509 +
static void       create_pointer(struct wlr_pointer *pointer);
510 +
static void       create_pointer_constraint(struct wl_listener *listener, void *data);
511 +
static void       create_popup(struct wl_listener *listener, void *data);
512 +
static void       cursor_constrain(struct wlr_pointer_constraint_v1 *constraint);
513 +
static void       cursor_frame(struct wl_listener *listener, void *data);
514 +
static void       cursor_warp_to_hint(void);
515 +
static void       destroy_decoration(struct wl_listener *listener, void *data);
516 +
static void       destroy_drag_icon(struct wl_listener *listener, void *data);
517 +
static void       destroy_idle_inhibitor(struct wl_listener *listener, void *data);
518 +
static void       layer_surface_destroy_notify(struct wl_listener *listener, void *data);
519 +
static void       destroy_lock(session_lock_t *lock, int unlocked);
520 +
static void       destroy_lock_surface(struct wl_listener *listener, void *data);
521 +
static void       destroy_notify(struct wl_listener *listener, void *data);
522 +
static void       destroy_pointer_constraint(struct wl_listener *listener, void *data);
523 +
static void       destroy_session_lock(struct wl_listener *listener, void *data);
524 +
static void       destroy_keyboard_group(struct wl_listener *listener, void *data);
525 +
static monitor_t *direction_to_monitor(enum wlr_direction dir);
526 +
static void       cycle_layout(const arg_t *arg);
527 +
static void       cycle_workspace(const arg_t *arg);
528 +
static void       focus_client(client_t *c, int lift);
529 +
static client_t  *focus_close(client_t *c);
530 +
static void       focus_main(const arg_t *arg);
531 +
static void       focus_urgent(const arg_t *arg);
532 +
static void       ftl_activate_notify(struct wl_listener *listener, void *data);
533 +
static void       ftl_capture_request_notify(struct wl_listener *listener, void *data);
534 +
static void       ftl_close_notify(struct wl_listener *listener, void *data);
535 +
static void       ftl_fullscreen_notify(struct wl_listener *listener, void *data);
536 +
static void       ftl_sync(client_t *c);
537 +
static void       input_method_commit_notify(struct wl_listener *listener, void *data);
538 +
static void       input_method_create_notify(struct wl_listener *listener, void *data);
539 +
static void       input_method_destroy_notify(struct wl_listener *listener, void *data);
532 540
static struct wlr_text_input_v3 *input_method_focused_text_input(void);
533 541
static void      input_method_grab_keyboard(struct wl_listener *listener, void *data);
534 542
static void      input_method_new_popup(struct wl_listener *listener, void *data);
535 543
static void      input_method_send_state(struct wlr_text_input_v3 *ti);
536 544
static void      input_method_set_focus(struct wlr_surface *surface);
837 845
838 846
/* Load configuration after declaring the types and state it may use. */
839 847
#include "config.h"
840 848
841 849
static_assert(WSCOUNT > 0 && WSCOUNT <= 32, "WSCOUNT must be between 1 and 32");
850 +
static_assert(
851 +
    LENGTH(keyboardrules) <= MAX_KEYBOARD_GROUPS,
852 +
    "keyboard rules must fit in the keyboard group pool"
853 +
);
842 854
843 -
static workspace_t workspaces[WSCOUNT];
855 +
static workspace_t       workspaces[WSCOUNT];
856 +
static keyboard_group_t *physical_keyboard_groups[LENGTH(keyboardrules)];
844 857
845 858
static pid_t                           autostart_pids[MAX_AUTOSTART];
846 859
static struct wlr_backend_output_state output_state_pool[MAX_MONITORS];
847 860
848 861
/* Fixed-capacity object pools. */
2188 2201
    }
2189 2202
    for (i = 0; i < LENGTH(signal_sources); i++)
2190 2203
        wl_event_source_remove(signal_sources[i]);
2191 2204
    wlr_xcursor_manager_destroy(cursor_mgr);
2192 2205
2193 -
    destroy_keyboard_group(&kb_group->destroy, nullptr);
2206 +
    for (i = 0; i < LENGTH(physical_keyboard_groups); i++)
2207 +
        if (physical_keyboard_groups[i])
2208 +
            destroy_keyboard_group(&physical_keyboard_groups[i]->destroy, nullptr);
2194 2209
2195 2210
    /* Destroy this explicitly because wlroots may otherwise retain a pointer
2196 2211
     * to the seat after the seat is gone. */
2197 2212
    wlr_backend_destroy(backend);
2198 2213
2497 2512
    listen_static(&idle_inhibitor->events.destroy, destroy_idle_inhibitor);
2498 2513
2499 2514
    check_idle_inhibitor(nullptr);
2500 2515
}
2501 2516
2502 -
/* Add a physical keyboard to the shared keyboard group. */
2517 +
/* Select the first named keyboard rule or the fallback rule. */
2518 +
size_t keyboard_rule_index(
2519 +
    const keyboard_rule_t *keyboard_rules, size_t rule_count, const char *device_name
2520 +
) {
2521 +
    size_t fallback = rule_count;
2522 +
    size_t i;
2523 +
2524 +
    for (i = 0; i < rule_count; i++) {
2525 +
        if (!keyboard_rules[i].name) {
2526 +
            if (fallback == rule_count)
2527 +
                fallback = i;
2528 +
        } else if (device_name && !strcmp(keyboard_rules[i].name, device_name))
2529 +
            return i;
2530 +
    }
2531 +
    return fallback;
2532 +
}
2533 +
2534 +
/* Report whether at least one physical keyboard is connected. */
2535 +
bool has_physical_keyboard(void) {
2536 +
    size_t i;
2537 +
2538 +
    for (i = 0; i < LENGTH(physical_keyboard_groups); i++)
2539 +
        if (physical_keyboard_groups[i] &&
2540 +
            !wl_list_empty(&physical_keyboard_groups[i]->wlr_group->devices))
2541 +
            return true;
2542 +
    return false;
2543 +
}
2544 +
2545 +
/* Add a physical keyboard to the group for its matching rule. */
2503 2546
void create_keyboard(struct wlr_keyboard *keyboard) {
2547 +
    size_t index = keyboard_rule_index(keyboardrules, LENGTH(keyboardrules), keyboard->base.name);
2548 +
    keyboard_group_t *group;
2549 +
2550 +
    if (index == LENGTH(keyboardrules))
2551 +
        die("keyboard rules require a fallback rule");
2552 +
    group = physical_keyboard_groups[index];
2553 +
2504 2554
    /* Use the keyboard group's key mapping. */
2505 -
    wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap);
2555 +
    wlr_keyboard_set_keymap(keyboard, group->wlr_group->keyboard.keymap);
2506 2556
2507 -
    /* Add the keyboard to the shared group. */
2508 -
    wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard);
2557 +
    /* Combine keyboards that use the same rule. */
2558 +
    wlr_keyboard_group_add_keyboard(group->wlr_group, keyboard);
2509 2559
}
2510 2560
2511 2561
/* Create a keyboard group with the configured key map and repeat settings. */
2512 -
keyboard_group_t *create_keyboard_group(struct wlr_virtual_keyboard_v1 *virtual_keyboard) {
2562 +
keyboard_group_t *create_keyboard_group(
2563 +
    struct wlr_virtual_keyboard_v1 *virtual_keyboard, const struct xkb_rule_names *xkb
2564 +
) {
2513 2565
    keyboard_group_t   *group = pool_take(&keyboard_group_pool);
2514 2566
    struct xkb_context *context;
2515 2567
    struct xkb_keymap  *keymap;
2516 2568
2517 2569
    if (!group)
2526 2578
    group->virtual_keyboard = virtual_keyboard;
2527 2579
2528 2580
    /* Prepare an XKB keymap and assign it to the keyboard group. */
2529 2581
    context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
2530 2582
2531 -
    if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, XKB_KEYMAP_COMPILE_NO_FLAGS)))
2583 +
    if (!(keymap = xkb_keymap_new_from_names(context, xkb, XKB_KEYMAP_COMPILE_NO_FLAGS)))
2532 2584
        die("failed to compile keymap");
2533 2585
2534 2586
    wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap);
2535 2587
    xkb_keymap_unref(keymap);
2536 2588
    xkb_context_unref(context);
2541 2593
    LISTEN(&group->wlr_group->keyboard.events.key, &group->key, key_press);
2542 2594
    LISTEN(&group->wlr_group->keyboard.events.modifiers, &group->modifiers, key_press_modifiers);
2543 2595
2544 2596
    group->key_repeat_source = wl_event_loop_add_timer(event_loop, key_repeat, group);
2545 2597
2546 -
    /* Wayland exposes one active keyboard per seat, so combine physical
2547 -
     * keyboards into one group. */
2548 -
    if (!virtual_keyboard)
2549 -
        wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
2550 2598
    return group;
2551 2599
}
2552 2600
2553 2601
/* Choose the layer that contains popups belonging to a layer-shell surface. */
2554 2602
static struct wlr_scene_tree *layer_popup_parent(struct wlr_layer_surface_v1 *layer_surface) {
3098 3146
/* Remove a keyboard group, its timer, and its event listeners. */
3099 3147
void destroy_keyboard_group(struct wl_listener *listener, void *data) {
3100 3148
    keyboard_group_t *group       = wl_container_of(listener, group, destroy);
3101 3149
    int               was_current = wlr_seat_get_keyboard(seat) == &group->wlr_group->keyboard;
3102 3150
    bool              is_main     = group == kb_group;
3151 +
    size_t            i;
3103 3152
3104 3153
    if (group->virtual_keyboard)
3105 3154
        virtual_keyboards--;
3155 +
    else
3156 +
        for (i = 0; i < LENGTH(physical_keyboard_groups); i++)
3157 +
            if (physical_keyboard_groups[i] == group)
3158 +
                physical_keyboard_groups[i] = nullptr;
3106 3159
    wl_event_source_remove(group->key_repeat_source);
3107 3160
    wl_list_remove(&group->key.link);
3108 3161
    wl_list_remove(&group->modifiers.link);
3109 3162
    wl_list_remove(&group->destroy.link);
3110 3163
    wlr_keyboard_group_destroy(group->wlr_group);
3117 3170
    if (was_current)
3118 3171
        wlr_seat_set_keyboard(seat, &kb_group->wlr_group->keyboard);
3119 3172
    wlr_seat_set_capabilities(
3120 3173
        seat,
3121 3174
        WL_SEAT_CAPABILITY_POINTER |
3122 -
            (!wl_list_empty(&kb_group->wlr_group->devices) || virtual_keyboards
3123 -
                 ? WL_SEAT_CAPABILITY_KEYBOARD
3124 -
                 : 0)
3175 +
            (has_physical_keyboard() || virtual_keyboards ? WL_SEAT_CAPABILITY_KEYBOARD : 0)
3125 3176
    );
3126 3177
}
3127 3178
3128 3179
/* Return the nearest enabled display in the requested direction. */
3129 3180
monitor_t *direction_to_monitor(enum wlr_direction dir) {
3831 3882
    /* Advertise the input devices available to applications. swm always has
3832 3883
     * a cursor, even when no physical pointing device is connected. */
3833 3884
    /* TODO: Check whether swm truly needs to always advertise a pointer. */
3834 3885
    caps = WL_SEAT_CAPABILITY_POINTER;
3835 3886
3836 -
    if (!wl_list_empty(&kb_group->wlr_group->devices) || virtual_keyboards)
3887 +
    if (has_physical_keyboard() || virtual_keyboards)
3837 3888
        caps |= WL_SEAT_CAPABILITY_KEYBOARD;
3838 3889
    wlr_seat_set_capabilities(seat, caps);
3839 3890
}
3840 3891
3841 3892
/* Run the matching window-manager shortcut and report whether it may repeat. */
6176 6227
6177 6228
/* Create the Wayland server, protocols, input state, and rendering resources. */
6178 6229
void setup(void) {
6179 6230
    static const int signals[] = { SIGCHLD, SIGINT, SIGTERM };
6180 6231
    int              drm_fd, i;
6232 +
    size_t           rule_index;
6181 6233
6182 6234
    wlr_log_init(log_level, nullptr);
6183 6235
6184 6236
    /* Create the Wayland server and its event loop. */
6185 6237
    dpy        = wl_display_create();
6398 6450
    im_mgr = wlr_input_method_manager_v2_create(dpy);
6399 6451
    listen_global(&im_mgr->events.new_input_method, &new_input_method);
6400 6452
    ti_mgr = wlr_text_input_manager_v3_create(dpy);
6401 6453
    listen_global(&ti_mgr->events.new_text_input, &new_text_input);
6402 6454
6403 -
    kb_group = create_keyboard_group(nullptr);
6404 -
6405 -
    if (!kb_group)
6406 -
        die("failed to allocate primary keyboard group");
6407 -
    wl_list_init(&kb_group->destroy.link);
6455 +
    if (keyboardrules[LENGTH(keyboardrules) - 1].name)
6456 +
        die("the last keyboard rule must be a fallback rule");
6457 +
    for (rule_index = 0; rule_index < LENGTH(keyboardrules); rule_index++) {
6458 +
        physical_keyboard_groups[rule_index] =
6459 +
            create_keyboard_group(nullptr, &keyboardrules[rule_index].xkb);
6460 +
        if (!physical_keyboard_groups[rule_index])
6461 +
            die("failed to allocate physical keyboard group");
6462 +
        wl_list_init(&physical_keyboard_groups[rule_index]->destroy.link);
6463 +
    }
6464 +
    kb_group = physical_keyboard_groups[LENGTH(physical_keyboard_groups) - 1];
6465 +
    /* Wayland exposes one active keyboard per seat. Use the fallback group
6466 +
     * until input selects another physical keyboard group. */
6467 +
    wlr_seat_set_keyboard(seat, &kb_group->wlr_group->keyboard);
6408 6468
6409 6469
    output_mgr = wlr_output_manager_v1_create(dpy);
6410 6470
    listen_global(&output_mgr->events.apply, &output_mgr_apply);
6411 6471
    listen_global(&output_mgr->events.test, &output_mgr_test);
6412 6472
6942 7002
/* Create an independent keyboard group for a virtual keyboard. */
6943 7003
void virtual_keyboard(struct wl_listener *listener, void *data) {
6944 7004
    struct wlr_virtual_keyboard_v1 *kb = data;
6945 7005
6946 7006
    /* Give each virtual keyboard its own group. */
6947 -
    keyboard_group_t *group = create_keyboard_group(kb);
7007 +
    keyboard_group_t *group =
7008 +
        create_keyboard_group(kb, &keyboardrules[LENGTH(keyboardrules) - 1].xkb);
6948 7009
6949 7010
    if (!group) {
6950 7011
        wl_resource_post_no_memory(kb->resource);
6951 7012
        return;
6952 7013
    }
test/unit.c +13 -0
730 730
    slot = pool_take(&static_listener_pool);
731 731
    assert(slot);
732 732
    listener_release(&slot->listener);
733 733
}
734 734
735 +
/* Verify exact keyboard rule matching and fallback selection. */
736 +
static void test_keyboard_rules(void) {
737 +
    const keyboard_rule_t keyboard_rules[] = {
738 +
        { .name = "Internal Keyboard" },
739 +
        { .name = nullptr },
740 +
    };
741 +
742 +
    assert(keyboard_rule_index(keyboard_rules, LENGTH(keyboard_rules), "Internal Keyboard") == 0);
743 +
    assert(keyboard_rule_index(keyboard_rules, LENGTH(keyboard_rules), "USB Keyboard") == 1);
744 +
    assert(keyboard_rule_index(keyboard_rules, 1, "USB Keyboard") == 1);
745 +
}
746 +
735 747
/* Verify XDG and XWayland client accessors without a running compositor. */
736 748
static void test_client_accessors(void) {
737 749
    client_t                    client   = {};
738 750
    struct wlr_xdg_surface      xdg      = { .geometry = { 3, 4, 50, 60 } };
739 751
    struct wlr_xdg_toplevel     toplevel = {};
1183 1195
        { "layer-unmap-monitor", test_layer_unmap_monitor },
1184 1196
        { "command-failure-notification", test_command_failure_notification },
1185 1197
        { "layer-focus-release", test_layer_focus_release },
1186 1198
        { "commands-without-clients", test_commands_without_clients },
1187 1199
        { "small-helpers", test_small_helpers },
1200 +
        { "keyboard-rules", test_keyboard_rules },
1188 1201
        { "pointer-grab-button", test_pointer_grab_button },
1189 1202
        { "client-accessors", test_client_accessors },
1190 1203
        { "autostart", test_autostart_execution },
1191 1204
        { "pointer-gestures", test_pointer_gestures },
1192 1205
        { "x11-urgency", test_x11_urgency },