mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 17:26:09 +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,
|
gtk_toolbar_style: configpkg.Config.GtkToolbarStyle,
|
||||||
|
|
||||||
quick_terminal_position: configpkg.Config.QuickTerminalPosition,
|
quick_terminal_position: configpkg.Config.QuickTerminalPosition,
|
||||||
|
quick_terminal_autohide: bool,
|
||||||
|
|
||||||
maximize: bool,
|
maximize: bool,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
@ -98,6 +99,7 @@ pub const DerivedConfig = struct {
|
|||||||
.gtk_toolbar_style = config.@"gtk-toolbar-style",
|
.gtk_toolbar_style = config.@"gtk-toolbar-style",
|
||||||
|
|
||||||
.quick_terminal_position = config.@"quick-terminal-position",
|
.quick_terminal_position = config.@"quick-terminal-position",
|
||||||
|
.quick_terminal_autohide = config.@"quick-terminal-autohide",
|
||||||
|
|
||||||
.maximize = config.maximize,
|
.maximize = config.maximize,
|
||||||
.fullscreen = config.fullscreen,
|
.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::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::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
|
// 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.
|
// 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 {
|
pub fn toggleVisibility(self: *Window) void {
|
||||||
const window: *gtk.Widget = @ptrCast(self.window);
|
const window: *gtk.Widget = @ptrCast(self.window);
|
||||||
|
|
||||||
window.setVisible(@intFromBool(window.isVisible() == 0));
|
window.setVisible(@intFromBool(window.isVisible() == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isQuickTerminal(self: *Window) bool {
|
||||||
|
return self.app.quick_terminal == self;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn updateConfig(
|
pub fn updateConfig(
|
||||||
self: *Window,
|
self: *Window,
|
||||||
config: *const configpkg.Config,
|
config: *const configpkg.Config,
|
||||||
@ -421,7 +429,7 @@ pub fn syncAppearance(self: *Window) !void {
|
|||||||
if (!csd_enabled) break :visible false;
|
if (!csd_enabled) break :visible false;
|
||||||
|
|
||||||
// Never display the header bar as a quick terminal.
|
// 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.
|
// Unconditionally disable the header bar when fullscreened.
|
||||||
if (self.config.fullscreen) break :visible false;
|
if (self.config.fullscreen) break :visible false;
|
||||||
@ -472,12 +480,6 @@ pub fn syncAppearance(self: *Window) !void {
|
|||||||
self.winproto.syncAppearance() catch |err| {
|
self.winproto.syncAppearance() catch |err| {
|
||||||
log.warn("failed to sync winproto appearance error={}", .{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(
|
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
|
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
|
||||||
// sends an undefined value.
|
// sends an undefined value.
|
||||||
fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
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);
|
const window: *gtk.Window = @ptrCast(self.window);
|
||||||
|
|
||||||
// Unset the quick terminal on the app level
|
// 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();
|
window.destroy();
|
||||||
}
|
}
|
||||||
@ -813,9 +829,6 @@ fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
|||||||
log.debug("window close request", .{});
|
log.debug("window close request", .{});
|
||||||
const self = userdataSelf(ud.?);
|
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.
|
// If none of our surfaces need confirmation, we can just exit.
|
||||||
for (self.app.core_app.surfaces.items) |surface| {
|
for (self.app.core_app.surfaces.items) |surface| {
|
||||||
if (surface.container.window()) |window| {
|
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 {
|
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
inline else => |v| v.clientSideDecorationEnabled(),
|
inline else => |v| v.clientSideDecorationEnabled(),
|
||||||
|
@ -59,8 +59,6 @@ pub const Window = struct {
|
|||||||
|
|
||||||
pub fn syncAppearance(_: *Window) !void {}
|
pub fn syncAppearance(_: *Window) !void {}
|
||||||
|
|
||||||
pub fn syncQuickTerminal(_: *Window) !void {}
|
|
||||||
|
|
||||||
/// This returns true if CSD is enabled for this window. This
|
/// This returns true if CSD is enabled for this window. This
|
||||||
/// should be the actual present state of the window, not the
|
/// should be the actual present state of the window, not the
|
||||||
/// desired state.
|
/// desired state.
|
||||||
|
@ -27,6 +27,8 @@ pub const App = struct {
|
|||||||
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
// https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398
|
||||||
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
|
kde_decoration_manager: ?*org.KdeKwinServerDecorationManager = null,
|
||||||
|
|
||||||
|
kde_slide_manager: ?*org.KdeKwinSlideManager = null,
|
||||||
|
|
||||||
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,13 +119,26 @@ pub const App = struct {
|
|||||||
global,
|
global,
|
||||||
)) |blur_manager| {
|
)) |blur_manager| {
|
||||||
context.kde_blur_manager = blur_manager;
|
context.kde_blur_manager = blur_manager;
|
||||||
} else if (registryBind(
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registryBind(
|
||||||
org.KdeKwinServerDecorationManager,
|
org.KdeKwinServerDecorationManager,
|
||||||
registry,
|
registry,
|
||||||
global,
|
global,
|
||||||
)) |deco_manager| {
|
)) |deco_manager| {
|
||||||
context.kde_decoration_manager = deco_manager;
|
context.kde_decoration_manager = deco_manager;
|
||||||
deco_manager.setListener(*Context, decoManagerListener, context);
|
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.
|
/// of the window.
|
||||||
decoration: ?*org.KdeKwinServerDecoration,
|
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(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
app: *App,
|
app: *App,
|
||||||
@ -232,6 +251,7 @@ pub const Window = struct {
|
|||||||
.app_context = app.context,
|
.app_context = app.context,
|
||||||
.blur_token = null,
|
.blur_token = null,
|
||||||
.decoration = deco,
|
.decoration = deco,
|
||||||
|
.slide = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +259,7 @@ pub const Window = struct {
|
|||||||
_ = alloc;
|
_ = alloc;
|
||||||
if (self.blur_token) |blur| blur.release();
|
if (self.blur_token) |blur| blur.release();
|
||||||
if (self.decoration) |deco| deco.release();
|
if (self.decoration) |deco| deco.release();
|
||||||
|
if (self.slide) |slide| slide.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resizeEvent(_: *Window) !void {}
|
pub fn resizeEvent(_: *Window) !void {}
|
||||||
@ -250,6 +271,12 @@ pub const Window = struct {
|
|||||||
self.syncDecoration() catch |err| {
|
self.syncDecoration() catch |err| {
|
||||||
log.err("failed to sync blur={}", .{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 {
|
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;
|
if (comptime !build_options.layer_shell) return;
|
||||||
|
|
||||||
const window = self.apprt_window.window;
|
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),
|
.top, .bottom, .center => c.gtk_window_set_default_size(window, 800, 400),
|
||||||
.left, .right => c.gtk_window_set_default_size(window, 400, 800),
|
.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,
|
right = c.GTK_LAYER_SHELL_EDGE_RIGHT,
|
||||||
top = c.GTK_LAYER_SHELL_EDGE_TOP,
|
top = c.GTK_LAYER_SHELL_EDGE_TOP,
|
||||||
bottom = c.GTK_LAYER_SHELL_EDGE_BOTTOM,
|
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 {
|
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||||
return switch (self.config.window_decoration) {
|
return switch (self.config.window_decoration) {
|
||||||
.auto, .client => true,
|
.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
|
// 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/blur.xml"));
|
||||||
scanner.addCustomProtocol(plasma_wayland_protocols.path("src/protocols/server-decoration.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("wl_compositor", 1);
|
||||||
scanner.generate("org_kde_kwin_blur_manager", 1);
|
scanner.generate("org_kde_kwin_blur_manager", 1);
|
||||||
scanner.generate("org_kde_kwin_server_decoration_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("wayland", wayland);
|
||||||
step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));
|
step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));
|
||||||
|
@ -1637,7 +1637,8 @@ keybind: Keybinds = .{},
|
|||||||
/// * `right` - Terminal appears at the right of the screen.
|
/// * `right` - Terminal appears at the right of the screen.
|
||||||
/// * `center` - Terminal appears at the center 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.
|
/// Note: There is no default keybind for toggling the quick terminal.
|
||||||
/// To enable this feature, bind the `toggle_quick_terminal` action to a key.
|
/// 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
|
/// The default value is `main` because this is the recommended screen
|
||||||
/// by the operating system.
|
/// by the operating system.
|
||||||
|
///
|
||||||
|
/// Only implemented on macOS.
|
||||||
@"quick-terminal-screen": QuickTerminalScreen = .main,
|
@"quick-terminal-screen": QuickTerminalScreen = .main,
|
||||||
|
|
||||||
/// Duration (in seconds) of the quick terminal enter and exit animation.
|
/// Duration (in seconds) of the quick terminal enter and exit animation.
|
||||||
/// Set it to 0 to disable animation completely. This can be changed at
|
/// Set it to 0 to disable animation completely. This can be changed at
|
||||||
/// runtime.
|
/// runtime.
|
||||||
|
///
|
||||||
|
/// Only implemented on macOS.
|
||||||
@"quick-terminal-animation-duration": f64 = 0.2,
|
@"quick-terminal-animation-duration": f64 = 0.2,
|
||||||
|
|
||||||
/// Automatically hide the quick terminal when focus shifts to another window.
|
/// Automatically hide the quick terminal when focus shifts to another window.
|
||||||
@ -1687,6 +1692,9 @@ keybind: Keybinds = .{},
|
|||||||
/// space.
|
/// space.
|
||||||
///
|
///
|
||||||
/// The default value is `move`.
|
/// The default value is `move`.
|
||||||
|
///
|
||||||
|
/// Only implemented on macOS.
|
||||||
|
/// On Linux the behavior is always equivalent to `move`.
|
||||||
@"quick-terminal-space-behavior": QuickTerminalSpaceBehavior = .move,
|
@"quick-terminal-space-behavior": QuickTerminalSpaceBehavior = .move,
|
||||||
|
|
||||||
/// Whether to enable shell integration auto-injection or not. Shell integration
|
/// 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
|
/// See the various configurations for the quick terminal in the
|
||||||
/// configuration file to customize its behavior.
|
/// 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,
|
toggle_quick_terminal: void,
|
||||||
|
|
||||||
/// Show/hide all windows. If all windows become shown, we also ensure
|
/// Show/hide all windows. If all windows become shown, we also ensure
|
||||||
|
Reference in New Issue
Block a user