mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
gtk: implement quick terminal slide & autohide (#6090)
This commit is contained in:
@ -81,6 +81,7 @@ pub const DerivedConfig = struct {
|
||||
gtk_toolbar_style: configpkg.Config.GtkToolbarStyle,
|
||||
|
||||
quick_terminal_position: configpkg.Config.QuickTerminalPosition,
|
||||
quick_terminal_autohide: bool,
|
||||
|
||||
maximize: bool,
|
||||
fullscreen: bool,
|
||||
@ -98,6 +99,7 @@ pub const DerivedConfig = struct {
|
||||
.gtk_toolbar_style = config.@"gtk-toolbar-style",
|
||||
|
||||
.quick_terminal_position = config.@"quick-terminal-position",
|
||||
.quick_terminal_autohide = config.@"quick-terminal-autohide",
|
||||
|
||||
.maximize = config.maximize,
|
||||
.fullscreen = config.fullscreen,
|
||||
@ -247,6 +249,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::is-active", c.G_CALLBACK(>kWindowNotifyIsActive), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
|
||||
// need to stick the headerbar into the content box.
|
||||
@ -378,9 +381,14 @@ pub fn present(self: *Window) void {
|
||||
|
||||
pub fn toggleVisibility(self: *Window) void {
|
||||
const window: *gtk.Widget = @ptrCast(self.window);
|
||||
|
||||
window.setVisible(@intFromBool(window.isVisible() == 0));
|
||||
}
|
||||
|
||||
pub fn isQuickTerminal(self: *Window) bool {
|
||||
return self.app.quick_terminal == self;
|
||||
}
|
||||
|
||||
pub fn updateConfig(
|
||||
self: *Window,
|
||||
config: *const configpkg.Config,
|
||||
@ -421,7 +429,7 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
if (!csd_enabled) break :visible false;
|
||||
|
||||
// Never display the header bar as a quick terminal.
|
||||
if (self.app.quick_terminal == self) break :visible false;
|
||||
if (self.isQuickTerminal()) break :visible false;
|
||||
|
||||
// Unconditionally disable the header bar when fullscreened.
|
||||
if (self.config.fullscreen) break :visible false;
|
||||
@ -472,12 +480,6 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
self.winproto.syncAppearance() catch |err| {
|
||||
log.warn("failed to sync winproto appearance error={}", .{err});
|
||||
};
|
||||
|
||||
if (self.app.quick_terminal == self) {
|
||||
self.winproto.syncQuickTerminal() catch |err| {
|
||||
log.warn("failed to sync quick terminal appearance error={}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleCssClass(
|
||||
@ -730,6 +732,20 @@ fn gtkWindowNotifyFullscreened(
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkWindowNotifyIsActive(
|
||||
_: *c.GObject,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self = userdataSelf(ud orelse return);
|
||||
if (!self.isQuickTerminal()) return;
|
||||
|
||||
// Hide when we're unfocused
|
||||
if (self.config.quick_terminal_autohide and c.gtk_window_is_active(self.window) == 0) {
|
||||
self.toggleVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
|
||||
// sends an undefined value.
|
||||
fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
@ -803,7 +819,7 @@ pub fn close(self: *Window) void {
|
||||
const window: *gtk.Window = @ptrCast(self.window);
|
||||
|
||||
// Unset the quick terminal on the app level
|
||||
if (self.app.quick_terminal == self) self.app.quick_terminal = null;
|
||||
if (self.isQuickTerminal()) self.app.quick_terminal = null;
|
||||
|
||||
window.destroy();
|
||||
}
|
||||
@ -813,9 +829,6 @@ fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
log.debug("window close request", .{});
|
||||
const self = userdataSelf(ud.?);
|
||||
|
||||
// This path should never occur, but this is here as a safety measure.
|
||||
if (self.app.quick_terminal == self) return true;
|
||||
|
||||
// If none of our surfaces need confirmation, we can just exit.
|
||||
for (self.app.core_app.surfaces.items) |surface| {
|
||||
if (surface.container.window()) |window| {
|
||||
|
@ -133,12 +133,6 @@ pub const Window = union(Protocol) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syncQuickTerminal(self: *Window) !void {
|
||||
switch (self.*) {
|
||||
inline else => |*v| try v.syncQuickTerminal(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return switch (self) {
|
||||
inline else => |v| v.clientSideDecorationEnabled(),
|
||||
|
@ -59,8 +59,6 @@ pub const Window = struct {
|
||||
|
||||
pub fn syncAppearance(_: *Window) !void {}
|
||||
|
||||
pub fn syncQuickTerminal(_: *Window) !void {}
|
||||
|
||||
/// This returns true if CSD is enabled for this window. This
|
||||
/// should be the actual present state of the window, not the
|
||||
/// desired state.
|
||||
|
@ -27,6 +27,8 @@ pub const App = struct {
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
||||
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
|
||||
|
||||
kde_slide_manager: ?*org.KdeKwinSlideManager = null,
|
||||
|
||||
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
||||
};
|
||||
|
||||
@ -117,13 +119,26 @@ pub const App = struct {
|
||||
global,
|
||||
)) |blur_manager| {
|
||||
context.kde_blur_manager = blur_manager;
|
||||
} else if (registryBind(
|
||||
return;
|
||||
}
|
||||
|
||||
if (registryBind(
|
||||
org.KdeKwinServerDecorationManager,
|
||||
registry,
|
||||
global,
|
||||
)) |deco_manager| {
|
||||
context.kde_decoration_manager = deco_manager;
|
||||
deco_manager.setListener(*Context, decoManagerListener, context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (registryBind(
|
||||
org.KdeKwinSlideManager,
|
||||
registry,
|
||||
global,
|
||||
)) |slide_manager| {
|
||||
context.kde_slide_manager = slide_manager;
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@ -188,6 +203,10 @@ pub const Window = struct {
|
||||
/// of the window.
|
||||
decoration: ?*org.KdeKwinServerDecoration,
|
||||
|
||||
/// Object that controls the slide-in/slide-out animations of the
|
||||
/// quick terminal. Always null for windows other than the quick terminal.
|
||||
slide: ?*org.KdeKwinSlide,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
app: *App,
|
||||
@ -232,6 +251,7 @@ pub const Window = struct {
|
||||
.app_context = app.context,
|
||||
.blur_token = null,
|
||||
.decoration = deco,
|
||||
.slide = null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -239,6 +259,7 @@ pub const Window = struct {
|
||||
_ = alloc;
|
||||
if (self.blur_token) |blur| blur.release();
|
||||
if (self.decoration) |deco| deco.release();
|
||||
if (self.slide) |slide| slide.release();
|
||||
}
|
||||
|
||||
pub fn resizeEvent(_: *Window) !void {}
|
||||
@ -250,6 +271,12 @@ pub const Window = struct {
|
||||
self.syncDecoration() catch |err| {
|
||||
log.err("failed to sync blur={}", .{err});
|
||||
};
|
||||
|
||||
if (self.apprt_window.isQuickTerminal()) {
|
||||
self.syncQuickTerminal() catch |err| {
|
||||
log.warn("failed to sync quick terminal appearance={}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
@ -308,7 +335,7 @@ pub const Window = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn syncQuickTerminal(self: *Window) !void {
|
||||
fn syncQuickTerminal(self: *Window) !void {
|
||||
if (comptime !build_options.layer_shell) return;
|
||||
|
||||
const window = self.apprt_window.window;
|
||||
@ -339,6 +366,22 @@ pub const Window = struct {
|
||||
.top, .bottom, .center => c.gtk_window_set_default_size(window, 800, 400),
|
||||
.left, .right => c.gtk_window_set_default_size(window, 400, 800),
|
||||
}
|
||||
|
||||
if (self.apprt_window.isQuickTerminal()) {
|
||||
if (self.slide) |slide| slide.release();
|
||||
|
||||
self.slide = if (anchored_edge) |anchored| slide: {
|
||||
const mgr = self.app_context.kde_slide_manager orelse break :slide null;
|
||||
|
||||
const slide = mgr.create(self.surface) catch |err| {
|
||||
log.warn("could not create slide object={}", .{err});
|
||||
break :slide null;
|
||||
};
|
||||
slide.setLocation(@intCast(@intFromEnum(anchored.toKdeSlideLocation())));
|
||||
slide.commit();
|
||||
break :slide slide;
|
||||
} else null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -347,4 +390,13 @@ const LayerShellEdge = enum(c_uint) {
|
||||
right = c.GTK_LAYER_SHELL_EDGE_RIGHT,
|
||||
top = c.GTK_LAYER_SHELL_EDGE_TOP,
|
||||
bottom = c.GTK_LAYER_SHELL_EDGE_BOTTOM,
|
||||
|
||||
fn toKdeSlideLocation(self: LayerShellEdge) org.KdeKwinSlide.Location {
|
||||
return switch (self) {
|
||||
.left => .left,
|
||||
.top => .top,
|
||||
.right => .right,
|
||||
.bottom => .bottom,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -228,8 +228,6 @@ pub const Window = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn syncQuickTerminal(_: *Window) !void {}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return switch (self.config.window_decoration) {
|
||||
.auto, .client => true,
|
||||
|
@ -474,10 +474,12 @@ pub fn add(
|
||||
// FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/blur.xml"));
|
||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/server-decoration.xml"));
|
||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/slide.xml"));
|
||||
|
||||
scanner.generate("wl_compositor", 1);
|
||||
scanner.generate("org_kde_kwin_blur_manager", 1);
|
||||
scanner.generate("org_kde_kwin_server_decoration_manager", 1);
|
||||
scanner.generate("org_kde_kwin_slide_manager", 1);
|
||||
|
||||
step.root_module.addImport("wayland", wayland);
|
||||
step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));
|
||||
|
@ -1637,7 +1637,8 @@ keybind: Keybinds = .{},
|
||||
/// * `right` - Terminal appears at the right of the screen.
|
||||
/// * `center` - Terminal appears at the center of the screen.
|
||||
///
|
||||
/// Changing this configuration requires restarting Ghostty completely.
|
||||
/// On macOS, changing this configuration requires restarting Ghostty
|
||||
/// completely.
|
||||
///
|
||||
/// Note: There is no default keybind for toggling the quick terminal.
|
||||
/// To enable this feature, bind the `toggle_quick_terminal` action to a key.
|
||||
@ -1661,11 +1662,15 @@ keybind: Keybinds = .{},
|
||||
///
|
||||
/// The default value is `main` because this is the recommended screen
|
||||
/// by the operating system.
|
||||
///
|
||||
/// Only implemented on macOS.
|
||||
@"quick-terminal-screen": QuickTerminalScreen = .main,
|
||||
|
||||
/// Duration (in seconds) of the quick terminal enter and exit animation.
|
||||
/// Set it to 0 to disable animation completely. This can be changed at
|
||||
/// runtime.
|
||||
///
|
||||
/// Only implemented on macOS.
|
||||
@"quick-terminal-animation-duration": f64 = 0.2,
|
||||
|
||||
/// Automatically hide the quick terminal when focus shifts to another window.
|
||||
@ -1687,6 +1692,9 @@ keybind: Keybinds = .{},
|
||||
/// space.
|
||||
///
|
||||
/// The default value is `move`.
|
||||
///
|
||||
/// Only implemented on macOS.
|
||||
/// On Linux the behavior is always equivalent to `move`.
|
||||
@"quick-terminal-space-behavior": QuickTerminalSpaceBehavior = .move,
|
||||
|
||||
/// Whether to enable shell integration auto-injection or not. Shell integration
|
||||
|
@ -471,7 +471,15 @@ pub const Action = union(enum) {
|
||||
/// See the various configurations for the quick terminal in the
|
||||
/// configuration file to customize its behavior.
|
||||
///
|
||||
/// This currently only works on macOS.
|
||||
/// Supported on macOS and some desktop environments on Linux, namely
|
||||
/// those that support the `wlr-layer-shell` Wayland protocol
|
||||
/// (i.e. most desktop environments and window managers except GNOME).
|
||||
///
|
||||
/// Slide-in animations on Linux are only supported on KDE when the
|
||||
/// "Sliding Popups" KWin plugin is enabled. If you do not have this
|
||||
/// plugin enabled, open System Settings > Apps & Windows > Window
|
||||
/// Management > Desktop Effects, and enable the plugin in the plugin list.
|
||||
/// Ghostty would then need to be restarted for this to take effect.
|
||||
toggle_quick_terminal: void,
|
||||
|
||||
/// Show/hide all windows. If all windows become shown, we also ensure
|
||||
|
Reference in New Issue
Block a user