lib/windowing/src/key.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const native_xkb = @import("xkb");
3
4 const Keysym = native_xkb.keysym.Keysym;
5
6 pub const Key = enum(u16) {
7 unknown = 0,
8
9 space = 32,
10 apostrophe = 39,
11 comma = 44,
12 minus = 45,
13 period = 46,
14 slash = 47,
15 @"0" = 48,
16 @"1" = 49,
17 @"2" = 50,
18 @"3" = 51,
19 @"4" = 52,
20 @"5" = 53,
21 @"6" = 54,
22 @"7" = 55,
23 @"8" = 56,
24 @"9" = 57,
25 semicolon = 59,
26 equal = 61,
27 a = 65,
28 b = 66,
29 c = 67,
30 d = 68,
31 e = 69,
32 f = 70,
33 g = 71,
34 h = 72,
35 i = 73,
36 j = 74,
37 k = 75,
38 l = 76,
39 m = 77,
40 n = 78,
41 o = 79,
42 p = 80,
43 q = 81,
44 r = 82,
45 s = 83,
46 t = 84,
47 u = 85,
48 v = 86,
49 w = 87,
50 x = 88,
51 y = 89,
52 z = 90,
53 left_bracket = 91,
54 backslash = 92,
55 right_bracket = 93,
56 grave = 96,
57
58 escape = 256,
59 enter = 257,
60 tab = 258,
61 backspace = 259,
62 insert = 260,
63 delete = 261,
64 right = 262,
65 left = 263,
66 down = 264,
67 up = 265,
68 page_up = 266,
69 page_down = 267,
70 home = 268,
71 end = 269,
72
73 caps_lock = 280,
74 scroll_lock = 281,
75 num_lock = 282,
76 print_screen = 283,
77 pause = 284,
78
79 f1 = 290,
80 f2 = 291,
81 f3 = 292,
82 f4 = 293,
83 f5 = 294,
84 f6 = 295,
85 f7 = 296,
86 f8 = 297,
87 f9 = 298,
88 f10 = 299,
89 f11 = 300,
90 f12 = 301,
91
92 kp_0 = 320,
93 kp_1 = 321,
94 kp_2 = 322,
95 kp_3 = 323,
96 kp_4 = 324,
97 kp_5 = 325,
98 kp_6 = 326,
99 kp_7 = 327,
100 kp_8 = 328,
101 kp_9 = 329,
102 kp_decimal = 330,
103 kp_divide = 331,
104 kp_multiply = 332,
105 kp_subtract = 333,
106 kp_add = 334,
107 kp_enter = 335,
108 kp_equal = 336,
109
110 left_shift = 340,
111 left_ctrl = 341,
112 left_alt = 342,
113 left_super = 343,
114 right_shift = 344,
115 right_ctrl = 345,
116 right_alt = 346,
117 right_super = 347,
118 menu = 348,
119
120 _,
121
122 pub fn fromKeysym(value: Keysym) Key {
123 const raw = @backingInt(value);
124 if (raw >= 'a' and raw <= 'z') {
125 return @fromBackingInt(@intCast(raw - 'a' + 'A'));
126 }
127 if (raw >= 'A' and raw <= 'Z') return @fromBackingInt(@intCast(raw));
128 if (raw >= '0' and raw <= '9') return @fromBackingInt(@intCast(raw));
129 if (raw >= 0xffbe and raw <= 0xffc9) {
130 return @fromBackingInt(@intCast(@backingInt(Key.f1) + raw - 0xffbe));
131 }
132 if (raw >= 0xffb0 and raw <= 0xffb9) {
133 return @fromBackingInt(@intCast(@backingInt(Key.kp_0) + raw - 0xffb0));
134 }
135 return printableFromKeysym(raw) orelse specialFromKeysym(raw);
136 }
137 };
138
139 fn printableFromKeysym(value: u32) ?Key {
140 return switch (value) {
141 ' ' => .space,
142 '\'', '"' => .apostrophe,
143 ',', '<' => .comma,
144 '-', '_' => .minus,
145 '.', '>' => .period,
146 '/', '?' => .slash,
147 '!' => .@"1",
148 '@' => .@"2",
149 '#' => .@"3",
150 '$' => .@"4",
151 '%' => .@"5",
152 '^' => .@"6",
153 '&' => .@"7",
154 '*' => .@"8",
155 '(' => .@"9",
156 ')' => .@"0",
157 ';', ':' => .semicolon,
158 '=', '+' => .equal,
159 '[', '{' => .left_bracket,
160 '\\', '|' => .backslash,
161 ']', '}' => .right_bracket,
162 '`', '~' => .grave,
163 else => null,
164 };
165 }
166
167 fn specialFromKeysym(value: u32) Key {
168 return switch (value) {
169 0xff1b => .escape,
170 0xff0d => .enter,
171 0xff09, 0xfe20 => .tab,
172 0xff08 => .backspace,
173 0xff63 => .insert,
174 0xffff => .delete,
175 0xff53 => .right,
176 0xff51 => .left,
177 0xff54 => .down,
178 0xff52 => .up,
179 0xff55 => .page_up,
180 0xff56 => .page_down,
181 0xff50 => .home,
182 0xff57 => .end,
183 0xffe5 => .caps_lock,
184 0xff14 => .scroll_lock,
185 0xff7f => .num_lock,
186 0xff61 => .print_screen,
187 0xff13 => .pause,
188 0xff95 => .kp_7,
189 0xff96 => .kp_4,
190 0xff97 => .kp_8,
191 0xff98 => .kp_6,
192 0xff99 => .kp_2,
193 0xff9a => .kp_9,
194 0xff9b => .kp_3,
195 0xff9c => .kp_1,
196 0xff9d => .kp_5,
197 0xff9e => .kp_0,
198 0xff9f, 0xffae => .kp_decimal,
199 0xffaf => .kp_divide,
200 0xffaa => .kp_multiply,
201 0xffad => .kp_subtract,
202 0xffab => .kp_add,
203 0xff8d => .kp_enter,
204 0xffbd => .kp_equal,
205 0xffe1 => .left_shift,
206 0xffe3 => .left_ctrl,
207 0xffe7, 0xffe9 => .left_alt,
208 0xffeb => .left_super,
209 0xffe2 => .right_shift,
210 0xffe4 => .right_ctrl,
211 0xffe8, 0xffea => .right_alt,
212 0xffec => .right_super,
213 0xff67 => .menu,
214 else => .unknown,
215 };
216 }
217
218 test "keysym mapping preserves logical keys across printable levels" {
219 try std.testing.expectEqual(Key.a, Key.fromKeysym(@fromBackingInt('a')));
220 try std.testing.expectEqual(Key.a, Key.fromKeysym(@fromBackingInt('A')));
221 try std.testing.expectEqual(Key.@"1", Key.fromKeysym(@fromBackingInt('!')));
222 try std.testing.expectEqual(Key.slash, Key.fromKeysym(@fromBackingInt('?')));
223 try std.testing.expectEqual(Key.f12, Key.fromKeysym(@fromBackingInt(0xffc9)));
224 try std.testing.expectEqual(Key.right_super, Key.fromKeysym(@fromBackingInt(0xffec)));
225 try std.testing.expectEqual(Key.unknown, Key.fromKeysym(@fromBackingInt(0xfffe)));
226 }
227
228 test "keysym mapping covers every keypad value and navigation alias" {
229 const cases = [_]struct { value: u32, expected: Key }{
230 .{ .value = 0xffb0, .expected = .kp_0 },
231 .{ .value = 0xffb1, .expected = .kp_1 },
232 .{ .value = 0xffb2, .expected = .kp_2 },
233 .{ .value = 0xffb3, .expected = .kp_3 },
234 .{ .value = 0xffb4, .expected = .kp_4 },
235 .{ .value = 0xffb5, .expected = .kp_5 },
236 .{ .value = 0xffb6, .expected = .kp_6 },
237 .{ .value = 0xffb7, .expected = .kp_7 },
238 .{ .value = 0xffb8, .expected = .kp_8 },
239 .{ .value = 0xffb9, .expected = .kp_9 },
240 .{ .value = 0xffae, .expected = .kp_decimal },
241 .{ .value = 0xffaf, .expected = .kp_divide },
242 .{ .value = 0xffaa, .expected = .kp_multiply },
243 .{ .value = 0xffad, .expected = .kp_subtract },
244 .{ .value = 0xffab, .expected = .kp_add },
245 .{ .value = 0xff8d, .expected = .kp_enter },
246 .{ .value = 0xffbd, .expected = .kp_equal },
247 .{ .value = 0xff95, .expected = .kp_7 },
248 .{ .value = 0xff96, .expected = .kp_4 },
249 .{ .value = 0xff97, .expected = .kp_8 },
250 .{ .value = 0xff98, .expected = .kp_6 },
251 .{ .value = 0xff99, .expected = .kp_2 },
252 .{ .value = 0xff9a, .expected = .kp_9 },
253 .{ .value = 0xff9b, .expected = .kp_3 },
254 .{ .value = 0xff9c, .expected = .kp_1 },
255 .{ .value = 0xff9d, .expected = .kp_5 },
256 .{ .value = 0xff9e, .expected = .kp_0 },
257 .{ .value = 0xff9f, .expected = .kp_decimal },
258 };
259 for (cases) |case| {
260 try std.testing.expectEqual(case.expected, Key.fromKeysym(@fromBackingInt(case.value)));
261 }
262 }