diff --git a/build.zig b/build.zig index 2db6478..e86ad34 100644 --- a/build.zig +++ b/build.zig @@ -182,14 +182,14 @@ pub fn build(b: *std.Build) void { b.installDirectory(.{ .source_dir = b.path("resources/shell-integration"), .install_dir = .prefix, - .install_subdir = "share/shell-integration", + .install_subdir = "share/seance/shell-integration", }); // Install wrapper scripts (claude wrapper, etc.) b.installDirectory(.{ .source_dir = b.path("resources/bin"), .install_dir = .prefix, - .install_subdir = "share/bin", + .install_subdir = "share/seance/bin", }); // Install bundled icons (from GNOME Icon Library) into hicolor theme diff --git a/src/command_palette.zig b/src/command_palette.zig index e856fb7..d116f7b 100644 --- a/src/command_palette.zig +++ b/src/command_palette.zig @@ -117,6 +117,7 @@ const static_commands = [_]Command{ .{ .label = "Jump to Unread", .action = .jump_to_unread, .keywords = "jump unread notification" }, .{ .label = "Open Settings", .action = .open_settings, .keywords = "settings preferences config options" }, .{ .label = "Reload Configuration", .action = .reload_config, .keywords = "config reload refresh settings" }, + .{ .label = "Toggle Fullscreen", .action = .toggle_fullscreen, .keywords = "fullscreen toggle window maximize f11" }, .{ .label = "Quit Seance", .action = .quit_app, .keywords = "quit exit close application app shutdown" }, // Workspace commands diff --git a/src/ghostty_bridge.zig b/src/ghostty_bridge.zig index 42c6754..a555de0 100644 --- a/src/ghostty_bridge.zig +++ b/src/ghostty_bridge.zig @@ -211,6 +211,21 @@ fn createWrapperResourcesDir() bool { } } + // Symlink ghostty's .zshenv — ghostty sets ZDOTDIR to this directory so + // zsh auto-sources .zshenv, which restores the real ZDOTDIR and chains + // into ghostty-integration. Without this symlink the wrapper directory + // has no .zshenv and zsh shows the new-user wizard. + { + var zbuf: [std.fs.max_path_bytes]u8 = undefined; + const ztarget = std.fmt.bufPrint(&zbuf, "{s}/shell-integration/zsh/.zshenv", .{real}) catch return false; + var zsh_dir = wd.openDir("shell-integration/zsh", .{}) catch return false; + defer zsh_dir.close(); + zsh_dir.symLink(ztarget, ".zshenv", .{}) catch |e| { + std.log.warn("ghostty_bridge: failed to symlink .zshenv: {}", .{e}); + return false; + }; + } + // Write wrapper shell integration scripts writeShellWrapper(wd, "shell-integration/bash/ghostty.bash", real, "/shell-integration/bash/ghostty.bash", "/bash-integration.sh") catch return false; writeShellWrapper(wd, "shell-integration/zsh/ghostty-integration", real, "/shell-integration/zsh/ghostty-integration", "/zsh-integration.sh") catch return false; diff --git a/src/keybinds.zig b/src/keybinds.zig index 8ed2096..3fb98ee 100644 --- a/src/keybinds.zig +++ b/src/keybinds.zig @@ -73,6 +73,7 @@ pub const Action = enum(u8) { // Window new_window, + toggle_fullscreen, quit_app, // Command palette @@ -260,6 +261,7 @@ fn initDefaults() void { // Window set(.new_window, .{ .key = c.GDK_KEY_N, .ctrl = true, .shift = true }); + set(.toggle_fullscreen, .{ .key = c.GDK_KEY_F11 }); set(.quit_app, .{ .key = c.GDK_KEY_Q, .ctrl = true, .shift = true }); // Command palette @@ -547,6 +549,7 @@ pub fn executeAction(action: Action, state: *Window.WindowState) c.gboolean { .new_window => { _ = state.window_manager.newWindow(); }, + .toggle_fullscreen => state.toggleFullscreen(), .quit_app => state.quitApp(), // Command palette diff --git a/src/pane.zig b/src/pane.zig index 23fe877..66a0432 100644 --- a/src/pane.zig +++ b/src/pane.zig @@ -670,10 +670,10 @@ fn initSurface(pane: *Pane, width: u32, height: u32) void { if (std.fs.selfExePath(&exe_buf)) |exe_path| { if (std.fs.path.dirname(exe_path)) |exe_dir| { if (std.fs.path.dirname(exe_dir)) |prefix| { - if (std.fmt.bufPrintZ(&int_env_val_buf, "{s}/share/shell-integration", .{prefix})) |iv| { + if (std.fmt.bufPrintZ(&int_env_val_buf, "{s}/share/seance/shell-integration", .{prefix})) |iv| { int_val = iv; } else |_| {} - if (std.fmt.bufPrintZ(&bin_env_val_buf, "{s}/share/bin", .{prefix})) |bv| { + if (std.fmt.bufPrintZ(&bin_env_val_buf, "{s}/share/seance/bin", .{prefix})) |bv| { bin_val = bv; } else |_| {} } diff --git a/src/shortcuts_overlay.zig b/src/shortcuts_overlay.zig index b98f185..203f354 100644 --- a/src/shortcuts_overlay.zig +++ b/src/shortcuts_overlay.zig @@ -121,6 +121,7 @@ const groups = [_]Group{ .title = "General", .rows = &.{ .{ .single = .{ .action = .new_window, .label = "New Window" } }, + .{ .single = .{ .action = .toggle_fullscreen, .label = "Fullscreen" } }, .{ .single = .{ .action = .open_command_palette, .label = "Command Palette" } }, .{ .single = .{ .action = .open_folder, .label = "Open Folder" } }, .{ .single = .{ .action = .open_settings, .label = "Settings" } }, diff --git a/src/window.zig b/src/window.zig index 3f11a04..b90a1c9 100644 --- a/src/window.zig +++ b/src/window.zig @@ -814,6 +814,15 @@ pub const WindowState = struct { /// `confirm_close_window` is enabled and there's interesting state to /// warn about (multiple windows or multiple workspaces), shows a single /// quit confirmation dialog instead of doing it per-window. + pub fn toggleFullscreen(self: *WindowState) void { + const win: *c.GtkWindow = @ptrCast(self.gtk_window); + if (c.gtk_window_is_fullscreen(win) != 0) { + c.gtk_window_unfullscreen(win); + } else { + c.gtk_window_fullscreen(win); + } + } + pub fn quitApp(self: *WindowState) void { const cfg = config_mod.get(); if (!cfg.confirm_close_window or !hasInterestingState(self.window_manager)) { @@ -1552,6 +1561,16 @@ pub fn create(wm: *WindowManager) !*WindowState { 0, ); + // Hide CSD title bar when entering fullscreen + _ = c.g_signal_connect_data( + @as(c.gpointer, @ptrCast(window)), + "notify::fullscreened", + @as(c.GCallback, @ptrCast(&onFullscreenChanged)), + @ptrCast(state), + null, + 0, + ); + // Load theme-aware CSS loadThemeCss(); @@ -1695,6 +1714,13 @@ fn onWindowFocusChanged(_: *c.GObject, _: ?*anyopaque, data: c.gpointer) callcon } } +fn onFullscreenChanged(_: *c.GObject, _: ?*anyopaque, data: c.gpointer) callconv(.c) void { + const state: *WindowState = @ptrCast(@alignCast(data)); + const tv = state.toolbar_view orelse return; + const is_fullscreen = c.gtk_window_is_fullscreen(@as(*c.GtkWindow, @ptrCast(state.gtk_window))); + c.adw_toolbar_view_set_reveal_top_bars(@as(*c.AdwToolbarView, @ptrCast(tv)), if (is_fullscreen != 0) 0 else 1); +} + fn onWorkspaceSelect(index: usize) void { if (window_manager) |wm| if (wm.active_window) |state| state.selectWorkspace(index); }