diff --git a/src/apprt.zig b/src/apprt.zig index f0c4c2ffe..e979588a9 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -64,5 +64,6 @@ pub const Runtime = enum { }; test { - @import("std").testing.refAllDecls(@This()); + _ = Runtime; + _ = runtime; } diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index f4e4c74a8..268da2ee4 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -5,4 +5,6 @@ pub const Surface = @import("gtk/Surface.zig"); test { @import("std").testing.refAllDecls(@This()); + + _ = @import("gtk/key.zig"); } diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index b5647427b..9e1b23556 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -15,6 +15,7 @@ const assert = std.debug.assert; const builtin = @import("builtin"); const glfw = @import("glfw"); const configpkg = @import("../../config.zig"); +const input = @import("../../input.zig"); const Config = configpkg.Config; const CoreApp = @import("../../App.zig"); const CoreSurface = @import("../../Surface.zig"); @@ -23,6 +24,7 @@ const Surface = @import("Surface.zig"); const Window = @import("Window.zig"); const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig"); const c = @import("c.zig"); +const key = @import("key.zig"); const log = std.log.scoped(.gtk); @@ -163,15 +165,19 @@ pub fn reloadConfig(self: *App) !?*const Config { // Update the existing config, be sure to clean up the old one. self.config.deinit(); self.config = config; - - // If there were errors, report them - self.updateConfigErrors() catch |err| { - log.warn("error handling configuration errors err={}", .{err}); + self.syncConfigChanges() catch |err| { + log.warn("error handling configuration changes err={}", .{err}); }; return &self.config; } +/// Call this anytime the configuration changes. +fn syncConfigChanges(self: *App) !void { + try self.updateConfigErrors(); + try self.syncActionAccelerators(); +} + /// This should be called whenever the configuration changes to update /// the state of our config errors window. This will show the window if /// there are new configuration errors and hide the window if the errors @@ -189,6 +195,29 @@ fn updateConfigErrors(self: *App) !void { } } +fn syncActionAccelerators(self: *App) !void { + try self.syncActionAccelerator("app.quit", .{ .quit = {} }); +} + +fn syncActionAccelerator( + self: *App, + gtk_action: [:0]const u8, + action: input.Binding.Action, +) !void { + const trigger = self.config.keybind.set.getTrigger(action) orelse return; + + // Build our accelerator string. + var buf: [256]u8 = undefined; + const accel = try key.accelFromTrigger(&buf, trigger) orelse return; + const accels = [_]?[*:0]const u8{ accel, null }; + + c.gtk_application_set_accels_for_action( + @ptrCast(self.app), + gtk_action.ptr, + &accels, + ); +} + /// Called by CoreApp to wake up the event loop. pub fn wakeup(self: App) void { _ = self; @@ -204,9 +233,10 @@ pub fn run(self: *App) !void { self.initMenu(); // On startup, we want to check for configuration errors right away - // so we can show our error window. - self.updateConfigErrors() catch |err| { - log.warn("error handling configuration errors err={}", .{err}); + // so we can show our error window. We also need to setup other initial + // state. + self.syncConfigChanges() catch |err| { + log.warn("error handling configuration changes err={}", .{err}); }; while (self.running) { @@ -357,9 +387,9 @@ fn gtkActionQuit( fn initActions(self: *App) void { const action_quit = c.g_simple_action_new("quit", null); defer c.g_object_unref(action_quit); - c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action_quit)); - _ = c.g_signal_connect_data(action_quit, "activate", c.G_CALLBACK(>kActionQuit), self, null, c.G_CONNECT_DEFAULT); + + c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action_quit)); } /// This sets the self.menu property to the application menu that can be diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 398f2bf85..ad8369c7c 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -1008,133 +1008,3 @@ fn translateMods(state: c.GdkModifierType) input.Mods { if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true; return mods; } - -fn translateKey(keyval: c.guint) input.Key { - return switch (keyval) { - c.GDK_KEY_a => .a, - c.GDK_KEY_b => .b, - c.GDK_KEY_c => .c, - c.GDK_KEY_d => .d, - c.GDK_KEY_e => .e, - c.GDK_KEY_f => .f, - c.GDK_KEY_g => .g, - c.GDK_KEY_h => .h, - c.GDK_KEY_i => .i, - c.GDK_KEY_j => .j, - c.GDK_KEY_k => .k, - c.GDK_KEY_l => .l, - c.GDK_KEY_m => .m, - c.GDK_KEY_n => .n, - c.GDK_KEY_o => .o, - c.GDK_KEY_p => .p, - c.GDK_KEY_q => .q, - c.GDK_KEY_r => .r, - c.GDK_KEY_s => .s, - c.GDK_KEY_t => .t, - c.GDK_KEY_u => .u, - c.GDK_KEY_v => .v, - c.GDK_KEY_w => .w, - c.GDK_KEY_x => .x, - c.GDK_KEY_y => .y, - c.GDK_KEY_z => .z, - - c.GDK_KEY_0 => .zero, - c.GDK_KEY_1 => .one, - c.GDK_KEY_2 => .two, - c.GDK_KEY_3 => .three, - c.GDK_KEY_4 => .four, - c.GDK_KEY_5 => .five, - c.GDK_KEY_6 => .six, - c.GDK_KEY_7 => .seven, - c.GDK_KEY_8 => .eight, - c.GDK_KEY_9 => .nine, - - c.GDK_KEY_semicolon => .semicolon, - c.GDK_KEY_space => .space, - c.GDK_KEY_apostrophe => .apostrophe, - c.GDK_KEY_comma => .comma, - c.GDK_KEY_grave => .grave_accent, // ` - c.GDK_KEY_period => .period, - c.GDK_KEY_slash => .slash, - c.GDK_KEY_minus => .minus, - c.GDK_KEY_equal => .equal, - c.GDK_KEY_bracketleft => .left_bracket, // [ - c.GDK_KEY_bracketright => .right_bracket, // ] - c.GDK_KEY_backslash => .backslash, // / - - c.GDK_KEY_Up => .up, - c.GDK_KEY_Down => .down, - c.GDK_KEY_Right => .right, - c.GDK_KEY_Left => .left, - c.GDK_KEY_Home => .home, - c.GDK_KEY_End => .end, - c.GDK_KEY_Insert => .insert, - c.GDK_KEY_Delete => .delete, - c.GDK_KEY_Caps_Lock => .caps_lock, - c.GDK_KEY_Scroll_Lock => .scroll_lock, - c.GDK_KEY_Num_Lock => .num_lock, - c.GDK_KEY_Page_Up => .page_up, - c.GDK_KEY_Page_Down => .page_down, - c.GDK_KEY_Escape => .escape, - c.GDK_KEY_Return => .enter, - c.GDK_KEY_Tab => .tab, - c.GDK_KEY_BackSpace => .backspace, - c.GDK_KEY_Print => .print_screen, - c.GDK_KEY_Pause => .pause, - - c.GDK_KEY_F1 => .f1, - c.GDK_KEY_F2 => .f2, - c.GDK_KEY_F3 => .f3, - c.GDK_KEY_F4 => .f4, - c.GDK_KEY_F5 => .f5, - c.GDK_KEY_F6 => .f6, - c.GDK_KEY_F7 => .f7, - c.GDK_KEY_F8 => .f8, - c.GDK_KEY_F9 => .f9, - c.GDK_KEY_F10 => .f10, - c.GDK_KEY_F11 => .f11, - c.GDK_KEY_F12 => .f12, - c.GDK_KEY_F13 => .f13, - c.GDK_KEY_F14 => .f14, - c.GDK_KEY_F15 => .f15, - c.GDK_KEY_F16 => .f16, - c.GDK_KEY_F17 => .f17, - c.GDK_KEY_F18 => .f18, - c.GDK_KEY_F19 => .f19, - c.GDK_KEY_F20 => .f20, - c.GDK_KEY_F21 => .f21, - c.GDK_KEY_F22 => .f22, - c.GDK_KEY_F23 => .f23, - c.GDK_KEY_F24 => .f24, - c.GDK_KEY_F25 => .f25, - - c.GDK_KEY_KP_0 => .kp_0, - c.GDK_KEY_KP_1 => .kp_1, - c.GDK_KEY_KP_2 => .kp_2, - c.GDK_KEY_KP_3 => .kp_3, - c.GDK_KEY_KP_4 => .kp_4, - c.GDK_KEY_KP_5 => .kp_5, - c.GDK_KEY_KP_6 => .kp_6, - c.GDK_KEY_KP_7 => .kp_7, - c.GDK_KEY_KP_8 => .kp_8, - c.GDK_KEY_KP_9 => .kp_9, - c.GDK_KEY_KP_Decimal => .kp_decimal, - c.GDK_KEY_KP_Divide => .kp_divide, - c.GDK_KEY_KP_Multiply => .kp_multiply, - c.GDK_KEY_KP_Subtract => .kp_subtract, - c.GDK_KEY_KP_Add => .kp_add, - c.GDK_KEY_KP_Enter => .kp_enter, - c.GDK_KEY_KP_Equal => .kp_equal, - - c.GDK_KEY_Shift_L => .left_shift, - c.GDK_KEY_Control_L => .left_control, - c.GDK_KEY_Alt_L => .left_alt, - c.GDK_KEY_Super_L => .left_super, - c.GDK_KEY_Shift_R => .right_shift, - c.GDK_KEY_Control_R => .right_control, - c.GDK_KEY_Alt_R => .right_alt, - c.GDK_KEY_Super_R => .right_super, - - else => .invalid, - }; -} diff --git a/src/apprt/gtk/key.zig b/src/apprt/gtk/key.zig new file mode 100644 index 000000000..6f3a5cc81 --- /dev/null +++ b/src/apprt/gtk/key.zig @@ -0,0 +1,201 @@ +const std = @import("std"); +const input = @import("../../input.zig"); +const c = @import("c.zig"); + +/// Returns a GTK accelerator string from a trigger. +pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 { + var buf_stream = std.io.fixedBufferStream(buf); + const writer = buf_stream.writer(); + + // Modifiers + if (trigger.mods.shift) try writer.writeAll(""); + if (trigger.mods.ctrl) try writer.writeAll(""); + if (trigger.mods.alt) try writer.writeAll(""); + if (trigger.mods.super) try writer.writeAll(""); + + // Write our key + const keyval = keyvalFromKey(trigger.key) orelse return null; + try writer.writeAll(std.mem.sliceTo(c.gdk_keyval_name(keyval), 0)); + + // We need to make the string null terminated. + try writer.writeByte(0); + const slice = buf_stream.getWritten(); + return slice[0 .. slice.len - 1 :0]; +} + +pub 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; +} + +/// Returns an input key from a keyval or null if we don't have a mapping. +pub fn keyFromKeyval(keyval: c.guint) ?input.Key { + for (keymap) |entry| { + if (entry[0] == keyval) return entry[1]; + } + + return null; +} + +/// Returns a keyval from an input key or null if we don't have a mapping. +pub fn keyvalFromKey(key: input.Key) ?c.guint { + switch (key) { + inline else => |key_comptime| { + return comptime value: { + @setEvalBranchQuota(10_000); + for (keymap) |entry| { + if (entry[1] == key_comptime) break :value entry[0]; + } + + break :value null; + }; + }, + } +} + +test "accelFromTrigger" { + const testing = std.testing; + var buf: [256]u8 = undefined; + + try testing.expectEqualStrings("q", (try accelFromTrigger(&buf, .{ + .mods = .{ .super = true }, + .key = .q, + })).?); +} + +/// A raw entry in the keymap. Our keymap contains mappings between +/// GDK keys and our own key enum. +const RawEntry = struct { c.guint, input.Key }; + +const keymap: []const RawEntry = &.{ + .{ c.GDK_KEY_a, .a }, + .{ c.GDK_KEY_b, .b }, + .{ c.GDK_KEY_c, .c }, + .{ c.GDK_KEY_d, .d }, + .{ c.GDK_KEY_e, .e }, + .{ c.GDK_KEY_f, .f }, + .{ c.GDK_KEY_g, .g }, + .{ c.GDK_KEY_h, .h }, + .{ c.GDK_KEY_i, .i }, + .{ c.GDK_KEY_j, .j }, + .{ c.GDK_KEY_k, .k }, + .{ c.GDK_KEY_l, .l }, + .{ c.GDK_KEY_m, .m }, + .{ c.GDK_KEY_n, .n }, + .{ c.GDK_KEY_o, .o }, + .{ c.GDK_KEY_p, .p }, + .{ c.GDK_KEY_q, .q }, + .{ c.GDK_KEY_r, .r }, + .{ c.GDK_KEY_s, .s }, + .{ c.GDK_KEY_t, .t }, + .{ c.GDK_KEY_u, .u }, + .{ c.GDK_KEY_v, .v }, + .{ c.GDK_KEY_w, .w }, + .{ c.GDK_KEY_x, .x }, + .{ c.GDK_KEY_y, .y }, + .{ c.GDK_KEY_z, .z }, + + .{ c.GDK_KEY_0, .zero }, + .{ c.GDK_KEY_1, .one }, + .{ c.GDK_KEY_2, .two }, + .{ c.GDK_KEY_3, .three }, + .{ c.GDK_KEY_4, .four }, + .{ c.GDK_KEY_5, .five }, + .{ c.GDK_KEY_6, .six }, + .{ c.GDK_KEY_7, .seven }, + .{ c.GDK_KEY_8, .eight }, + .{ c.GDK_KEY_9, .nine }, + + .{ c.GDK_KEY_semicolon, .semicolon }, + .{ c.GDK_KEY_space, .space }, + .{ c.GDK_KEY_apostrophe, .apostrophe }, + .{ c.GDK_KEY_comma, .comma }, + .{ c.GDK_KEY_grave, .grave_accent }, + .{ c.GDK_KEY_period, .period }, + .{ c.GDK_KEY_slash, .slash }, + .{ c.GDK_KEY_minus, .minus }, + .{ c.GDK_KEY_equal, .equal }, + .{ c.GDK_KEY_bracketleft, .left_bracket }, + .{ c.GDK_KEY_bracketright, .right_bracket }, + .{ c.GDK_KEY_backslash, .backslash }, + + .{ c.GDK_KEY_Up, .up }, + .{ c.GDK_KEY_Down, .down }, + .{ c.GDK_KEY_Right, .right }, + .{ c.GDK_KEY_Left, .left }, + .{ c.GDK_KEY_Home, .home }, + .{ c.GDK_KEY_End, .end }, + .{ c.GDK_KEY_Insert, .insert }, + .{ c.GDK_KEY_Delete, .delete }, + .{ c.GDK_KEY_Caps_Lock, .caps_lock }, + .{ c.GDK_KEY_Scroll_Lock, .scroll_lock }, + .{ c.GDK_KEY_Num_Lock, .num_lock }, + .{ c.GDK_KEY_Page_Up, .page_up }, + .{ c.GDK_KEY_Page_Down, .page_down }, + .{ c.GDK_KEY_Escape, .escape }, + .{ c.GDK_KEY_Return, .enter }, + .{ c.GDK_KEY_Tab, .tab }, + .{ c.GDK_KEY_BackSpace, .backspace }, + .{ c.GDK_KEY_Print, .print_screen }, + .{ c.GDK_KEY_Pause, .pause }, + + .{ c.GDK_KEY_F1, .f1 }, + .{ c.GDK_KEY_F2, .f2 }, + .{ c.GDK_KEY_F3, .f3 }, + .{ c.GDK_KEY_F4, .f4 }, + .{ c.GDK_KEY_F5, .f5 }, + .{ c.GDK_KEY_F6, .f6 }, + .{ c.GDK_KEY_F7, .f7 }, + .{ c.GDK_KEY_F8, .f8 }, + .{ c.GDK_KEY_F9, .f9 }, + .{ c.GDK_KEY_F10, .f10 }, + .{ c.GDK_KEY_F11, .f11 }, + .{ c.GDK_KEY_F12, .f12 }, + .{ c.GDK_KEY_F13, .f13 }, + .{ c.GDK_KEY_F14, .f14 }, + .{ c.GDK_KEY_F15, .f15 }, + .{ c.GDK_KEY_F16, .f16 }, + .{ c.GDK_KEY_F17, .f17 }, + .{ c.GDK_KEY_F18, .f18 }, + .{ c.GDK_KEY_F19, .f19 }, + .{ c.GDK_KEY_F20, .f20 }, + .{ c.GDK_KEY_F21, .f21 }, + .{ c.GDK_KEY_F22, .f22 }, + .{ c.GDK_KEY_F23, .f23 }, + .{ c.GDK_KEY_F24, .f24 }, + .{ c.GDK_KEY_F25, .f25 }, + + .{ c.GDK_KEY_KP_0, .kp_0 }, + .{ c.GDK_KEY_KP_1, .kp_1 }, + .{ c.GDK_KEY_KP_2, .kp_2 }, + .{ c.GDK_KEY_KP_3, .kp_3 }, + .{ c.GDK_KEY_KP_4, .kp_4 }, + .{ c.GDK_KEY_KP_5, .kp_5 }, + .{ c.GDK_KEY_KP_6, .kp_6 }, + .{ c.GDK_KEY_KP_7, .kp_7 }, + .{ c.GDK_KEY_KP_8, .kp_8 }, + .{ c.GDK_KEY_KP_9, .kp_9 }, + .{ c.GDK_KEY_KP_Decimal, .kp_decimal }, + .{ c.GDK_KEY_KP_Divide, .kp_divide }, + .{ c.GDK_KEY_KP_Multiply, .kp_multiply }, + .{ c.GDK_KEY_KP_Subtract, .kp_subtract }, + .{ c.GDK_KEY_KP_Add, .kp_add }, + .{ c.GDK_KEY_KP_Enter, .kp_enter }, + .{ c.GDK_KEY_KP_Equal, .kp_equal }, + + .{ c.GDK_KEY_Shift_L, .left_shift }, + .{ c.GDK_KEY_Control_L, .left_control }, + .{ c.GDK_KEY_Alt_L, .left_alt }, + .{ c.GDK_KEY_Super_L, .left_super }, + .{ c.GDK_KEY_Shift_R, .right_shift }, + .{ c.GDK_KEY_Control_R, .right_control }, + .{ c.GDK_KEY_Alt_R, .right_alt }, + .{ c.GDK_KEY_Super_R, .right_super }, +}; diff --git a/src/main.zig b/src/main.zig index 10a9d62ae..c61f94c5e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -259,6 +259,7 @@ test { _ = @import("Pty.zig"); _ = @import("Command.zig"); _ = @import("font/main.zig"); + _ = @import("apprt.zig"); _ = @import("renderer.zig"); _ = @import("termio.zig"); _ = @import("input.zig");