mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #244 from mitchellh/alt-as-esc
Make Ghostty aware of left/right modifier keys
This commit is contained in:
@ -72,12 +72,16 @@ typedef int ghostty_input_scroll_mods_t;
|
||||
|
||||
typedef enum {
|
||||
GHOSTTY_MODS_NONE = 0,
|
||||
GHOSTTY_MODS_SHIFT = 1 << 0,
|
||||
GHOSTTY_MODS_CTRL = 1 << 1,
|
||||
GHOSTTY_MODS_ALT = 1 << 2,
|
||||
GHOSTTY_MODS_SUPER = 1 << 3,
|
||||
GHOSTTY_MODS_CAPS = 1 << 4,
|
||||
GHOSTTY_MODS_NUM = 1 << 5,
|
||||
GHOSTTY_MODS_LEFT_SHIFT = 1 << 0,
|
||||
GHOSTTY_MODS_RIGHT_SHIFT = 1 << 1,
|
||||
GHOSTTY_MODS_LEFT_CTRL = 1 << 2,
|
||||
GHOSTTY_MODS_RIGHT_CTRL = 1 << 3,
|
||||
GHOSTTY_MODS_LEFT_ALT = 1 << 4,
|
||||
GHOSTTY_MODS_RIGHT_ALT = 1 << 5,
|
||||
GHOSTTY_MODS_LEFT_SUPER = 1 << 6,
|
||||
GHOSTTY_MODS_RIGHT_SUPER = 1 << 7,
|
||||
GHOSTTY_MODS_CAPS = 1 << 8,
|
||||
GHOSTTY_MODS_NUM = 1 << 9,
|
||||
} ghostty_input_mods_e;
|
||||
|
||||
typedef enum {
|
||||
|
@ -491,10 +491,16 @@ extension Ghostty {
|
||||
|
||||
private static func translateFlags(_ flags: NSEvent.ModifierFlags) -> ghostty_input_mods_e {
|
||||
var mods: UInt32 = GHOSTTY_MODS_NONE.rawValue
|
||||
if (flags.contains(.shift)) { mods |= GHOSTTY_MODS_SHIFT.rawValue }
|
||||
if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue }
|
||||
if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue }
|
||||
if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue }
|
||||
|
||||
let rawFlags = flags.rawValue
|
||||
if (rawFlags & UInt(NX_DEVICELSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_LEFT_SHIFT.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_RIGHT_SHIFT.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICELCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_LEFT_CTRL.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_RIGHT_CTRL.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICELALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_LEFT_ALT.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICERALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_RIGHT_ALT.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICELCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_LEFT_SUPER.rawValue }
|
||||
if (rawFlags & UInt(NX_DEVICERCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_RIGHT_SUPER.rawValue }
|
||||
if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue }
|
||||
|
||||
return ghostty_input_mods_e(mods)
|
||||
|
@ -981,12 +981,12 @@ pub fn keyCallback(
|
||||
|
||||
// Handle non-printables
|
||||
const char: u8 = char: {
|
||||
const mods_int: u8 = @bitCast(mods);
|
||||
const ctrl_only: u8 = @bitCast(input.Mods{ .ctrl = true });
|
||||
const mods_int: input.Mods.Int = @bitCast(mods);
|
||||
const ctrl_only: input.Mods.Int = @bitCast(input.Mods{ .ctrl = .both });
|
||||
|
||||
// If we're only pressing control, check if this is a character
|
||||
// we convert to a non-printable.
|
||||
if (mods_int == ctrl_only) {
|
||||
if (mods_int & ctrl_only > 0) {
|
||||
const val: u8 = switch (key) {
|
||||
.left_bracket => 0x1B,
|
||||
.backslash => 0x1C,
|
||||
@ -1324,9 +1324,9 @@ fn mouseReport(
|
||||
|
||||
// X10 doesn't have modifiers
|
||||
if (self.io.terminal.modes.mouse_event != .x10) {
|
||||
if (mods.shift) acc += 4;
|
||||
if (mods.super) acc += 8;
|
||||
if (mods.ctrl) acc += 16;
|
||||
if (mods.shift.pressed()) acc += 4;
|
||||
if (mods.super.pressed()) acc += 8;
|
||||
if (mods.ctrl.pressed()) acc += 16;
|
||||
}
|
||||
|
||||
// Motion adds another bit
|
||||
@ -1478,13 +1478,13 @@ pub fn mouseButtonCallback(
|
||||
|
||||
// Always record our latest mouse state
|
||||
self.mouse.click_state[@intCast(@intFromEnum(button))] = action;
|
||||
self.mouse.mods = @bitCast(mods);
|
||||
self.mouse.mods = mods;
|
||||
|
||||
// Shift-click continues the previous mouse state if we have a selection.
|
||||
// cursorPosCallback will also do a mouse report so we don't need to do any
|
||||
// of the logic below.
|
||||
if (button == .left and action == .press) {
|
||||
if (mods.shift and self.mouse.left_click_count > 0) {
|
||||
if (mods.shift.pressed() and self.mouse.left_click_count > 0) {
|
||||
// Checking for selection requires the renderer state mutex which
|
||||
// sucks but this should be pretty rare of an event so it won't
|
||||
// cause a ton of contention.
|
||||
@ -1508,7 +1508,7 @@ pub fn mouseButtonCallback(
|
||||
// Report mouse events if enabled
|
||||
if (self.io.terminal.modes.mouse_event != .none) report: {
|
||||
// Shift overrides mouse "grabbing" in the window, taken from Kitty.
|
||||
if (mods.shift) break :report;
|
||||
if (mods.shift.pressed()) break :report;
|
||||
|
||||
// In any other mouse button scenario without shift pressed we
|
||||
// clear the selection since the underlying application can handle
|
||||
@ -1634,7 +1634,7 @@ pub fn cursorPosCallback(
|
||||
// Do a mouse report
|
||||
if (self.io.terminal.modes.mouse_event != .none) report: {
|
||||
// Shift overrides mouse "grabbing" in the window, taken from Kitty.
|
||||
if (self.mouse.mods.shift) break :report;
|
||||
if (self.mouse.mods.shift.pressed()) break :report;
|
||||
|
||||
// We use the first mouse button we find pressed in order to report
|
||||
// since the spec (afaict) does not say...
|
||||
|
@ -508,7 +508,7 @@ pub const CAPI = struct {
|
||||
action,
|
||||
key,
|
||||
unmapped_key,
|
||||
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(mods))))),
|
||||
@bitCast(@as(input.Mods.Int, @truncate(@as(c_uint, @bitCast(mods))))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -527,7 +527,7 @@ pub const CAPI = struct {
|
||||
surface.mouseButtonCallback(
|
||||
action,
|
||||
button,
|
||||
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(mods))))),
|
||||
@bitCast(@as(input.Mods.Int, @truncate(@as(c_uint, @bitCast(mods))))),
|
||||
);
|
||||
}
|
||||
|
||||
@ -545,7 +545,7 @@ pub const CAPI = struct {
|
||||
surface.scrollCallback(
|
||||
x,
|
||||
y,
|
||||
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(scroll_mods))))),
|
||||
@bitCast(@as(input.ScrollMods.Int, @truncate(@as(c_uint, @bitCast(scroll_mods))))),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -564,7 +564,7 @@ pub const Surface = struct {
|
||||
defer tracy.end();
|
||||
|
||||
// Convert our glfw types into our input types
|
||||
const mods: input.Mods = @bitCast(glfw_mods);
|
||||
const mods = convertMods(glfw_mods);
|
||||
const action: input.Action = switch (glfw_action) {
|
||||
.release => .release,
|
||||
.press => .press,
|
||||
@ -784,7 +784,7 @@ pub const Surface = struct {
|
||||
const core_win = window.getUserPointer(CoreSurface) orelse return;
|
||||
|
||||
// Convert glfw button to input button
|
||||
const mods: input.Mods = @bitCast(glfw_mods);
|
||||
const mods = convertMods(glfw_mods);
|
||||
const button: input.MouseButton = switch (glfw_button) {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
@ -806,4 +806,15 @@ pub const Surface = struct {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn convertMods(mods: glfw.Mods) input.Mods {
|
||||
return .{
|
||||
.shift = if (mods.shift) .both else .none,
|
||||
.ctrl = if (mods.control) .both else .none,
|
||||
.alt = if (mods.alt) .both else .none,
|
||||
.super = if (mods.super) .both else .none,
|
||||
.caps_lock = mods.caps_lock,
|
||||
.num_lock = mods.num_lock,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1230,10 +1230,10 @@ fn translateMouseButton(button: c.guint) input.MouseButton {
|
||||
|
||||
fn translateMods(state: c.GdkModifierType) input.Mods {
|
||||
var mods: input.Mods = .{};
|
||||
if (state & c.GDK_SHIFT_MASK != 0) mods.shift = true;
|
||||
if (state & c.GDK_CONTROL_MASK != 0) mods.ctrl = true;
|
||||
if (state & c.GDK_ALT_MASK != 0) mods.alt = true;
|
||||
if (state & c.GDK_SUPER_MASK != 0) mods.super = true;
|
||||
if (state & c.GDK_SHIFT_MASK != 0) mods.shift = .both;
|
||||
if (state & c.GDK_CONTROL_MASK != 0) mods.ctrl = .both;
|
||||
if (state & c.GDK_ALT_MASK != 0) mods.alt = .both;
|
||||
if (state & c.GDK_SUPER_MASK != 0) mods.super = .both;
|
||||
|
||||
// Lock is dependent on the X settings but we just assume caps lock.
|
||||
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
||||
|
102
src/config.zig
102
src/config.zig
@ -3,6 +3,7 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const apprt = @import("apprt.zig");
|
||||
const inputpkg = @import("input.zig");
|
||||
const terminal = @import("terminal/main.zig");
|
||||
const internal_os = @import("os/main.zig");
|
||||
@ -300,7 +301,7 @@ pub const Config = struct {
|
||||
// Add our default keybindings
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .space, .mods = .{ .super = true, .alt = true, .ctrl = true } },
|
||||
.{ .key = .space, .mods = .{ .super = .both, .alt = .both, .ctrl = .both } },
|
||||
.{ .reload_config = {} },
|
||||
);
|
||||
|
||||
@ -308,9 +309,9 @@ pub const Config = struct {
|
||||
// On macOS we default to super but Linux ctrl+shift since
|
||||
// ctrl+c is to kill the process.
|
||||
const mods: inputpkg.Mods = if (builtin.target.isDarwin())
|
||||
.{ .super = true }
|
||||
.{ .super = .both }
|
||||
else
|
||||
.{ .ctrl = true, .shift = true };
|
||||
.{ .ctrl = .both, .shift = .both };
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
@ -393,13 +394,13 @@ pub const Config = struct {
|
||||
// Dev Mode
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .shift = true, .super = true } },
|
||||
.{ .key = .down, .mods = .{ .shift = .both, .super = .both } },
|
||||
.{ .toggle_dev_mode = {} },
|
||||
);
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .j, .mods = ctrlOrSuper(.{ .shift = true }) },
|
||||
.{ .key = .j, .mods = ctrlOrSuper(.{ .shift = .both }) },
|
||||
.{ .write_scrollback_file = {} },
|
||||
);
|
||||
|
||||
@ -407,89 +408,89 @@ pub const Config = struct {
|
||||
if (comptime !builtin.target.isDarwin()) {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .n, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .n, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .new_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .w, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .close_surface = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .q, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .q, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .quit = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .f4, .mods = .{ .alt = true } },
|
||||
.{ .key = .f4, .mods = .{ .alt = .both } },
|
||||
.{ .close_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .t, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .t, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .new_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .left, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .previous_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .right, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .next_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .o, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .o, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .new_split = .right },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .e, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .key = .e, .mods = .{ .ctrl = .both, .shift = .both } },
|
||||
.{ .new_split = .down },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .key = .left_bracket, .mods = .{ .ctrl = .both, .super = .both } },
|
||||
.{ .goto_split = .previous },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .ctrl = true, .super = true } },
|
||||
.{ .key = .right_bracket, .mods = .{ .ctrl = .both, .super = .both } },
|
||||
.{ .goto_split = .next },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .up, .mods = .{ .ctrl = .both, .alt = .both } },
|
||||
.{ .goto_split = .top },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .down, .mods = .{ .ctrl = .both, .alt = .both } },
|
||||
.{ .goto_split = .bottom },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .left, .mods = .{ .ctrl = .both, .alt = .both } },
|
||||
.{ .goto_split = .left },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .ctrl = true, .alt = true } },
|
||||
.{ .key = .right, .mods = .{ .ctrl = .both, .alt = .both } },
|
||||
.{ .goto_split = .right },
|
||||
);
|
||||
|
||||
// Semantic prompts
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_up, .mods = .{ .shift = true } },
|
||||
.{ .key = .page_up, .mods = .{ .shift = .both } },
|
||||
.{ .jump_to_prompt = -1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .page_down, .mods = .{ .shift = true } },
|
||||
.{ .key = .page_down, .mods = .{ .shift = .both } },
|
||||
.{ .jump_to_prompt = 1 },
|
||||
);
|
||||
}
|
||||
@ -502,9 +503,9 @@ pub const Config = struct {
|
||||
// On macOS we default to super but everywhere else
|
||||
// is alt.
|
||||
const mods: inputpkg.Mods = if (builtin.target.isDarwin())
|
||||
.{ .super = true }
|
||||
.{ .super = .both }
|
||||
else
|
||||
.{ .alt = true };
|
||||
.{ .alt = .both };
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
@ -525,97 +526,97 @@ pub const Config = struct {
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .q, .mods = .{ .super = true } },
|
||||
.{ .key = .q, .mods = .{ .super = .both } },
|
||||
.{ .quit = {} },
|
||||
);
|
||||
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .k, .mods = .{ .super = true } },
|
||||
.{ .key = .k, .mods = .{ .super = .both } },
|
||||
.{ .clear_screen = {} },
|
||||
);
|
||||
|
||||
// Semantic prompts
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .up, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .jump_to_prompt = -1 },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .down, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .jump_to_prompt = 1 },
|
||||
);
|
||||
|
||||
// Mac windowing
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .n, .mods = .{ .super = true } },
|
||||
.{ .key = .n, .mods = .{ .super = .both } },
|
||||
.{ .new_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .super = true } },
|
||||
.{ .key = .w, .mods = .{ .super = .both } },
|
||||
.{ .close_surface = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .w, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .w, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .close_window = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .t, .mods = .{ .super = true } },
|
||||
.{ .key = .t, .mods = .{ .super = .both } },
|
||||
.{ .new_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .left_bracket, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .previous_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .right_bracket, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .next_tab = {} },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .d, .mods = .{ .super = true } },
|
||||
.{ .key = .d, .mods = .{ .super = .both } },
|
||||
.{ .new_split = .right },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .d, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .key = .d, .mods = .{ .super = .both, .shift = .both } },
|
||||
.{ .new_split = .down },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .super = true } },
|
||||
.{ .key = .left_bracket, .mods = .{ .super = .both } },
|
||||
.{ .goto_split = .previous },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .super = true } },
|
||||
.{ .key = .right_bracket, .mods = .{ .super = .both } },
|
||||
.{ .goto_split = .next },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .up, .mods = .{ .super = .both, .alt = .both } },
|
||||
.{ .goto_split = .top },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .down, .mods = .{ .super = .both, .alt = .both } },
|
||||
.{ .goto_split = .bottom },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .left, .mods = .{ .super = .both, .alt = .both } },
|
||||
.{ .goto_split = .left },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .key = .right, .mods = .{ .super = .both, .alt = .both } },
|
||||
.{ .goto_split = .right },
|
||||
);
|
||||
}
|
||||
@ -630,9 +631,9 @@ pub const Config = struct {
|
||||
fn ctrlOrSuper(mods: inputpkg.Mods) inputpkg.Mods {
|
||||
var copy = mods;
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
copy.super = true;
|
||||
copy.super = .both;
|
||||
} else {
|
||||
copy.ctrl = true;
|
||||
copy.ctrl = .both;
|
||||
}
|
||||
|
||||
return copy;
|
||||
@ -1206,7 +1207,18 @@ pub const Keybinds = struct {
|
||||
};
|
||||
errdefer if (copy) |v| alloc.free(v);
|
||||
|
||||
const binding = try inputpkg.Binding.parse(value);
|
||||
const binding = binding: {
|
||||
var binding = try inputpkg.Binding.parse(value);
|
||||
|
||||
// Unless we're on native macOS, we don't allow directional
|
||||
// keys, so we just remap them to "both".
|
||||
if (comptime !(builtin.target.isDarwin() and apprt.runtime == apprt.embedded)) {
|
||||
binding.trigger.mods = binding.trigger.mods.removeDirection();
|
||||
}
|
||||
|
||||
break :binding binding;
|
||||
};
|
||||
|
||||
switch (binding.action) {
|
||||
.unbind => self.set.remove(binding.trigger),
|
||||
else => try self.set.put(alloc, binding.trigger, binding.action),
|
||||
|
@ -3,6 +3,7 @@
|
||||
const Binding = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const key = @import("key.zig");
|
||||
@ -42,12 +43,44 @@ pub fn parse(input: []const u8) !Binding {
|
||||
// Check if its a modifier
|
||||
const modsInfo = @typeInfo(key.Mods).Struct;
|
||||
inline for (modsInfo.fields) |field| {
|
||||
if (field.type == bool) {
|
||||
if (std.mem.eql(u8, part, field.name)) {
|
||||
// Repeat not allowed
|
||||
if (@field(result.mods, field.name)) return Error.InvalidFormat;
|
||||
if (field.name[0] != '_') {
|
||||
if (std.mem.endsWith(u8, part, field.name)) {
|
||||
// Parse the directional modifier if it exists
|
||||
const side: key.Mods.Side = side: {
|
||||
if (std.mem.eql(u8, part, field.name))
|
||||
break :side .both;
|
||||
if (std.mem.eql(u8, part, "left_" ++ field.name))
|
||||
break :side .left;
|
||||
if (std.mem.eql(u8, part, "right_" ++ field.name))
|
||||
break :side .right;
|
||||
|
||||
return Error.InvalidFormat;
|
||||
};
|
||||
|
||||
switch (field.type) {
|
||||
bool => {
|
||||
// Can only be set once
|
||||
if (@field(result.mods, field.name))
|
||||
return Error.InvalidFormat;
|
||||
|
||||
// Can not be directional
|
||||
if (side != .both)
|
||||
return Error.InvalidFormat;
|
||||
|
||||
@field(result.mods, field.name) = true;
|
||||
},
|
||||
|
||||
key.Mods.Side => {
|
||||
// Can only be set once
|
||||
if (@field(result.mods, field.name).pressed())
|
||||
return Error.InvalidFormat;
|
||||
|
||||
@field(result.mods, field.name) = side;
|
||||
},
|
||||
|
||||
else => @compileError("invalid type"),
|
||||
}
|
||||
|
||||
@field(result.mods, field.name) = true;
|
||||
continue :loop;
|
||||
}
|
||||
}
|
||||
@ -336,23 +369,32 @@ test "parse: triggers" {
|
||||
// single modifier
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.mods = .{ .shift = .both },
|
||||
.key = .a,
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("shift+a=ignore"));
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .ctrl = true },
|
||||
.mods = .{ .ctrl = .both },
|
||||
.key = .a,
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("ctrl+a=ignore"));
|
||||
|
||||
// directional modifier
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = .left },
|
||||
.key = .a,
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
}, try parse("left_shift+a=ignore"));
|
||||
|
||||
// multiple modifier
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true, .ctrl = true },
|
||||
.mods = .{ .shift = .both, .ctrl = .both },
|
||||
.key = .a,
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
@ -361,7 +403,7 @@ test "parse: triggers" {
|
||||
// key can come before modifier
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.mods = .{ .shift = .both },
|
||||
.key = .a,
|
||||
},
|
||||
.action = .{ .ignore = {} },
|
||||
@ -370,7 +412,7 @@ test "parse: triggers" {
|
||||
// unmapped keys
|
||||
try testing.expectEqual(Binding{
|
||||
.trigger = .{
|
||||
.mods = .{ .shift = true },
|
||||
.mods = .{ .shift = .both },
|
||||
.key = .a,
|
||||
.unmapped = true,
|
||||
},
|
||||
@ -383,6 +425,9 @@ test "parse: triggers" {
|
||||
// repeated control
|
||||
try testing.expectError(Error.InvalidFormat, parse("shift+shift+a=ignore"));
|
||||
|
||||
// conflicting sides
|
||||
try testing.expectError(Error.InvalidFormat, parse("left_shift+right_shift+a=ignore"));
|
||||
|
||||
// multiple character
|
||||
try testing.expectError(Error.InvalidFormat, parse("a+b=ignore"));
|
||||
}
|
||||
|
@ -5,22 +5,54 @@ const Allocator = std.mem.Allocator;
|
||||
/// GLFW representation, but we use this generically.
|
||||
///
|
||||
/// IMPORTANT: Any changes here update include/ghostty.h
|
||||
pub const Mods = packed struct(u8) {
|
||||
shift: bool = false,
|
||||
ctrl: bool = false,
|
||||
alt: bool = false,
|
||||
super: bool = false,
|
||||
pub const Mods = packed struct(Mods.Int) {
|
||||
pub const Int = u10;
|
||||
|
||||
shift: Side = .none,
|
||||
ctrl: Side = .none,
|
||||
alt: Side = .none,
|
||||
super: Side = .none,
|
||||
caps_lock: bool = false,
|
||||
num_lock: bool = false,
|
||||
_padding: u2 = 0,
|
||||
|
||||
/// Keeps track of left/right press. A packed struct makes it easy
|
||||
/// to set as a bitmask and then check the individual values.
|
||||
pub const Side = enum(u2) {
|
||||
none = 0,
|
||||
left = 1,
|
||||
right = 2,
|
||||
|
||||
/// Note that while this should only be set for BOTH being set,
|
||||
/// this is semantically used to mean "any" for the purposes of
|
||||
/// keybindings. We do not allow keybindings to map to "both".
|
||||
both = 3,
|
||||
|
||||
/// Returns true if the key is pressed at all.
|
||||
pub fn pressed(self: Side) bool {
|
||||
return @intFromEnum(self) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Return the identical mods but with all directional configuration
|
||||
/// removed and all of it set to "both".
|
||||
pub fn removeDirection(self: Mods) Mods {
|
||||
return Mods{
|
||||
.shift = if (self.shift.pressed()) .both else .none,
|
||||
.ctrl = if (self.ctrl.pressed()) .both else .none,
|
||||
.alt = if (self.alt.pressed()) .both else .none,
|
||||
.super = if (self.super.pressed()) .both else .none,
|
||||
.caps_lock = self.caps_lock,
|
||||
.num_lock = self.num_lock,
|
||||
};
|
||||
}
|
||||
|
||||
// For our own understanding
|
||||
test {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(@as(u8, @bitCast(Mods{})), @as(u8, 0b0));
|
||||
try testing.expectEqual(@as(Int, @bitCast(Mods{})), @as(Int, 0b0));
|
||||
try testing.expectEqual(
|
||||
@as(u8, @bitCast(Mods{ .shift = true })),
|
||||
@as(u8, 0b0000_0001),
|
||||
@as(Int, @bitCast(Mods{ .shift = .left })),
|
||||
@as(Int, 0b0000_0001),
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -99,7 +131,7 @@ pub const Key = enum(c_int) {
|
||||
equal,
|
||||
left_bracket, // [
|
||||
right_bracket, // ]
|
||||
backslash, // /
|
||||
backslash, // \
|
||||
|
||||
// control
|
||||
up,
|
||||
|
@ -64,7 +64,9 @@ pub const MouseMomentum = enum(u3) {
|
||||
};
|
||||
|
||||
/// The bitmask for mods for scroll events.
|
||||
pub const ScrollMods = packed struct(u8) {
|
||||
pub const ScrollMods = packed struct(ScrollMods.Int) {
|
||||
pub const Int = u8;
|
||||
|
||||
/// True if this is a high-precision scroll event. For example, Apple
|
||||
/// devices such as Magic Mouse, trackpads, etc. are high-precision
|
||||
/// and send very detailed scroll events.
|
||||
@ -79,10 +81,10 @@ pub const ScrollMods = packed struct(u8) {
|
||||
// For our own understanding
|
||||
test {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(@as(u8, @bitCast(ScrollMods{})), @as(u8, 0b0));
|
||||
try testing.expectEqual(@as(Int, @bitCast(ScrollMods{})), @as(Int, 0b0));
|
||||
try testing.expectEqual(
|
||||
@as(u8, @bitCast(ScrollMods{ .precision = true })),
|
||||
@as(u8, 0b0000_0001),
|
||||
@as(Int, @bitCast(ScrollMods{ .precision = true })),
|
||||
@as(Int, 0b0000_0001),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user