input: add unicode bindings

This commit is contained in:
Mitchell Hashimoto
2024-06-02 10:32:03 -07:00
parent eb05606213
commit 8e7d109a2d
4 changed files with 56 additions and 5 deletions

View File

@ -333,11 +333,13 @@ typedef struct {
typedef enum {
GHOSTTY_TRIGGER_TRANSLATED,
GHOSTTY_TRIGGER_PHYSICAL,
GHOSTTY_TRIGGER_UNICODE,
} ghostty_input_trigger_tag_e;
typedef union {
ghostty_input_key_e translated;
ghostty_input_key_e physical;
uint32_t unicode;
} ghostty_input_trigger_key_u;
typedef struct {

View File

@ -131,6 +131,9 @@ extension Ghostty {
return nil
}
case GHOSTTY_TRIGGER_UNICODE:
equiv = String(trigger.key.unicode)
default:
return nil
}

View File

@ -1205,6 +1205,15 @@ pub fn keyCallback(
set.getConsumed(trigger),
};
if (event.unshifted_codepoint > 0) {
trigger.key = .{ .unicode = event.unshifted_codepoint };
if (set.get(trigger)) |v| break :action .{
v,
trigger,
set.getConsumed(trigger),
};
}
break :binding;
};

View File

@ -89,11 +89,7 @@ pub fn parse(raw_input: []const u8) !Binding {
if (!std.mem.eql(u8, field.name, "invalid")) {
if (std.mem.eql(u8, key_part, field.name)) {
// Repeat not allowed
if (result.key != .translated or
result.key.translated != .invalid)
{
return Error.InvalidFormat;
}
if (!result.isKeyUnset()) return Error.InvalidFormat;
const keyval = @field(key.Key, field.name);
result.key = if (physical)
@ -105,6 +101,21 @@ pub fn parse(raw_input: []const u8) !Binding {
}
}
// If we're still unset and we have exactly one unicode
// character then we can use that as a key.
if (result.isKeyUnset()) unicode: {
// Invalid UTF8 drops to invalid format
const view = std.unicode.Utf8View.init(key_part) catch break :unicode;
var it = view.iterator();
// No codepoints or multiple codepoints drops to invalid format
const cp = it.nextCodepoint() orelse break :unicode;
if (it.nextCodepoint() != null) break :unicode;
result.key = .{ .unicode = cp };
continue :loop;
}
// We didn't recognize this value
return Error.InvalidFormat;
}
@ -524,6 +535,11 @@ pub const Trigger = struct {
/// is used to bind to a physical key location rather than a translated
/// key.
physical: key.Key,
/// This is used for binding to keys that produce a certain unicode
/// codepoint. This is useful for binding to keys that don't have a
/// registered keycode with Ghostty.
unicode: u21,
};
/// The extern struct used for triggers in the C API.
@ -535,14 +551,24 @@ pub const Trigger = struct {
pub const Tag = enum(c_int) {
translated,
physical,
unicode,
};
pub const Key = extern union {
translated: key.Key,
physical: key.Key,
unicode: u32,
};
};
/// Returns true if this trigger has no key set.
pub fn isKeyUnset(self: Trigger) bool {
return switch (self.key) {
.translated => |v| v == .invalid,
else => false,
};
}
/// Returns a hash code that can be used to uniquely identify this trigger.
pub fn hash(self: Trigger) u64 {
var hasher = std.hash.Wyhash.init(0);
@ -558,6 +584,7 @@ pub const Trigger = struct {
.key = switch (self.key) {
.translated => |v| .{ .translated = v },
.physical => |v| .{ .physical = v },
.unicode => |v| .{ .unicode = @intCast(v) },
},
.mods = self.mods,
};
@ -583,6 +610,7 @@ pub const Trigger = struct {
switch (self.key) {
.translated => |k| try writer.print("{s}", .{@tagName(k)}),
.physical => |k| try writer.print("physical:{s}", .{@tagName(k)}),
.unicode => |c| try writer.print("{u}", .{c}),
}
}
};
@ -813,6 +841,15 @@ test "parse: triggers" {
.action = .{ .ignore = {} },
}, try parse("shift+physical:a=ignore"));
// unicode keys
try testing.expectEqual(Binding{
.trigger = .{
.mods = .{ .shift = true },
.key = .{ .unicode = 'ö' },
},
.action = .{ .ignore = {} },
}, try parse("shift+ö=ignore"));
// unconsumed keys
try testing.expectEqual(Binding{
.trigger = .{