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