mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #2406 from ghostty-org/gtk-key
gtk: handle key press events at the window level if necessary
This commit is contained in:
@ -1268,7 +1268,7 @@ fn gtkMouseDown(
|
|||||||
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
||||||
|
|
||||||
const button = translateMouseButton(c.gtk_gesture_single_get_current_button(@ptrCast(gesture)));
|
const button = translateMouseButton(c.gtk_gesture_single_get_current_button(@ptrCast(gesture)));
|
||||||
const mods = translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
|
|
||||||
// If we don't have focus, grab it.
|
// If we don't have focus, grab it.
|
||||||
const gl_widget = @as(*c.GtkWidget, @ptrCast(self.gl_area));
|
const gl_widget = @as(*c.GtkWidget, @ptrCast(self.gl_area));
|
||||||
@ -1300,7 +1300,7 @@ fn gtkMouseUp(
|
|||||||
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
||||||
|
|
||||||
const button = translateMouseButton(c.gtk_gesture_single_get_current_button(@ptrCast(gesture)));
|
const button = translateMouseButton(c.gtk_gesture_single_get_current_button(@ptrCast(gesture)));
|
||||||
const mods = translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
|
|
||||||
const self = userdataSelf(ud.?);
|
const self = userdataSelf(ud.?);
|
||||||
_ = self.core_surface.mouseButtonCallback(.release, button, mods) catch |err| {
|
_ = self.core_surface.mouseButtonCallback(.release, button, mods) catch |err| {
|
||||||
@ -1342,7 +1342,7 @@ fn gtkMouseMotion(
|
|||||||
// Get our modifiers
|
// Get our modifiers
|
||||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec));
|
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec));
|
||||||
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
||||||
const mods = translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
|
|
||||||
self.core_surface.cursorPosCallback(self.cursor_pos, mods) catch |err| {
|
self.core_surface.cursorPosCallback(self.cursor_pos, mods) catch |err| {
|
||||||
log.err("error in cursor pos callback err={}", .{err});
|
log.err("error in cursor pos callback err={}", .{err});
|
||||||
@ -1359,7 +1359,7 @@ fn gtkMouseLeave(
|
|||||||
// Get our modifiers
|
// Get our modifiers
|
||||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec));
|
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec));
|
||||||
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
const gtk_mods = c.gdk_event_get_modifier_state(event);
|
||||||
const mods = translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
self.core_surface.cursorPosCallback(.{ .x = -1, .y = -1 }, mods) catch |err| {
|
self.core_surface.cursorPosCallback(.{ .x = -1, .y = -1 }, mods) catch |err| {
|
||||||
log.err("error in cursor pos callback err={}", .{err});
|
log.err("error in cursor pos callback err={}", .{err});
|
||||||
return;
|
return;
|
||||||
@ -1395,7 +1395,14 @@ fn gtkKeyPressed(
|
|||||||
gtk_mods: c.GdkModifierType,
|
gtk_mods: c.GdkModifierType,
|
||||||
ud: ?*anyopaque,
|
ud: ?*anyopaque,
|
||||||
) callconv(.C) c.gboolean {
|
) callconv(.C) c.gboolean {
|
||||||
return if (keyEvent(.press, ec_key, keyval, keycode, gtk_mods, ud)) 1 else 0;
|
const self = userdataSelf(ud.?);
|
||||||
|
return if (self.keyEvent(
|
||||||
|
.press,
|
||||||
|
ec_key,
|
||||||
|
keyval,
|
||||||
|
keycode,
|
||||||
|
gtk_mods,
|
||||||
|
)) 1 else 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkKeyReleased(
|
fn gtkKeyReleased(
|
||||||
@ -1405,7 +1412,14 @@ fn gtkKeyReleased(
|
|||||||
state: c.GdkModifierType,
|
state: c.GdkModifierType,
|
||||||
ud: ?*anyopaque,
|
ud: ?*anyopaque,
|
||||||
) callconv(.C) c.gboolean {
|
) callconv(.C) c.gboolean {
|
||||||
return if (keyEvent(.release, ec_key, keyval, keycode, state, ud)) 1 else 0;
|
const self = userdataSelf(ud.?);
|
||||||
|
return if (self.keyEvent(
|
||||||
|
.release,
|
||||||
|
ec_key,
|
||||||
|
keyval,
|
||||||
|
keycode,
|
||||||
|
state,
|
||||||
|
)) 1 else 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key press event. This is where we do ALL of our key handling,
|
/// Key press event. This is where we do ALL of our key handling,
|
||||||
@ -1432,64 +1446,26 @@ fn gtkKeyReleased(
|
|||||||
/// Note we ALSO have an IMContext attached directly to the widget
|
/// Note we ALSO have an IMContext attached directly to the widget
|
||||||
/// which can emit preedit and commit callbacks. But, if we're not
|
/// which can emit preedit and commit callbacks. But, if we're not
|
||||||
/// in a keypress, we let those automatically work.
|
/// in a keypress, we let those automatically work.
|
||||||
fn keyEvent(
|
pub fn keyEvent(
|
||||||
|
self: *Surface,
|
||||||
action: input.Action,
|
action: input.Action,
|
||||||
ec_key: *c.GtkEventControllerKey,
|
ec_key: *c.GtkEventControllerKey,
|
||||||
keyval: c.guint,
|
keyval: c.guint,
|
||||||
keycode: c.guint,
|
keycode: c.guint,
|
||||||
gtk_mods: c.GdkModifierType,
|
gtk_mods: c.GdkModifierType,
|
||||||
ud: ?*anyopaque,
|
|
||||||
) bool {
|
) bool {
|
||||||
const self = userdataSelf(ud.?);
|
|
||||||
const keyval_unicode = c.gdk_keyval_to_unicode(keyval);
|
const keyval_unicode = c.gdk_keyval_to_unicode(keyval);
|
||||||
const event = c.gtk_event_controller_get_current_event(@ptrCast(ec_key));
|
const event = c.gtk_event_controller_get_current_event(
|
||||||
const display = c.gtk_widget_get_display(@ptrCast(self.gl_area));
|
@ptrCast(ec_key),
|
||||||
|
) orelse return false;
|
||||||
|
|
||||||
// Get the unshifted unicode value of the keyval. This is used
|
// Get the unshifted unicode value of the keyval. This is used
|
||||||
// by the Kitty keyboard protocol.
|
// by the Kitty keyboard protocol.
|
||||||
const keyval_unicode_unshifted: u21 = unshifted: {
|
const keyval_unicode_unshifted: u21 = gtk_key.keyvalUnicodeUnshifted(
|
||||||
// We need to get the currently active keyboard layout so we know
|
@ptrCast(self.gl_area),
|
||||||
// what group to look at.
|
event,
|
||||||
const layout = c.gdk_key_event_get_layout(@ptrCast(event));
|
keycode,
|
||||||
|
);
|
||||||
// Get all the possible keyboard mappings for this keycode. A keycode
|
|
||||||
// is the physical key pressed.
|
|
||||||
var keys: [*]c.GdkKeymapKey = undefined;
|
|
||||||
var keyvals: [*]c.guint = undefined;
|
|
||||||
var n_keys: c_int = 0;
|
|
||||||
if (c.gdk_display_map_keycode(
|
|
||||||
display,
|
|
||||||
keycode,
|
|
||||||
@ptrCast(&keys),
|
|
||||||
@ptrCast(&keyvals),
|
|
||||||
&n_keys,
|
|
||||||
) == 0) break :unshifted 0;
|
|
||||||
|
|
||||||
defer c.g_free(keys);
|
|
||||||
defer c.g_free(keyvals);
|
|
||||||
|
|
||||||
// debugging:
|
|
||||||
// log.debug("layout={}", .{layout});
|
|
||||||
// for (0..@intCast(n_keys)) |i| {
|
|
||||||
// log.debug("keymap key={} codepoint={x}", .{
|
|
||||||
// keys[i],
|
|
||||||
// c.gdk_keyval_to_unicode(keyvals[i]),
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (0..@intCast(n_keys)) |i| {
|
|
||||||
if (keys[i].group == layout and
|
|
||||||
keys[i].level == 0)
|
|
||||||
{
|
|
||||||
break :unshifted std.math.cast(
|
|
||||||
u21,
|
|
||||||
c.gdk_keyval_to_unicode(keyvals[i]),
|
|
||||||
) orelse 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break :unshifted 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// We always reset our committed text when ending a keypress so that
|
// We always reset our committed text when ending a keypress so that
|
||||||
// future keypresses don't think we have a commit event.
|
// future keypresses don't think we have a commit event.
|
||||||
@ -1549,44 +1525,20 @@ fn keyEvent(
|
|||||||
if (entry.native == keycode) break :keycode entry.key;
|
if (entry.native == keycode) break :keycode entry.key;
|
||||||
} else .invalid;
|
} else .invalid;
|
||||||
|
|
||||||
const mods = mods: {
|
// Get our modifier for the event
|
||||||
const device = c.gdk_event_get_device(event);
|
const mods: input.Mods = gtk_key.eventMods(
|
||||||
|
@ptrCast(self.gl_area),
|
||||||
var mods = if (self.app.x11_xkb) |xkb|
|
event,
|
||||||
// Add any modifier state events from Xkb if we have them (X11
|
physical_key,
|
||||||
// only). Null back from the Xkb call means there was no modifier
|
gtk_mods,
|
||||||
// event to read. This likely means that the key event did not
|
if (self.app.x11_xkb) |*xkb| xkb else null,
|
||||||
// result in a modifier change and we can safely rely on the GDK
|
);
|
||||||
// state.
|
|
||||||
xkb.modifier_state_from_notify(display) orelse translateMods(gtk_mods)
|
|
||||||
else
|
|
||||||
// On Wayland, we have to use the GDK device because the mods sent
|
|
||||||
// to this event do not have the modifier key applied if it was
|
|
||||||
// presssed (i.e. left control).
|
|
||||||
translateMods(c.gdk_device_get_modifier_state(device));
|
|
||||||
|
|
||||||
mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1;
|
|
||||||
|
|
||||||
switch (physical_key) {
|
|
||||||
.left_shift => mods.sides.shift = .left,
|
|
||||||
.right_shift => mods.sides.shift = .right,
|
|
||||||
.left_control => mods.sides.ctrl = .left,
|
|
||||||
.right_control => mods.sides.ctrl = .right,
|
|
||||||
.left_alt => mods.sides.alt = .left,
|
|
||||||
.right_alt => mods.sides.alt = .right,
|
|
||||||
.left_super => mods.sides.super = .left,
|
|
||||||
.right_super => mods.sides.super = .right,
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
break :mods mods;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get our consumed modifiers
|
// Get our consumed modifiers
|
||||||
const consumed_mods: input.Mods = consumed: {
|
const consumed_mods: input.Mods = consumed: {
|
||||||
const raw = c.gdk_key_event_get_consumed_modifiers(event);
|
const raw = c.gdk_key_event_get_consumed_modifiers(event);
|
||||||
const masked = raw & c.GDK_MODIFIER_MASK;
|
const masked = raw & c.GDK_MODIFIER_MASK;
|
||||||
break :consumed translateMods(masked);
|
break :consumed gtk_key.translateMods(masked);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we're not in a dead key state, we want to translate our text
|
// If we're not in a dead key state, we want to translate our text
|
||||||
@ -1908,18 +1860,6 @@ 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;
|
|
||||||
|
|
||||||
// Lock is dependent on the X settings but we just assume caps lock.
|
|
||||||
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn present(self: *Surface) void {
|
pub fn present(self: *Surface) void {
|
||||||
if (self.container.window()) |window| {
|
if (self.container.window()) |window| {
|
||||||
if (self.container.tab()) |tab| {
|
if (self.container.tab()) |tab| {
|
||||||
|
@ -21,6 +21,7 @@ const Surface = @import("Surface.zig");
|
|||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
|
const gtk_key = @import("key.zig");
|
||||||
const Notebook = @import("notebook.zig").Notebook;
|
const Notebook = @import("notebook.zig").Notebook;
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
@ -255,10 +256,18 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
// If we are in fullscreen mode, new windows start fullscreen.
|
// If we are in fullscreen mode, new windows start fullscreen.
|
||||||
if (app.config.fullscreen) c.gtk_window_fullscreen(self.window);
|
if (app.config.fullscreen) c.gtk_window_fullscreen(self.window);
|
||||||
|
|
||||||
|
// We register a key event controller with the window so
|
||||||
|
// we can catch key events when our surface may not be
|
||||||
|
// focused (i.e. when the libadw tab overview is shown).
|
||||||
|
const ec_key_press = c.gtk_event_controller_key_new();
|
||||||
|
errdefer c.g_object_unref(ec_key_press);
|
||||||
|
c.gtk_widget_add_controller(window, ec_key_press);
|
||||||
|
|
||||||
// All of our events
|
// All of our events
|
||||||
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(>kKeyPressed), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
|
||||||
// Our actions for the menu
|
// Our actions for the menu
|
||||||
initActions(self);
|
initActions(self);
|
||||||
@ -662,6 +671,40 @@ fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
|||||||
alloc.destroy(self);
|
alloc.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gtkKeyPressed(
|
||||||
|
ec_key: *c.GtkEventControllerKey,
|
||||||
|
keyval: c.guint,
|
||||||
|
keycode: c.guint,
|
||||||
|
gtk_mods: c.GdkModifierType,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) c.gboolean {
|
||||||
|
const self = userdataSelf(ud.?);
|
||||||
|
|
||||||
|
// We only process window-level events currently for the tab
|
||||||
|
// overview. This is primarily defensive programming because
|
||||||
|
// I'm not 100% certain how our logic below will interact with
|
||||||
|
// other parts of the application but I know for sure we must
|
||||||
|
// handle this during the tab overview.
|
||||||
|
//
|
||||||
|
// If someone can confidently show or explain that this is not
|
||||||
|
// necessary, please remove this check.
|
||||||
|
if (comptime adwaita.versionAtLeast(1, 4, 0)) {
|
||||||
|
if (self.tab_overview) |tab_overview_widget| {
|
||||||
|
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||||
|
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const surface = self.app.core_app.focusedSurface() orelse return 0;
|
||||||
|
return if (surface.rt_surface.keyEvent(
|
||||||
|
.press,
|
||||||
|
ec_key,
|
||||||
|
keyval,
|
||||||
|
keycode,
|
||||||
|
gtk_mods,
|
||||||
|
)) 1 else 0;
|
||||||
|
}
|
||||||
|
|
||||||
fn gtkActionAbout(
|
fn gtkActionAbout(
|
||||||
_: *c.GSimpleAction,
|
_: *c.GSimpleAction,
|
||||||
_: *c.GVariant,
|
_: *c.GVariant,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
const x11 = @import("x11.zig");
|
||||||
|
|
||||||
/// Returns a GTK accelerator string from a trigger.
|
/// Returns a GTK accelerator string from a trigger.
|
||||||
pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 {
|
pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 {
|
||||||
@ -47,6 +48,102 @@ pub fn translateMods(state: c.GdkModifierType) input.Mods {
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the unshifted unicode value of the keyval. This is used
|
||||||
|
// by the Kitty keyboard protocol.
|
||||||
|
pub fn keyvalUnicodeUnshifted(
|
||||||
|
widget: *c.GtkWidget,
|
||||||
|
event: *c.GdkEvent,
|
||||||
|
keycode: c.guint,
|
||||||
|
) u21 {
|
||||||
|
const display = c.gtk_widget_get_display(widget);
|
||||||
|
|
||||||
|
// We need to get the currently active keyboard layout so we know
|
||||||
|
// what group to look at.
|
||||||
|
const layout = c.gdk_key_event_get_layout(@ptrCast(event));
|
||||||
|
|
||||||
|
// Get all the possible keyboard mappings for this keycode. A keycode
|
||||||
|
// is the physical key pressed.
|
||||||
|
var keys: [*]c.GdkKeymapKey = undefined;
|
||||||
|
var keyvals: [*]c.guint = undefined;
|
||||||
|
var n_keys: c_int = 0;
|
||||||
|
if (c.gdk_display_map_keycode(
|
||||||
|
display,
|
||||||
|
keycode,
|
||||||
|
@ptrCast(&keys),
|
||||||
|
@ptrCast(&keyvals),
|
||||||
|
&n_keys,
|
||||||
|
) == 0) return 0;
|
||||||
|
|
||||||
|
defer c.g_free(keys);
|
||||||
|
defer c.g_free(keyvals);
|
||||||
|
|
||||||
|
// debugging:
|
||||||
|
// log.debug("layout={}", .{layout});
|
||||||
|
// for (0..@intCast(n_keys)) |i| {
|
||||||
|
// log.debug("keymap key={} codepoint={x}", .{
|
||||||
|
// keys[i],
|
||||||
|
// c.gdk_keyval_to_unicode(keyvals[i]),
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (0..@intCast(n_keys)) |i| {
|
||||||
|
if (keys[i].group == layout and
|
||||||
|
keys[i].level == 0)
|
||||||
|
{
|
||||||
|
return std.math.cast(
|
||||||
|
u21,
|
||||||
|
c.gdk_keyval_to_unicode(keyvals[i]),
|
||||||
|
) orelse 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mods to use a key event from a GTK event.
|
||||||
|
/// This requires a lot of context because the GdkEvent
|
||||||
|
/// doesn't contain enough on its own.
|
||||||
|
pub fn eventMods(
|
||||||
|
widget: *c.GtkWidget,
|
||||||
|
event: *c.GdkEvent,
|
||||||
|
physical_key: input.Key,
|
||||||
|
gtk_mods: c.GdkModifierType,
|
||||||
|
x11_xkb: ?*x11.Xkb,
|
||||||
|
) input.Mods {
|
||||||
|
const device = c.gdk_event_get_device(event);
|
||||||
|
const display = c.gtk_widget_get_display(widget);
|
||||||
|
|
||||||
|
var mods = if (x11_xkb) |xkb|
|
||||||
|
// Add any modifier state events from Xkb if we have them (X11
|
||||||
|
// only). Null back from the Xkb call means there was no modifier
|
||||||
|
// event to read. This likely means that the key event did not
|
||||||
|
// result in a modifier change and we can safely rely on the GDK
|
||||||
|
// state.
|
||||||
|
xkb.modifier_state_from_notify(display) orelse
|
||||||
|
translateMods(gtk_mods)
|
||||||
|
else
|
||||||
|
// On Wayland, we have to use the GDK device because the mods sent
|
||||||
|
// to this event do not have the modifier key applied if it was
|
||||||
|
// presssed (i.e. left control).
|
||||||
|
translateMods(c.gdk_device_get_modifier_state(device));
|
||||||
|
|
||||||
|
mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1;
|
||||||
|
|
||||||
|
switch (physical_key) {
|
||||||
|
.left_shift => mods.sides.shift = .left,
|
||||||
|
.right_shift => mods.sides.shift = .right,
|
||||||
|
.left_control => mods.sides.ctrl = .left,
|
||||||
|
.right_control => mods.sides.ctrl = .right,
|
||||||
|
.left_alt => mods.sides.alt = .left,
|
||||||
|
.right_alt => mods.sides.alt = .right,
|
||||||
|
.left_super => mods.sides.super = .left,
|
||||||
|
.right_super => mods.sides.super = .right,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an input key from a keyval or null if we don't have a mapping.
|
/// Returns an input key from a keyval or null if we don't have a mapping.
|
||||||
pub fn keyFromKeyval(keyval: c.guint) ?input.Key {
|
pub fn keyFromKeyval(keyval: c.guint) ?input.Key {
|
||||||
for (keymap) |entry| {
|
for (keymap) |entry| {
|
||||||
|
Reference in New Issue
Block a user