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 }