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 }