mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #285 from mitchellh/left-right-alt
macos: option-as-alt can specify left or right option key
This commit is contained in:
@ -83,6 +83,10 @@ typedef enum {
|
|||||||
GHOSTTY_MODS_SUPER = 1 << 3,
|
GHOSTTY_MODS_SUPER = 1 << 3,
|
||||||
GHOSTTY_MODS_CAPS = 1 << 4,
|
GHOSTTY_MODS_CAPS = 1 << 4,
|
||||||
GHOSTTY_MODS_NUM = 1 << 5,
|
GHOSTTY_MODS_NUM = 1 << 5,
|
||||||
|
GHOSTTY_MODS_SHIFT_RIGHT = 1 << 6,
|
||||||
|
GHOSTTY_MODS_CTRL_RIGHT = 1 << 7,
|
||||||
|
GHOSTTY_MODS_ALT_RIGHT = 1 << 8,
|
||||||
|
GHOSTTY_MODS_SUPER_RIGHT = 1 << 9,
|
||||||
} ghostty_input_mods_e;
|
} ghostty_input_mods_e;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -480,12 +480,21 @@ 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(.shift)) { mods |= GHOSTTY_MODS_SHIFT.rawValue }
|
||||||
if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue }
|
if (flags.contains(.control)) { mods |= GHOSTTY_MODS_CTRL.rawValue }
|
||||||
if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue }
|
if (flags.contains(.option)) { mods |= GHOSTTY_MODS_ALT.rawValue }
|
||||||
if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue }
|
if (flags.contains(.command)) { mods |= GHOSTTY_MODS_SUPER.rawValue }
|
||||||
if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue }
|
if (flags.contains(.capsLock)) { mods |= GHOSTTY_MODS_CAPS.rawValue }
|
||||||
|
|
||||||
|
// Handle sided input. We can't tell that both are pressed in the
|
||||||
|
// Ghostty structure but thats okay -- we don't use that information.
|
||||||
|
let rawFlags = flags.rawValue
|
||||||
|
if (rawFlags & UInt(NX_DEVICERSHIFTKEYMASK) != 0) { mods |= GHOSTTY_MODS_SHIFT_RIGHT.rawValue }
|
||||||
|
if (rawFlags & UInt(NX_DEVICERCTLKEYMASK) != 0) { mods |= GHOSTTY_MODS_CTRL_RIGHT.rawValue }
|
||||||
|
if (rawFlags & UInt(NX_DEVICERALTKEYMASK) != 0) { mods |= GHOSTTY_MODS_ALT_RIGHT.rawValue }
|
||||||
|
if (rawFlags & UInt(NX_DEVICERCMDKEYMASK) != 0) { mods |= GHOSTTY_MODS_SUPER_RIGHT.rawValue }
|
||||||
|
|
||||||
return ghostty_input_mods_e(mods)
|
return ghostty_input_mods_e(mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ const DerivedConfig = struct {
|
|||||||
confirm_close_surface: bool,
|
confirm_close_surface: bool,
|
||||||
mouse_interval: u64,
|
mouse_interval: u64,
|
||||||
macos_non_native_fullscreen: bool,
|
macos_non_native_fullscreen: bool,
|
||||||
macos_option_as_alt: bool,
|
macos_option_as_alt: configpkg.OptionAsAlt,
|
||||||
|
|
||||||
pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig {
|
pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig {
|
||||||
var arena = ArenaAllocator.init(alloc_gpa);
|
var arena = ArenaAllocator.init(alloc_gpa);
|
||||||
@ -1042,9 +1042,11 @@ pub fn charCallback(
|
|||||||
// On macOS, we have to opt-in to using alt because option
|
// On macOS, we have to opt-in to using alt because option
|
||||||
// by default is a unicode character sequence.
|
// by default is a unicode character sequence.
|
||||||
if (comptime builtin.target.isDarwin()) {
|
if (comptime builtin.target.isDarwin()) {
|
||||||
if (!self.config.macos_option_as_alt) {
|
switch (self.config.macos_option_as_alt) {
|
||||||
log.debug("macos_option_as_alt disabled, not sending esc prefix", .{});
|
.false => break :alt,
|
||||||
break :alt;
|
.true => {},
|
||||||
|
.left => if (mods.sides.alt != .left) break :alt,
|
||||||
|
.right => if (mods.sides.alt != .right) break :alt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1094,12 +1096,7 @@ pub fn keyCallback(
|
|||||||
if (action != .press and action != .repeat) return false;
|
if (action != .press and action != .repeat) return false;
|
||||||
|
|
||||||
// Mods for bindings never include caps/num lock.
|
// Mods for bindings never include caps/num lock.
|
||||||
const binding_mods = mods: {
|
const binding_mods = mods.binding();
|
||||||
var binding_mods = mods;
|
|
||||||
binding_mods.caps_lock = false;
|
|
||||||
binding_mods.num_lock = false;
|
|
||||||
break :mods binding_mods;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if we're processing a binding first. If so, that negates
|
// Check if we're processing a binding first. If so, that negates
|
||||||
// any further key processing.
|
// any further key processing.
|
||||||
@ -1154,8 +1151,8 @@ pub fn keyCallback(
|
|||||||
.set_other => if (!modify_other_keys) continue,
|
.set_other => if (!modify_other_keys) continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mods_int: u8 = @bitCast(binding_mods);
|
const mods_int = binding_mods.int();
|
||||||
const entry_mods_int: u8 = @bitCast(entry.mods);
|
const entry_mods_int = entry.mods.int();
|
||||||
if (entry_mods_int == 0) {
|
if (entry_mods_int == 0) {
|
||||||
if (mods_int != 0 and !entry.mods_empty_is_any) continue;
|
if (mods_int != 0 and !entry.mods_empty_is_any) continue;
|
||||||
// mods are either empty, or empty means any so we allow it.
|
// mods are either empty, or empty means any so we allow it.
|
||||||
@ -1191,8 +1188,8 @@ pub fn keyCallback(
|
|||||||
|
|
||||||
// Handle non-printables
|
// Handle non-printables
|
||||||
const char: u8 = char: {
|
const char: u8 = char: {
|
||||||
const mods_int: u8 = @bitCast(unalt_mods);
|
const mods_int = unalt_mods.int();
|
||||||
const ctrl_only: u8 = @bitCast(input.Mods{ .ctrl = true });
|
const ctrl_only = (input.Mods{ .ctrl = true }).int();
|
||||||
|
|
||||||
// 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. The best table I've found for
|
// we convert to a non-printable. The best table I've found for
|
||||||
|
@ -394,8 +394,16 @@ pub const Surface = struct {
|
|||||||
// then we strip the alt modifier from the mods for translation.
|
// then we strip the alt modifier from the mods for translation.
|
||||||
const translate_mods = translate_mods: {
|
const translate_mods = translate_mods: {
|
||||||
var translate_mods = mods;
|
var translate_mods = mods;
|
||||||
if (self.app.config.@"macos-option-as-alt")
|
switch (self.app.config.@"macos-option-as-alt") {
|
||||||
translate_mods.alt = false;
|
.false => {},
|
||||||
|
.true => translate_mods.alt = false,
|
||||||
|
.left => if (mods.sides.alt == .left) {
|
||||||
|
translate_mods.alt = false;
|
||||||
|
},
|
||||||
|
.right => if (mods.sides.alt == .right) {
|
||||||
|
translate_mods.alt = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
break :translate_mods translate_mods;
|
break :translate_mods translate_mods;
|
||||||
};
|
};
|
||||||
@ -660,7 +668,10 @@ pub const CAPI = struct {
|
|||||||
surface.keyCallback(
|
surface.keyCallback(
|
||||||
action,
|
action,
|
||||||
keycode,
|
keycode,
|
||||||
@bitCast(@as(u8, @truncate(@as(c_uint, @bitCast(c_mods))))),
|
@bitCast(@as(
|
||||||
|
input.Mods.Backing,
|
||||||
|
@truncate(@as(c_uint, @bitCast(c_mods))),
|
||||||
|
)),
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err("error processing key event err={}", .{err});
|
log.err("error processing key event err={}", .{err});
|
||||||
return;
|
return;
|
||||||
@ -684,7 +695,10 @@ 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.Backing,
|
||||||
|
@truncate(@as(c_uint, @bitCast(mods))),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,7 +618,12 @@ pub const Surface = struct {
|
|||||||
const core_win = window.getUserPointer(CoreSurface) orelse return;
|
const core_win = window.getUserPointer(CoreSurface) orelse return;
|
||||||
|
|
||||||
// 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: input.Mods = .{
|
||||||
|
.shift = glfw_mods.shift,
|
||||||
|
.ctrl = glfw_mods.control,
|
||||||
|
.alt = glfw_mods.alt,
|
||||||
|
.super = glfw_mods.super,
|
||||||
|
};
|
||||||
const action: input.Action = switch (glfw_action) {
|
const action: input.Action = switch (glfw_action) {
|
||||||
.release => .release,
|
.release => .release,
|
||||||
.press => .press,
|
.press => .press,
|
||||||
@ -843,7 +848,12 @@ 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: input.Mods = .{
|
||||||
|
.shift = glfw_mods.shift,
|
||||||
|
.ctrl = glfw_mods.control,
|
||||||
|
.alt = glfw_mods.alt,
|
||||||
|
.super = glfw_mods.super,
|
||||||
|
};
|
||||||
const button: input.MouseButton = switch (glfw_button) {
|
const button: input.MouseButton = switch (glfw_button) {
|
||||||
.left => .left,
|
.left => .left,
|
||||||
.right => .right,
|
.right => .right,
|
||||||
|
@ -249,7 +249,7 @@ pub const Config = struct {
|
|||||||
/// (i.e. alt+ctrl+a).
|
/// (i.e. alt+ctrl+a).
|
||||||
///
|
///
|
||||||
/// This does not work with GLFW builds.
|
/// This does not work with GLFW builds.
|
||||||
@"macos-option-as-alt": bool = false,
|
@"macos-option-as-alt": OptionAsAlt = .false,
|
||||||
|
|
||||||
/// This is set by the CLI parser for deinit.
|
/// This is set by the CLI parser for deinit.
|
||||||
_arena: ?ArenaAllocator = null,
|
_arena: ?ArenaAllocator = null,
|
||||||
@ -1036,6 +1036,14 @@ fn equal(comptime T: type, old: T, new: T) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Valid values for macos-option-as-alt.
|
||||||
|
pub const OptionAsAlt = enum {
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
|
||||||
/// Color represents a color using RGB.
|
/// Color represents a color using RGB.
|
||||||
pub const Color = struct {
|
pub const Color = struct {
|
||||||
r: u8,
|
r: u8,
|
||||||
|
@ -293,10 +293,10 @@ pub const Trigger = struct {
|
|||||||
physical: bool = false,
|
physical: bool = false,
|
||||||
|
|
||||||
/// Returns a hash code that can be used to uniquely identify this trigger.
|
/// Returns a hash code that can be used to uniquely identify this trigger.
|
||||||
pub fn hash(self: Binding) u64 {
|
pub fn hash(self: Trigger) u64 {
|
||||||
var hasher = std.hash.Wyhash.init(0);
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
std.hash.autoHash(&hasher, self.key);
|
std.hash.autoHash(&hasher, self.key);
|
||||||
std.hash.autoHash(&hasher, self.mods);
|
std.hash.autoHash(&hasher, self.mods.binding());
|
||||||
std.hash.autoHash(&hasher, self.physical);
|
std.hash.autoHash(&hasher, self.physical);
|
||||||
return hasher.final();
|
return hasher.final();
|
||||||
}
|
}
|
||||||
@ -306,7 +306,12 @@ pub const Trigger = struct {
|
|||||||
/// The use case is that this will be called on EVERY key input to look
|
/// The use case is that this will be called on EVERY key input to look
|
||||||
/// for an associated action so it must be fast.
|
/// for an associated action so it must be fast.
|
||||||
pub const Set = struct {
|
pub const Set = struct {
|
||||||
const HashMap = std.AutoHashMapUnmanaged(Trigger, Action);
|
const HashMap = std.HashMapUnmanaged(
|
||||||
|
Trigger,
|
||||||
|
Action,
|
||||||
|
Context,
|
||||||
|
std.hash_map.default_max_load_percentage,
|
||||||
|
);
|
||||||
|
|
||||||
/// The set of bindings.
|
/// The set of bindings.
|
||||||
bindings: HashMap = .{},
|
bindings: HashMap = .{},
|
||||||
@ -318,10 +323,14 @@ pub const Set = struct {
|
|||||||
|
|
||||||
/// Add a binding to the set. If the binding already exists then
|
/// Add a binding to the set. If the binding already exists then
|
||||||
/// this will overwrite it.
|
/// this will overwrite it.
|
||||||
pub fn put(self: *Set, alloc: Allocator, t: Trigger, action: Action) !void {
|
pub fn put(
|
||||||
|
self: *Set,
|
||||||
|
alloc: Allocator,
|
||||||
|
t: Trigger,
|
||||||
|
action: Action,
|
||||||
|
) !void {
|
||||||
// unbind should never go into the set, it should be handled prior
|
// unbind should never go into the set, it should be handled prior
|
||||||
assert(action != .unbind);
|
assert(action != .unbind);
|
||||||
|
|
||||||
try self.bindings.put(alloc, t, action);
|
try self.bindings.put(alloc, t, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +343,19 @@ pub const Set = struct {
|
|||||||
pub fn remove(self: *Set, t: Trigger) void {
|
pub fn remove(self: *Set, t: Trigger) void {
|
||||||
_ = self.bindings.remove(t);
|
_ = self.bindings.remove(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The hash map context for the set. This defines how the hash map
|
||||||
|
/// gets the hash key and checks for equality.
|
||||||
|
const Context = struct {
|
||||||
|
pub fn hash(ctx: Context, k: Trigger) u64 {
|
||||||
|
_ = ctx;
|
||||||
|
return k.hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(ctx: Context, a: Trigger, b: Trigger) bool {
|
||||||
|
return ctx.hash(a) == ctx.hash(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
test "parse: triggers" {
|
test "parse: triggers" {
|
||||||
|
@ -5,23 +5,55 @@ 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.Backing) {
|
||||||
|
pub const Backing = u16;
|
||||||
|
|
||||||
shift: bool = false,
|
shift: bool = false,
|
||||||
ctrl: bool = false,
|
ctrl: bool = false,
|
||||||
alt: bool = false,
|
alt: bool = false,
|
||||||
super: bool = false,
|
super: bool = false,
|
||||||
caps_lock: bool = false,
|
caps_lock: bool = false,
|
||||||
num_lock: bool = false,
|
num_lock: bool = false,
|
||||||
_padding: u2 = 0,
|
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 { left, right };
|
||||||
|
|
||||||
|
/// Integer value of this struct.
|
||||||
|
pub fn int(self: Mods) Backing {
|
||||||
|
return @bitCast(self);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if no modifiers are set.
|
/// Returns true if no modifiers are set.
|
||||||
pub fn empty(self: Mods) bool {
|
pub fn empty(self: Mods) bool {
|
||||||
return @as(u8, @bitCast(self)) == 0;
|
return self.int() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if two mods are equal.
|
/// Returns true if two mods are equal.
|
||||||
pub fn equal(self: Mods, other: Mods) bool {
|
pub fn equal(self: Mods, other: Mods) bool {
|
||||||
return @as(u8, @bitCast(self)) == @as(u8, @bitCast(other));
|
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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the mods without locks set.
|
/// Returns the mods without locks set.
|
||||||
@ -35,10 +67,10 @@ pub const Mods = 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(Mods{})), @as(u8, 0b0));
|
try testing.expectEqual(@as(Backing, @bitCast(Mods{})), @as(Backing, 0b0));
|
||||||
try testing.expectEqual(
|
try testing.expectEqual(
|
||||||
@as(u8, @bitCast(Mods{ .shift = true })),
|
@as(Backing, @bitCast(Mods{ .shift = true })),
|
||||||
@as(u8, 0b0000_0001),
|
@as(Backing, 0b0000_0001),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user