aboutsummaryrefslogtreecommitdiff
path: root/src/keyboard.zig
blob: 4229e0d72fe6e1bfb059a09a77e23808442a9113 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
const std = @import("std");
const c = @import("c.zig").c;

const Seat = @import("seat.zig").Seat;

pub const Keyboard = struct {
    const Self = @This();

    seat: *Seat,
    device: *c.wlr_input_device,
    wlr_keyboard: *c.wlr_keyboard,

    listen_modifiers: c.wl_listener,
    listen_key: c.wl_listener,

    pub fn init(self: *Self, seat: *Seat, device: *c.wlr_input_device) !void {
        self.seat = seat;
        self.device = device;
        self.wlr_keyboard = device.unnamed_133.keyboard;

        // We need to prepare an XKB keymap and assign it to the keyboard. This
        // assumes the defaults (e.g. layout = "us").
        const rules = c.xkb_rule_names{
            .rules = null,
            .model = null,
            .layout = null,
            .variant = null,
            .options = null,
        };
        const context = c.xkb_context_new(c.enum_xkb_context_flags.XKB_CONTEXT_NO_FLAGS) orelse
            return error.CantCreateXkbContext;
        defer c.xkb_context_unref(context);

        const keymap = c.xkb_keymap_new_from_names(
            context,
            &rules,
            c.enum_xkb_keymap_compile_flags.XKB_KEYMAP_COMPILE_NO_FLAGS,
        ) orelse
            return error.CantCreateXkbKeymap;
        defer c.xkb_keymap_unref(keymap);

        // TODO: handle failure after https://github.com/swaywm/wlroots/pull/2081
        c.wlr_keyboard_set_keymap(self.wlr_keyboard, keymap);
        c.wlr_keyboard_set_repeat_info(self.wlr_keyboard, 25, 600);

        // Setup listeners for keyboard events
        self.listen_modifiers.notify = handleModifiers;
        c.wl_signal_add(&self.wlr_keyboard.events.modifiers, &self.listen_modifiers);

        self.listen_key.notify = handleKey;
        c.wl_signal_add(&self.wlr_keyboard.events.key, &self.listen_key);
    }

    fn handleModifiers(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
        // This event is raised when a modifier key, such as shift or alt, is
        // pressed. We simply communicate this to the client. */
        const keyboard = @fieldParentPtr(Keyboard, "listen_modifiers", listener.?);

        // A seat can only have one keyboard, but this is a limitation of the
        // Wayland protocol - not wlroots. We assign all connected keyboards to the
        // same seat. You can swap out the underlying wlr_keyboard like this and
        // wlr_seat handles this transparently.
        c.wlr_seat_set_keyboard(keyboard.seat.wlr_seat, keyboard.device);

        // Send modifiers to the client.
        c.wlr_seat_keyboard_notify_modifiers(
            keyboard.seat.wlr_seat,
            &keyboard.wlr_keyboard.modifiers,
        );
    }

    fn handleKey(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
        // This event is raised when a key is pressed or released.
        const keyboard = @fieldParentPtr(Keyboard, "listen_key", listener.?);
        const event = @ptrCast(
            *c.wlr_event_keyboard_key,
            @alignCast(@alignOf(*c.wlr_event_keyboard_key), data),
        );

        const wlr_keyboard: *c.wlr_keyboard = keyboard.device.unnamed_133.keyboard;

        // Translate libinput keycode -> xkbcommon
        const keycode = event.keycode + 8;
        // Get a list of keysyms based on the keymap for this keyboard
        var syms: ?[*]c.xkb_keysym_t = undefined;
        const nsyms = c.xkb_state_key_get_syms(wlr_keyboard.xkb_state, keycode, &syms);

        var handled = false;
        const modifiers = c.wlr_keyboard_get_modifiers(wlr_keyboard);
        if (modifiers & @intCast(u32, c.WLR_MODIFIER_LOGO) != 0 and
            event.state == c.enum_wlr_key_state.WLR_KEY_PRESSED)
        {
            // If mod is held down and this button was _pressed_, we attempt to
            // process it as a compositor keybinding.
            var i: usize = 0;
            while (i < nsyms) {
                handled = keyboard.seat.server.handleKeybinding(syms.?[i]);
                if (handled) {
                    break;
                }
                i += 1;
            }
        }

        if (!handled) {
            // Otherwise, we pass it along to the client.
            const wlr_seat = keyboard.seat.wlr_seat;
            c.wlr_seat_set_keyboard(wlr_seat, keyboard.device);
            c.wlr_seat_keyboard_notify_key(
                wlr_seat,
                event.time_msec,
                event.keycode,
                @intCast(u32, @enumToInt(event.state)),
            );
        }
    }
};