Merge pull request #244 from mitchellh/alt-as-esc

Make Ghostty aware of left/right modifier keys
This commit is contained in:
Mitchell Hashimoto
2023-08-07 16:01:31 -07:00
committed by GitHub
10 changed files with 210 additions and 98 deletions

View File

@ -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 {

View File

@ -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)

View File

@ -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...

View File

@ -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))))),
);
}

View File

@ -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,
};
}
};

View File

@ -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;

View File

@ -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),

View File

@ -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"));
}

View File

@ -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,

View File

@ -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),
);
}
};