From 619427c84c477ea3de9794b219156a0f410fe568 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 8 Jan 2026 07:02:46 -0800 Subject: [PATCH] input: move mods out to key_mods.zig --- src/input.zig | 3 +- src/input/Binding.zig | 15 ++-- src/input/key.zig | 157 +--------------------------------- src/input/key_mods.zig | 185 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 166 deletions(-) create mode 100644 src/input/key_mods.zig diff --git a/src/input.zig b/src/input.zig index fe981ccc9..df636c122 100644 --- a/src/input.zig +++ b/src/input.zig @@ -4,6 +4,7 @@ const builtin = @import("builtin"); const config = @import("input/config.zig"); const mouse = @import("input/mouse.zig"); const key = @import("input/key.zig"); +const key_mods = @import("input/key_mods.zig"); const keyboard = @import("input/keyboard.zig"); pub const command = @import("input/command.zig"); @@ -22,7 +23,7 @@ pub const Key = key.Key; pub const KeyboardLayout = keyboard.Layout; pub const KeyEvent = key.KeyEvent; pub const InspectorMode = Binding.Action.InspectorMode; -pub const Mods = key.Mods; +pub const Mods = key_mods.Mods; pub const MouseButton = mouse.Button; pub const MouseButtonState = mouse.ButtonState; pub const MousePressureStage = mouse.PressureStage; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 08f5fdf7c..3197bb7d1 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -9,6 +9,7 @@ const build_config = @import("../build_config.zig"); const uucode = @import("uucode"); const EntryFormatter = @import("../config/formatter.zig").EntryFormatter; const key = @import("key.zig"); +const key_mods = @import("key_mods.zig"); const KeyEvent = key.KeyEvent; /// The trigger that needs to be performed to execute the action. @@ -1640,18 +1641,12 @@ pub const Trigger = struct { } // Alias modifiers - const alias_mods = .{ - .{ "cmd", "super" }, - .{ "command", "super" }, - .{ "opt", "alt" }, - .{ "option", "alt" }, - .{ "control", "ctrl" }, - }; - inline for (alias_mods) |pair| { + inline for (key_mods.alias) |pair| { if (std.mem.eql(u8, part, pair[0])) { // Repeat not allowed - if (@field(result.mods, pair[1])) return Error.InvalidFormat; - @field(result.mods, pair[1]) = true; + const field = @tagName(pair[1]); + if (@field(result.mods, field)) return Error.InvalidFormat; + @field(result.mods, field) = true; continue :loop; } } diff --git a/src/input/key.zig b/src/input/key.zig index 6445871eb..a929a0323 100644 --- a/src/input/key.zig +++ b/src/input/key.zig @@ -4,6 +4,8 @@ const Allocator = std.mem.Allocator; const cimgui = @import("dcimgui"); const OptionAsAlt = @import("config.zig").OptionAsAlt; +pub const Mods = @import("key_mods.zig").Mods; + /// A generic key input event. This is the information that is necessary /// regardless of apprt in order to generate the proper terminal /// control sequences for a given key press. @@ -76,161 +78,6 @@ pub const KeyEvent = struct { } }; -/// A bitmask for all key modifiers. -/// -/// IMPORTANT: Any changes here update include/ghostty.h -pub const Mods = packed struct(Mods.Backing) { - pub const Backing = u16; - - shift: bool = false, - ctrl: bool = false, - alt: bool = false, - super: bool = false, - caps_lock: bool = false, - num_lock: bool = false, - sides: side = .{}, - _padding: u6 = 0, - - /// Tracks the side that is active for any given modifier. Note - /// that this doesn't confirm a modifier is pressed; you must check - /// the bool for that in addition to this. - /// - /// Not all platforms support this, check apprt for more info. - pub const side = packed struct(u4) { - shift: Side = .left, - ctrl: Side = .left, - alt: Side = .left, - super: Side = .left, - }; - - pub const Side = enum(u1) { left, right }; - - /// Integer value of this struct. - pub fn int(self: Mods) Backing { - return @bitCast(self); - } - - /// Returns true if no modifiers are set. - pub fn empty(self: Mods) bool { - return self.int() == 0; - } - - /// Returns true if two mods are equal. - pub fn equal(self: Mods, other: Mods) bool { - return self.int() == other.int(); - } - - /// Return mods that are only relevant for bindings. - pub fn binding(self: Mods) Mods { - return .{ - .shift = self.shift, - .ctrl = self.ctrl, - .alt = self.alt, - .super = self.super, - }; - } - - /// Perform `self &~ other` to remove the other mods from self. - pub fn unset(self: Mods, other: Mods) Mods { - return @bitCast(self.int() & ~other.int()); - } - - /// Returns the mods without locks set. - pub fn withoutLocks(self: Mods) Mods { - var copy = self; - copy.caps_lock = false; - copy.num_lock = false; - return copy; - } - - /// Return the mods to use for key translation. This handles settings - /// like macos-option-as-alt. The translation mods should be used for - /// translation but never sent back in for the key callback. - pub fn translation(self: Mods, option_as_alt: OptionAsAlt) Mods { - var result = self; - - // macos-option-as-alt for darwin - if (comptime builtin.target.os.tag.isDarwin()) alt: { - // Alt has to be set only on the correct side - switch (option_as_alt) { - .false => break :alt, - .true => {}, - .left => if (self.sides.alt == .right) break :alt, - .right => if (self.sides.alt == .left) break :alt, - } - - // Unset alt - result.alt = false; - } - - return result; - } - - /// Checks to see if super is on (MacOS) or ctrl. - pub fn ctrlOrSuper(self: Mods) bool { - if (comptime builtin.target.os.tag.isDarwin()) { - return self.super; - } - return self.ctrl; - } - - // For our own understanding - test { - const testing = std.testing; - try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0)); - try testing.expectEqual( - @as(Backing, @bitCast(Mods{ .shift = true })), - @as(Backing, 0b0000_0001), - ); - } - - test "translation macos-option-as-alt" { - if (comptime !builtin.target.os.tag.isDarwin()) return error.SkipZigTest; - - const testing = std.testing; - - // Unset - { - const mods: Mods = .{}; - const result = mods.translation(.true); - try testing.expectEqual(result, mods); - } - - // Set - { - const mods: Mods = .{ .alt = true }; - const result = mods.translation(.true); - try testing.expectEqual(Mods{}, result); - } - - // Set but disabled - { - const mods: Mods = .{ .alt = true }; - const result = mods.translation(.false); - try testing.expectEqual(result, mods); - } - - // Set wrong side - { - const mods: Mods = .{ .alt = true, .sides = .{ .alt = .right } }; - const result = mods.translation(.left); - try testing.expectEqual(result, mods); - } - { - const mods: Mods = .{ .alt = true, .sides = .{ .alt = .left } }; - const result = mods.translation(.right); - try testing.expectEqual(result, mods); - } - - // Set with other mods - { - const mods: Mods = .{ .alt = true, .shift = true }; - const result = mods.translation(.true); - try testing.expectEqual(Mods{ .shift = true }, result); - } - } -}; - /// The action associated with an input event. This is backed by a c_int /// so that we can use the enum as-is for our embedding API. /// diff --git a/src/input/key_mods.zig b/src/input/key_mods.zig new file mode 100644 index 000000000..885bceda1 --- /dev/null +++ b/src/input/key_mods.zig @@ -0,0 +1,185 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const OptionAsAlt = @import("config.zig").OptionAsAlt; + +/// Aliases for modifier names. +pub const alias: []const struct { []const u8, Mod } = &.{ + .{ "cmd", .super }, + .{ "command", .super }, + .{ "opt", .alt }, + .{ "option", .alt }, + .{ "control", .ctrl }, +}; + +/// Single modifier +pub const Mod = enum { + shift, + ctrl, + alt, + super, + + pub const Side = enum(u1) { left, right }; +}; + +/// A bitmask for all key modifiers. +/// +/// IMPORTANT: Any changes here update include/ghostty.h +pub const Mods = packed struct(Mods.Backing) { + pub const Backing = u16; + + shift: bool = false, + ctrl: bool = false, + alt: bool = false, + super: bool = false, + caps_lock: bool = false, + num_lock: bool = false, + sides: side = .{}, + _padding: u6 = 0, + + /// Tracks the side that is active for any given modifier. Note + /// that this doesn't confirm a modifier is pressed; you must check + /// the bool for that in addition to this. + /// + /// Not all platforms support this, check apprt for more info. + pub const side = packed struct(u4) { + shift: Mod.Side = .left, + ctrl: Mod.Side = .left, + alt: Mod.Side = .left, + super: Mod.Side = .left, + }; + + /// Integer value of this struct. + pub fn int(self: Mods) Backing { + return @bitCast(self); + } + + /// Returns true if no modifiers are set. + pub fn empty(self: Mods) bool { + return self.int() == 0; + } + + /// Returns true if two mods are equal. + pub fn equal(self: Mods, other: Mods) bool { + return self.int() == other.int(); + } + + /// Return mods that are only relevant for bindings. + pub fn binding(self: Mods) Mods { + return .{ + .shift = self.shift, + .ctrl = self.ctrl, + .alt = self.alt, + .super = self.super, + }; + } + + /// Perform `self &~ other` to remove the other mods from self. + pub fn unset(self: Mods, other: Mods) Mods { + return @bitCast(self.int() & ~other.int()); + } + + /// Returns the mods without locks set. + pub fn withoutLocks(self: Mods) Mods { + var copy = self; + copy.caps_lock = false; + copy.num_lock = false; + return copy; + } + + /// Return the mods to use for key translation. This handles settings + /// like macos-option-as-alt. The translation mods should be used for + /// translation but never sent back in for the key callback. + pub fn translation(self: Mods, option_as_alt: OptionAsAlt) Mods { + var result = self; + + // macos-option-as-alt for darwin + if (comptime builtin.target.os.tag.isDarwin()) alt: { + // Alt has to be set only on the correct side + switch (option_as_alt) { + .false => break :alt, + .true => {}, + .left => if (self.sides.alt == .right) break :alt, + .right => if (self.sides.alt == .left) break :alt, + } + + // Unset alt + result.alt = false; + } + + return result; + } + + /// Checks to see if super is on (MacOS) or ctrl. + pub fn ctrlOrSuper(self: Mods) bool { + if (comptime builtin.target.os.tag.isDarwin()) { + return self.super; + } + return self.ctrl; + } + + // For our own understanding + test { + const testing = std.testing; + try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0)); + try testing.expectEqual( + @as(Backing, @bitCast(Mods{ .shift = true })), + @as(Backing, 0b0000_0001), + ); + } + + test "translation macos-option-as-alt" { + if (comptime !builtin.target.os.tag.isDarwin()) return error.SkipZigTest; + + const testing = std.testing; + + // Unset + { + const mods: Mods = .{}; + const result = mods.translation(.true); + try testing.expectEqual(result, mods); + } + + // Set + { + const mods: Mods = .{ .alt = true }; + const result = mods.translation(.true); + try testing.expectEqual(Mods{}, result); + } + + // Set but disabled + { + const mods: Mods = .{ .alt = true }; + const result = mods.translation(.false); + try testing.expectEqual(result, mods); + } + + // Set wrong side + { + const mods: Mods = .{ .alt = true, .sides = .{ .alt = .right } }; + const result = mods.translation(.left); + try testing.expectEqual(result, mods); + } + { + const mods: Mods = .{ .alt = true, .sides = .{ .alt = .left } }; + const result = mods.translation(.right); + try testing.expectEqual(result, mods); + } + + // Set with other mods + { + const mods: Mods = .{ .alt = true, .shift = true }; + const result = mods.translation(.true); + try testing.expectEqual(Mods{ .shift = true }, result); + } + } +}; + +/// Modifier remapping. See `key-remap` in Config.zig for detailed docs. +pub const RemapSet = struct { + /// Available mappings. + map: std.ArrayHashMapUnmanaged(Mods, Mods), + + /// The mask of remapped modifiers that can be used to quickly + /// check if some input mods need remapping. + mask: Mods.Backing, +};