lib/xkb/src/keysym/name.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const keysym = @import("root.zig");
3 const data = @import("generated/root.zig").name;
4
5 const Keysym = keysym.Keysym;
6
7 pub fn fromName(input: []const u8) ?Keysym {
8 if (findExact(input)) |value| return value;
9 if (input.len > 1 and input[0] == 'U') return unicodeNotation(input[1..]);
10 if (input.len > 2 and std.mem.eql(u8, input[0..2], "0x")) return numericNotation(input[2..]);
11 if (std.mem.startsWith(u8, input, "XF86_")) return xf86Notation(input);
12 return null;
13 }
14
15 fn findExact(input: []const u8) ?Keysym {
16 var low: usize = 0;
17 var high: usize = data.entries.len;
18 while (low < high) {
19 const middle = low + (high - low) / 2;
20 const entry = data.entries[middle];
21 const candidate = data.bytes[entry.offset..][0..entry.length];
22 switch (std.mem.order(u8, input, candidate)) {
23 .lt => high = middle,
24 .gt => low = middle + 1,
25 .eq => return @fromBackingInt(@intCast(entry.keysym)),
26 }
27 }
28 return null;
29 }
30
31 fn unicodeNotation(digits: []const u8) ?Keysym {
32 const value = parseHex(digits) orelse return null;
33 if (value > 0x10ffff) return null;
34 if (value > 0xff) return @fromBackingInt(@intCast(keysym.unicode_offset + value));
35 return @fromBackingInt(@intCast(legacyUnicodeNotation(value)));
36 }
37
38 fn legacyUnicodeNotation(value: u32) u32 {
39 if ((value >= 0x20 and value <= 0x7e) or (value >= 0xa0 and value <= 0xff)) return value;
40 return switch (value) {
41 0 => 0,
42 0x08...0x0b => value | 0xff00,
43 0x0d => 0xff0d,
44 0x1b => 0xff1b,
45 0x7f => 0xffff,
46 else => keysym.unicode_offset | value,
47 };
48 }
49
50 fn numericNotation(digits: []const u8) ?Keysym {
51 const value = parseHex(digits) orelse return null;
52 if (value > keysym.max) return null;
53 return @fromBackingInt(@intCast(value));
54 }
55
56 fn parseHex(digits: []const u8) ?u32 {
57 if (digits.len == 0 or digits.len > 8) return null;
58 return std.fmt.parseInt(u32, digits, 16) catch null;
59 }
60
61 fn xf86Notation(input: []const u8) ?Keysym {
62 const normalized_length = input.len - 1;
63 if (normalized_length > data.max_length) return null;
64 var buffer: [data.max_length]u8 = undefined;
65 @memcpy(buffer[0..4], input[0..4]);
66 @memcpy(buffer[4..normalized_length], input[5..]);
67 return findExact(buffer[0..normalized_length]);
68 }
69
70 test "every pinned name and alias resolves" {
71 try std.testing.expectEqual(@as(usize, data.count), data.entries.len);
72 for (data.entries) |entry| {
73 const input = data.bytes[entry.offset..][0..entry.length];
74 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(entry.keysym))), fromName(input));
75 }
76 }
77
78 test "aliases retain their shared values" {
79 try std.testing.expectEqual(fromName("Prior"), fromName("Page_Up"));
80 try std.testing.expectEqual(fromName("Henkan_Mode"), fromName("Henkan"));
81 try std.testing.expectEqual(fromName("ISO_Group_Shift"), fromName("Mode_switch"));
82 try std.testing.expectEqual(fromName("ISO_Group_Shift"), fromName("script_switch"));
83 }
84
85 test "numeric and Unicode notation resolve without allocation" {
86 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x0101f600))), fromName("U1F600"));
87 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x0100d800))), fromName("U0D800"));
88 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x01000080))), fromName("U0080"));
89 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x00000041))), fromName("U0041"));
90 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x0000ff08))), fromName("U0008"));
91 try std.testing.expectEqual(@as(?Keysym, .no_symbol), fromName("U0000"));
92 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x1008ff12))), fromName("0x1008ff12"));
93 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(keysym.max))), fromName("0x1fffffff"));
94 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x1008ff12))), fromName("XF86_AudioMute"));
95 try std.testing.expectEqual(@as(?Keysym, @fromBackingInt(@intCast(0x1008fe05))), fromName("XF86_Switch_VT_5"));
96 }
97
98 test "invalid notation is absent" {
99 try std.testing.expectEqual(@as(?Keysym, null), fromName(""));
100 try std.testing.expectEqual(@as(?Keysym, null), fromName("NotAKeysym"));
101 try std.testing.expectEqual(@as(?Keysym, null), fromName("U_"));
102 try std.testing.expectEqual(@as(?Keysym, null), fromName("U110000"));
103 try std.testing.expectEqual(@as(?Keysym, null), fromName("0x"));
104 try std.testing.expectEqual(@as(?Keysym, null), fromName("0x20000000"));
105 try std.testing.expectEqual(@as(?Keysym, null), fromName("XF86_NotAKeysym"));
106 }