mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge pull request #486 from mitchellh/gtk-app-window
gtk: Menu, Header Buttons, About Window
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -87,7 +87,7 @@ jobs:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: test
|
||||
run: nix develop -c zig build test
|
||||
run: nix develop -c zig build -Dapp-runtime=none test
|
||||
|
||||
- name: Test GTK Build
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk
|
||||
|
@ -206,7 +206,11 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const exe_options = b.addOptions();
|
||||
exe_options.addOption(std.SemanticVersion, "app_version", version);
|
||||
exe_options.addOption([]const u8, "app_version_string", b.fmt("{}", .{version}));
|
||||
exe_options.addOption([:0]const u8, "app_version_string", try std.fmt.allocPrintZ(
|
||||
b.allocator,
|
||||
"{}",
|
||||
.{version},
|
||||
));
|
||||
exe_options.addOption(bool, "tracy_enabled", tracy);
|
||||
exe_options.addOption(bool, "flatpak", flatpak);
|
||||
exe_options.addOption(apprt.Runtime, "app_runtime", app_runtime);
|
||||
|
@ -1,8 +1,6 @@
|
||||
{ mkShell, lib, stdenv
|
||||
|
||||
, bashInteractive
|
||||
, debugedit
|
||||
, flatpak-builder
|
||||
, gdb
|
||||
, glxinfo
|
||||
, ncurses
|
||||
@ -92,10 +90,6 @@ in mkShell rec {
|
||||
# by default so we have to include this.
|
||||
bashInteractive
|
||||
|
||||
# Flatpak builds
|
||||
debugedit
|
||||
flatpak-builder
|
||||
|
||||
gdb
|
||||
valgrind
|
||||
wraptest
|
||||
|
@ -25,7 +25,7 @@ pub const surface = @import("apprt/surface.zig");
|
||||
/// Window or something.
|
||||
pub const runtime = switch (build_config.artifact) {
|
||||
.exe => switch (build_config.app_runtime) {
|
||||
.none => @compileError("exe with no runtime not allowed"),
|
||||
.none => struct {},
|
||||
.glfw => glfw,
|
||||
.gtk => gtk,
|
||||
},
|
||||
@ -64,5 +64,6 @@ pub const Runtime = enum {
|
||||
};
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
_ = Runtime;
|
||||
_ = runtime;
|
||||
}
|
||||
|
@ -5,4 +5,6 @@ pub const Surface = @import("gtk/Surface.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
|
||||
_ = @import("gtk/key.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);
|
||||
|
||||
@ -37,6 +39,9 @@ ctx: *c.GMainContext,
|
||||
/// The "none" cursor. We use one that is shared across the entire app.
|
||||
cursor_none: ?*c.GdkCursor,
|
||||
|
||||
/// The shared application menu.
|
||||
menu: ?*c.GMenu = null,
|
||||
|
||||
/// The configuration errors window, if it is currently open.
|
||||
config_errors_window: ?*ConfigErrorsWindow = null,
|
||||
|
||||
@ -140,6 +145,7 @@ pub fn terminate(self: *App) void {
|
||||
c.g_object_unref(self.app);
|
||||
|
||||
if (self.cursor_none) |cursor| c.g_object_unref(cursor);
|
||||
if (self.menu) |menu| c.g_object_unref(menu);
|
||||
|
||||
self.config.deinit();
|
||||
|
||||
@ -159,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
|
||||
@ -185,6 +195,35 @@ fn updateConfigErrors(self: *App) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn syncActionAccelerators(self: *App) !void {
|
||||
try self.syncActionAccelerator("app.quit", .{ .quit = {} });
|
||||
try self.syncActionAccelerator("app.reload_config", .{ .reload_config = {} });
|
||||
try self.syncActionAccelerator("win.close", .{ .close_surface = {} });
|
||||
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
|
||||
try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} });
|
||||
}
|
||||
|
||||
fn syncActionAccelerator(
|
||||
self: *App,
|
||||
gtk_action: [:0]const u8,
|
||||
action: input.Binding.Action,
|
||||
) !void {
|
||||
// Reset it initially
|
||||
const zero = [_]?[*:0]const u8{null};
|
||||
c.gtk_application_set_accels_for_action(@ptrCast(self.app), gtk_action.ptr, &zero);
|
||||
|
||||
const trigger = self.config.keybind.set.getTrigger(action) orelse return;
|
||||
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;
|
||||
@ -195,10 +234,15 @@ pub fn wakeup(self: App) void {
|
||||
pub fn run(self: *App) !void {
|
||||
if (!self.running) return;
|
||||
|
||||
// If we're not remote, then we also setup our actions and menus.
|
||||
self.initActions();
|
||||
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) {
|
||||
@ -331,3 +375,81 @@ fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
|
||||
.new_window = .{},
|
||||
}, .{ .forever = {} });
|
||||
}
|
||||
|
||||
fn gtkActionReloadConfig(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
||||
_ = self.core_app.mailbox.push(.{
|
||||
.reload_config = {},
|
||||
}, .{ .forever = {} });
|
||||
}
|
||||
|
||||
fn gtkActionQuit(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
||||
self.core_app.setQuit() catch |err| {
|
||||
log.warn("error setting quit err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// This is called to setup the action map that this application supports.
|
||||
/// This should be called only once on startup.
|
||||
fn initActions(self: *App) void {
|
||||
const actions = .{
|
||||
.{ "quit", >kActionQuit },
|
||||
.{ "reload_config", >kActionReloadConfig },
|
||||
};
|
||||
|
||||
inline for (actions) |entry| {
|
||||
const action = c.g_simple_action_new(entry[0], null);
|
||||
defer c.g_object_unref(action);
|
||||
_ = c.g_signal_connect_data(
|
||||
action,
|
||||
"activate",
|
||||
c.G_CALLBACK(entry[1]),
|
||||
self,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
);
|
||||
c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action));
|
||||
}
|
||||
}
|
||||
|
||||
/// This sets the self.menu property to the application menu that can be
|
||||
/// shared by all application windows.
|
||||
fn initMenu(self: *App) void {
|
||||
const menu = c.g_menu_new();
|
||||
errdefer c.g_object_unref(menu);
|
||||
|
||||
{
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "New Window", "win.new_window");
|
||||
c.g_menu_append(section, "New Tab", "win.new_tab");
|
||||
c.g_menu_append(section, "Close Window", "win.close");
|
||||
}
|
||||
|
||||
{
|
||||
const section = c.g_menu_new();
|
||||
defer c.g_object_unref(section);
|
||||
c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section)));
|
||||
c.g_menu_append(section, "Reload Configuration", "app.reload_config");
|
||||
c.g_menu_append(section, "About Ghostty", "win.about");
|
||||
}
|
||||
|
||||
// {
|
||||
// const section = c.g_menu_new();
|
||||
// defer c.g_object_unref(section);
|
||||
// c.g_menu_append_submenu(menu, "File", @ptrCast(@alignCast(section)));
|
||||
// }
|
||||
|
||||
self.menu = menu;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ fn destroy(self: *ConfigErrors) void {
|
||||
|
||||
fn init(self: *ConfigErrors, app: *App) !void {
|
||||
// Create the window
|
||||
const window = c.gtk_application_window_new(app.app);
|
||||
const window = c.gtk_window_new();
|
||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||
errdefer c.gtk_window_destroy(gtk_window);
|
||||
c.gtk_window_set_title(gtk_window, "Configuration Errors");
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ const Window = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const build_config = @import("../../build_config.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const configpkg = @import("../../config.zig");
|
||||
@ -89,6 +90,23 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
c.gtk_widget_set_opacity(@ptrCast(window), app.config.@"background-opacity");
|
||||
}
|
||||
|
||||
// Use the new GTK4 header bar
|
||||
const header = c.gtk_header_bar_new();
|
||||
c.gtk_window_set_titlebar(gtk_window, header);
|
||||
{
|
||||
const btn = c.gtk_menu_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, "Main Menu");
|
||||
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
||||
c.gtk_menu_button_set_menu_model(@ptrCast(btn), @ptrCast(@alignCast(app.menu)));
|
||||
c.gtk_header_bar_pack_end(@ptrCast(header), btn);
|
||||
}
|
||||
{
|
||||
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic");
|
||||
c.gtk_widget_set_tooltip_text(btn, "New Tab");
|
||||
c.gtk_header_bar_pack_end(@ptrCast(header), btn);
|
||||
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kActionNewTab), self, null, c.G_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
// Hide window decoration if configured. This has to happen before
|
||||
// `gtk_widget_show`.
|
||||
if (!app.config.@"window-decoration") {
|
||||
@ -109,10 +127,6 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
c.gtk_widget_set_vexpand(notebook_widget, 1);
|
||||
c.gtk_widget_set_hexpand(notebook_widget, 1);
|
||||
|
||||
// Create our add button for new tabs
|
||||
const notebook_add_btn = c.gtk_button_new_from_icon_name("list-add-symbolic");
|
||||
c.gtk_notebook_set_action_widget(notebook, notebook_add_btn, c.GTK_PACK_END);
|
||||
|
||||
// Create our box which will hold our widgets.
|
||||
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
@ -129,12 +143,14 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
// All of our events
|
||||
_ = 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(notebook_add_btn, "clicked", c.G_CALLBACK(>kTabAddClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(notebook, "page-added", c.G_CALLBACK(>kPageAdded), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(notebook, "page-removed", c.G_CALLBACK(>kPageRemoved), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(notebook, "switch-page", c.G_CALLBACK(>kSwitchPage), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(notebook, "create-window", c.G_CALLBACK(>kNotebookCreateWindow), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Our actions for the menu
|
||||
initActions(self);
|
||||
|
||||
// The box is our main child
|
||||
c.gtk_window_set_child(gtk_window, box);
|
||||
|
||||
@ -142,6 +158,32 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
c.gtk_widget_show(window);
|
||||
}
|
||||
|
||||
/// Sets up the GTK actions for the window scope. Actions are how GTK handles
|
||||
/// menus and such. The menu is defined in App.zig but the action is defined
|
||||
/// here. The string name binds them.
|
||||
fn initActions(self: *Window) void {
|
||||
const actions = .{
|
||||
.{ "about", >kActionAbout },
|
||||
.{ "close", >kActionClose },
|
||||
.{ "new_window", >kActionNewWindow },
|
||||
.{ "new_tab", >kActionNewTab },
|
||||
};
|
||||
|
||||
inline for (actions) |entry| {
|
||||
const action = c.g_simple_action_new(entry[0], null);
|
||||
defer c.g_object_unref(action);
|
||||
_ = c.g_signal_connect_data(
|
||||
action,
|
||||
"activate",
|
||||
c.G_CALLBACK(entry[1]),
|
||||
self,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
);
|
||||
c.g_action_map_add_action(@ptrCast(self.window), @ptrCast(action));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Window) void {
|
||||
if (self.icon_search_dir) |ptr| self.app.core_app.alloc.free(ptr);
|
||||
}
|
||||
@ -298,15 +340,6 @@ fn focusCurrentTab(self: *Window) void {
|
||||
_ = c.gtk_widget_grab_focus(widget);
|
||||
}
|
||||
|
||||
fn gtkTabAddClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self = userdataSelf(ud.?);
|
||||
const parent = self.app.core_app.focusedSurface();
|
||||
self.newTab(parent) catch |err| {
|
||||
log.warn("error adding new tab: {}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
const surface: *Surface = @ptrCast(@alignCast(ud));
|
||||
surface.core_surface.close();
|
||||
@ -455,6 +488,78 @@ fn getNotebookPageIndex(page: *c.GtkNotebookPage) c_int {
|
||||
return c.g_value_get_int(&value);
|
||||
}
|
||||
|
||||
fn gtkActionAbout(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
|
||||
c.gtk_show_about_dialog(
|
||||
self.window,
|
||||
"program-name",
|
||||
"Ghostty",
|
||||
"logo-icon-name",
|
||||
"com.mitchellh.ghostty",
|
||||
"title",
|
||||
"About Ghostty",
|
||||
"version",
|
||||
build_config.version_string.ptr,
|
||||
"website",
|
||||
"https://github.com/mitchellh/ghostty",
|
||||
@as(?*anyopaque, null),
|
||||
);
|
||||
}
|
||||
|
||||
fn gtkActionClose(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
surface.performBindingAction(.{ .close_surface = {} }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkActionNewWindow(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
surface.performBindingAction(.{ .new_window = {} }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkActionNewTab(
|
||||
_: *c.GSimpleAction,
|
||||
_: *c.GVariant,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
surface.performBindingAction(.{ .new_tab = {} }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the surface to use for an action.
|
||||
fn actionSurface(self: *Window) ?*CoreSurface {
|
||||
const page_idx = c.gtk_notebook_get_current_page(self.notebook);
|
||||
const page = c.gtk_notebook_get_nth_page(self.notebook, page_idx);
|
||||
const surface: *Surface = @ptrCast(@alignCast(
|
||||
c.g_object_get_data(@ptrCast(page), GL_AREA_SURFACE) orelse return null,
|
||||
));
|
||||
return &surface.core_surface;
|
||||
}
|
||||
|
||||
fn userdataSelf(ud: *anyopaque) *Window {
|
||||
return @ptrCast(@alignCast(ud));
|
||||
}
|
||||
|
201
src/apprt/gtk/key.zig
Normal file
201
src/apprt/gtk/key.zig
Normal file
@ -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("<Shift>");
|
||||
if (trigger.mods.ctrl) try writer.writeAll("<Ctrl>");
|
||||
if (trigger.mods.alt) try writer.writeAll("<Alt>");
|
||||
if (trigger.mods.super) try writer.writeAll("<Super>");
|
||||
|
||||
// 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("<Super>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 },
|
||||
};
|
@ -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");
|
||||
|
Reference in New Issue
Block a user