mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-19 15:41:20 +00:00
input: move mods out to key_mods.zig
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
185
src/input/key_mods.zig
Normal file
185
src/input/key_mods.zig
Normal file
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user