apprt/gtk-ng: winproto behaviors (quick terminal, csd/ssd, blur, etc.) (#8123)
This ports over the winproto behaviors to gtk-ng. The core winproto logic is unchanged except for trivial typing changes. The interaction with winproto is a bit different in ng due to the class separation of logic between surfaces and windows, but functionally the same. Ran against Valgrind and all looks good.pull/8128/head
commit
7836cc8f31
|
|
@ -549,6 +549,7 @@ pub const Application = extern struct {
|
|||
|
||||
.toggle_maximize => Action.toggleMaximize(target),
|
||||
.toggle_fullscreen => Action.toggleFullscreen(target),
|
||||
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
|
||||
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
||||
|
||||
// Unimplemented but todo on gtk-ng branch
|
||||
|
|
@ -562,7 +563,6 @@ pub const Application = extern struct {
|
|||
.goto_split,
|
||||
.toggle_split_zoom,
|
||||
// TODO: winproto
|
||||
.toggle_quick_terminal,
|
||||
.toggle_window_decorations,
|
||||
=> {
|
||||
log.warn("unimplemented action={}", .{action});
|
||||
|
|
@ -1477,7 +1477,14 @@ const Action = struct {
|
|||
parent: ?*CoreSurface,
|
||||
) !void {
|
||||
const win = Window.new(self);
|
||||
initAndShowWindow(self, win, parent);
|
||||
}
|
||||
|
||||
fn initAndShowWindow(
|
||||
self: *Application,
|
||||
win: *Window,
|
||||
parent: ?*CoreSurface,
|
||||
) void {
|
||||
// Setup a binding so that whenever our config changes so does the
|
||||
// window. There's never a time when the window config should be out
|
||||
// of sync with the application config.
|
||||
|
|
@ -1694,6 +1701,48 @@ const Action = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn toggleQuickTerminal(self: *Application) bool {
|
||||
// If we already have a quick terminal window, we just toggle the
|
||||
// visibility of it.
|
||||
if (getQuickTerminalWindow()) |win| {
|
||||
win.toggleVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we don't support quick terminals then we do nothing.
|
||||
const priv = self.private();
|
||||
if (!priv.winproto.supportsQuickTerminal()) return false;
|
||||
|
||||
// Create our new window as a quick terminal
|
||||
const win = gobject.ext.newInstance(Window, .{
|
||||
.application = self,
|
||||
.@"quick-terminal" = true,
|
||||
});
|
||||
assert(win.isQuickTerminal());
|
||||
initAndShowWindow(self, win, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn getQuickTerminalWindow() ?*Window {
|
||||
// Find a quick terminal window.
|
||||
const list = gtk.Window.listToplevels();
|
||||
defer list.free();
|
||||
if (ext.listFind(gtk.Window, list, struct {
|
||||
fn find(gtk_win: *gtk.Window) bool {
|
||||
const win = gobject.ext.cast(
|
||||
Window,
|
||||
gtk_win,
|
||||
) orelse return false;
|
||||
return win.isQuickTerminal();
|
||||
}
|
||||
}.find)) |w| return gobject.ext.cast(
|
||||
Window,
|
||||
w,
|
||||
).?;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn toggleMaximize(target: apprt.Target) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const Config = @import("config.zig").Config;
|
|||
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
|
||||
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
|
||||
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
|
||||
const Window = @import("window.zig").Window;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_surface);
|
||||
|
||||
|
|
@ -1061,8 +1062,6 @@ pub const Surface = extern struct {
|
|||
}
|
||||
|
||||
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
|
||||
_ = self;
|
||||
|
||||
const alloc = Application.default().allocator();
|
||||
var env = try internal_os.getEnvMap(alloc);
|
||||
errdefer env.deinit();
|
||||
|
|
@ -1099,6 +1098,14 @@ pub const Surface = extern struct {
|
|||
env.remove("GTK_PATH");
|
||||
}
|
||||
|
||||
// This is a hack because it ties ourselves (optionally) to the
|
||||
// Window class. The right solution we should do is emit a signal
|
||||
// here where the handler can modify our EnvMap, but boxing the
|
||||
// EnvMap is a bit annoying so I'm punting it.
|
||||
if (ext.getAncestor(Window, self.as(gtk.Widget))) |window| {
|
||||
try window.winproto().addSubprocessEnv(&env);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
|
@ -15,6 +16,7 @@ const ext = @import("../ext.zig");
|
|||
const gtk_version = @import("../gtk_version.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const winprotopkg = @import("../winproto.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
|
|
@ -131,6 +133,26 @@ pub const Window = extern struct {
|
|||
);
|
||||
};
|
||||
|
||||
pub const @"quick-terminal" = struct {
|
||||
pub const name = "quick-terminal";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.nick = "Quick Terminal",
|
||||
.blurb = "Whether this window behaves like a quick terminal.",
|
||||
.default = true,
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"quick_terminal",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"tabs-autohide" = struct {
|
||||
pub const name = "tabs-autohide";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
|
|
@ -205,12 +227,19 @@ pub const Window = extern struct {
|
|||
};
|
||||
|
||||
const Private = struct {
|
||||
/// Whether this window is a quick terminal. If it is then it
|
||||
/// behaves slightly differently under certain scenarios.
|
||||
quick_terminal: bool = false,
|
||||
|
||||
/// Binding group for our active tab.
|
||||
tab_bindings: *gobject.BindingGroup,
|
||||
|
||||
/// The configuration that this surface is using.
|
||||
config: ?*Config = null,
|
||||
|
||||
/// State and logic for windowing protocol for a window.
|
||||
winproto: winprotopkg.Window,
|
||||
|
||||
/// Kind of hacky to have this but this lets us know if we've
|
||||
/// initialized any single surface yet. We need this because we
|
||||
/// gate default size on this so that we don't resize the window
|
||||
|
|
@ -253,6 +282,10 @@ pub const Window = extern struct {
|
|||
priv.config = app.getConfig();
|
||||
}
|
||||
|
||||
// We initialize our windowing protocol to none because we can't
|
||||
// actually initialize this until we get realized.
|
||||
priv.winproto = .none;
|
||||
|
||||
// Add our dev CSS class if we're in debug mode.
|
||||
if (comptime build_config.is_debug) {
|
||||
self.as(gtk.Widget).addCssClass("devel");
|
||||
|
|
@ -270,6 +303,24 @@ pub const Window = extern struct {
|
|||
// Initialize our actions
|
||||
self.initActionMap();
|
||||
|
||||
// We need to setup resize notifications on our surface
|
||||
if (self.as(gtk.Native).getSurface()) |gdk_surface| {
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
gdk_surface,
|
||||
*Self,
|
||||
propGdkSurfaceWidth,
|
||||
self,
|
||||
.{ .detail = "width" },
|
||||
);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
gdk_surface,
|
||||
*Self,
|
||||
propGdkSurfaceHeight,
|
||||
self,
|
||||
.{ .detail = "height" },
|
||||
);
|
||||
}
|
||||
|
||||
// We always sync our appearance at the end because loading our
|
||||
// config and such can affect our bindings which ar setup initially
|
||||
// in initTemplate.
|
||||
|
|
@ -312,6 +363,11 @@ pub const Window = extern struct {
|
|||
}
|
||||
}
|
||||
|
||||
/// Winproto backend for this window.
|
||||
pub fn winproto(self: *Self) *winprotopkg.Window {
|
||||
return &self.private().winproto;
|
||||
}
|
||||
|
||||
/// Create a new tab with the given parent. The tab will be inserted
|
||||
/// at the position dictated by the `window-new-tab-position` config.
|
||||
/// The new tab will be selected.
|
||||
|
|
@ -466,12 +522,28 @@ pub const Window = extern struct {
|
|||
tab_overview.setOpen(@intFromBool(!is_open));
|
||||
}
|
||||
|
||||
/// Toggle the visible property.
|
||||
pub fn toggleVisibility(self: *Self) void {
|
||||
const widget = self.as(gtk.Widget);
|
||||
widget.setVisible(@intFromBool(widget.isVisible() == 0));
|
||||
}
|
||||
|
||||
/// Updates various appearance properties. This should always be safe
|
||||
/// to call multiple times. This should be called whenever a change
|
||||
/// happens that might affect how the window appears (config change,
|
||||
/// fullscreen, etc.).
|
||||
fn syncAppearance(self: *Self) void {
|
||||
// TODO: CSD/SSD
|
||||
const priv = self.private();
|
||||
const csd_enabled = priv.winproto.clientSideDecorationEnabled();
|
||||
self.as(gtk.Window).setDecorated(@intFromBool(csd_enabled));
|
||||
|
||||
// Fix any artifacting that may occur in window corners. The .ssd CSS
|
||||
// class is defined in the GtkWindow documentation:
|
||||
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
|
||||
// for .ssd is provided by GTK and Adwaita.
|
||||
self.toggleCssClass("csd", csd_enabled);
|
||||
self.toggleCssClass("ssd", !csd_enabled);
|
||||
self.toggleCssClass("no-border-radius", !csd_enabled);
|
||||
|
||||
// Trigger all our dynamic properties that depend on the config.
|
||||
inline for (&.{
|
||||
|
|
@ -488,15 +560,28 @@ pub const Window = extern struct {
|
|||
}
|
||||
|
||||
// Remainder uses the config
|
||||
const priv = self.private();
|
||||
const config = if (priv.config) |v| v.get() else return;
|
||||
|
||||
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
||||
// GTK version is before 4.16. The conditional is because above 4.16
|
||||
// we use GTK CSS color variables.
|
||||
self.toggleCssClass(
|
||||
"window-theme-ghostty",
|
||||
!gtk_version.atLeast(4, 16, 0) and
|
||||
config.@"window-theme" == .ghostty,
|
||||
);
|
||||
|
||||
// Move the tab bar to the proper location.
|
||||
priv.toolbar.remove(priv.tab_bar.as(gtk.Widget));
|
||||
switch (config.@"gtk-tabs-location") {
|
||||
.top => priv.toolbar.addTopBar(priv.tab_bar.as(gtk.Widget)),
|
||||
.bottom => priv.toolbar.addBottomBar(priv.tab_bar.as(gtk.Widget)),
|
||||
}
|
||||
|
||||
// Do our window-protocol specific appearance sync.
|
||||
priv.winproto.syncAppearance() catch |err| {
|
||||
log.warn("failed to sync winproto appearance error={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn toggleCssClass(self: *Self, class: [:0]const u8, value: bool) void {
|
||||
|
|
@ -535,6 +620,11 @@ pub const Window = extern struct {
|
|||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
/// Whether this terminal is a quick terminal or not.
|
||||
pub fn isQuickTerminal(self: *Self) bool {
|
||||
return self.private().quick_terminal;
|
||||
}
|
||||
|
||||
/// Get the currently active surface. See the "active-surface" property.
|
||||
/// This does not ref the value.
|
||||
fn getActiveSurface(self: *Self) ?*Surface {
|
||||
|
|
@ -542,6 +632,12 @@ pub const Window = extern struct {
|
|||
return tab.getActiveSurface();
|
||||
}
|
||||
|
||||
/// Returns the configuration for this window. The reference count
|
||||
/// is not increased.
|
||||
pub fn getConfig(self: *Self) ?*Config {
|
||||
return self.private().config;
|
||||
}
|
||||
|
||||
/// Get the currently selected tab as a Tab object.
|
||||
fn getSelectedTab(self: *Self) ?*Tab {
|
||||
const priv = self.private();
|
||||
|
|
@ -571,8 +667,14 @@ pub const Window = extern struct {
|
|||
}
|
||||
|
||||
fn getHeaderbarVisible(self: *Self) bool {
|
||||
// TODO: CSD/SSD
|
||||
// TODO: QuickTerminal
|
||||
const priv = self.private();
|
||||
|
||||
// Never display the header bar when CSDs are disabled.
|
||||
const csd_enabled = priv.winproto.clientSideDecorationEnabled();
|
||||
if (!csd_enabled) return false;
|
||||
|
||||
// Never display the header bar as a quick terminal.
|
||||
if (priv.quick_terminal) return false;
|
||||
|
||||
// If we're fullscreen we never show the header bar.
|
||||
if (self.as(gtk.Window).isFullscreen() != 0) return false;
|
||||
|
|
@ -648,6 +750,36 @@ pub const Window = extern struct {
|
|||
self.syncAppearance();
|
||||
}
|
||||
|
||||
fn propGdkSurfaceHeight(
|
||||
_: *gdk.Surface,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// X11 needs to fix blurring on resize, but winproto implementations
|
||||
// could do anything.
|
||||
self.private().winproto.resizeEvent() catch |err| {
|
||||
log.warn(
|
||||
"winproto resize event failed error={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn propGdkSurfaceWidth(
|
||||
_: *gdk.Surface,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// X11 needs to fix blurring on resize, but winproto implementations
|
||||
// could do anything.
|
||||
self.private().winproto.resizeEvent() catch |err| {
|
||||
log.warn(
|
||||
"winproto resize event failed error={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn propFullscreened(
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
|
|
@ -696,6 +828,26 @@ pub const Window = extern struct {
|
|||
action.setEnabled(@intFromBool(has_selection));
|
||||
}
|
||||
|
||||
fn propQuickTerminal(
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
if (priv.surface_init) {
|
||||
log.warn("quick terminal property can't be changed after surfaces have been initialized", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv.quick_terminal) {
|
||||
// Initialize the quick terminal at the app-layer
|
||||
Application.default().winproto().initQuickTerminal(self) catch |err| {
|
||||
log.warn("failed to initialize quick terminal error={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or remove "background" CSS class depending on if the background
|
||||
/// should be opaque.
|
||||
fn propBackgroundOpaque(
|
||||
|
|
@ -706,6 +858,24 @@ pub const Window = extern struct {
|
|||
self.toggleCssClass("background", self.getBackgroundOpaque());
|
||||
}
|
||||
|
||||
fn propScaleFactor(
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// On some platforms (namely X11) we need to refresh our appearance when
|
||||
// the scale factor changes. In theory this could be more fine-grained as
|
||||
// a full refresh could be expensive, but a) this *should* be rare, and
|
||||
// b) quite noticeable visual bugs would occur if this is not present.
|
||||
self.private().winproto.syncAppearance() catch |err| {
|
||||
log.warn(
|
||||
"failed to sync appearance after scale factor has been updated={}",
|
||||
.{err},
|
||||
);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual methods
|
||||
|
||||
|
|
@ -731,6 +901,7 @@ pub const Window = extern struct {
|
|||
fn finalize(self: *Self) callconv(.C) void {
|
||||
const priv = self.private();
|
||||
priv.tab_bindings.unref();
|
||||
priv.winproto.deinit(Application.default().allocator());
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
|
|
@ -741,6 +912,26 @@ pub const Window = extern struct {
|
|||
//---------------------------------------------------------------
|
||||
// Signal handlers
|
||||
|
||||
fn windowRealize(_: *gtk.Widget, self: *Window) callconv(.c) void {
|
||||
const app = Application.default();
|
||||
|
||||
// Initialize our window protocol logic
|
||||
if (winprotopkg.Window.init(
|
||||
app.allocator(),
|
||||
app.winproto(),
|
||||
self,
|
||||
)) |wp| {
|
||||
self.private().winproto = wp;
|
||||
} else |err| {
|
||||
log.warn("failed to initialize window protocol error={}", .{err});
|
||||
return;
|
||||
}
|
||||
|
||||
// When we are realized we always setup our appearance since this
|
||||
// calls some winproto functions.
|
||||
self.syncAppearance();
|
||||
}
|
||||
|
||||
fn btnNewTab(_: *adw.SplitButton, self: *Self) callconv(.c) void {
|
||||
self.performBindingAction(.new_tab);
|
||||
}
|
||||
|
|
@ -1362,6 +1553,7 @@ pub const Window = extern struct {
|
|||
properties.config.impl,
|
||||
properties.debug.impl,
|
||||
properties.@"headerbar-visible".impl,
|
||||
properties.@"quick-terminal".impl,
|
||||
properties.@"tabs-autohide".impl,
|
||||
properties.@"tabs-visible".impl,
|
||||
properties.@"tabs-wide".impl,
|
||||
|
|
@ -1376,6 +1568,7 @@ pub const Window = extern struct {
|
|||
class.bindTemplateChildPrivate("toast_overlay", .{});
|
||||
|
||||
// Template Callbacks
|
||||
class.bindTemplateCallback("realize", &windowRealize);
|
||||
class.bindTemplateCallback("new_tab", &btnNewTab);
|
||||
class.bindTemplateCallback("overview_create_tab", &tabOverviewCreateTab);
|
||||
class.bindTemplateCallback("overview_notify_open", &tabOverviewOpen);
|
||||
|
|
@ -1386,11 +1579,13 @@ pub const Window = extern struct {
|
|||
class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow);
|
||||
class.bindTemplateCallback("notify_n_pages", &tabViewNPages);
|
||||
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
|
||||
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
|
||||
class.bindTemplateCallback("notify_config", &propConfig);
|
||||
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
|
||||
class.bindTemplateCallback("notify_maximized", &propMaximized);
|
||||
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
|
||||
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
|
||||
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
|
||||
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@
|
|||
* https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles
|
||||
*/
|
||||
|
||||
window.ssd.no-border-radius {
|
||||
/* Without clearing the border radius, at least on Mutter with
|
||||
* gtk-titlebar=true and gtk-adwaita=false, there is some window artifacting
|
||||
* that this will mitigate.
|
||||
*/
|
||||
border-radius: 0 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* GhosttySurface URL overlay
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
|
|
@ -23,6 +24,24 @@ pub fn boxedFree(comptime T: type, ptr: ?*T) void {
|
|||
);
|
||||
}
|
||||
|
||||
/// A wrapper around `glib.List.findCustom` to find an element in the list.
|
||||
/// The type `T` must be the guaranteed type of every list element.
|
||||
pub fn listFind(
|
||||
comptime T: type,
|
||||
list: *glib.List,
|
||||
comptime func: *const fn (*T) bool,
|
||||
) ?*T {
|
||||
const elem_: ?*glib.List = list.findCustom(null, struct {
|
||||
fn callback(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int {
|
||||
const ptr = data orelse return 1;
|
||||
const v: *T = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
return if (func(v)) 0 else 1;
|
||||
}
|
||||
}.callback);
|
||||
const elem = elem_ orelse return null;
|
||||
return @ptrCast(@alignCast(elem.f_data));
|
||||
}
|
||||
|
||||
/// Wrapper around `gtk.Widget.getAncestor` to get the widget ancestor
|
||||
/// of the given type `T`, or null if it doesn't exist.
|
||||
pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
|||
]
|
||||
|
||||
close-request => $close_request();
|
||||
realize => $realize();
|
||||
notify::background-opaque => $notify_background_opaque();
|
||||
notify::config => $notify_config();
|
||||
notify::fullscreened => $notify_fullscreened();
|
||||
notify::maximized => $notify_maximized();
|
||||
notify::background-opaque => $notify_background_opaque();
|
||||
notify::quick-terminal => $notify_quick_terminal();
|
||||
notify::scale-factor => $notify_scale_factor();
|
||||
default-width: 800;
|
||||
default-height: 600;
|
||||
// GTK4 grabs F10 input by default to focus the menubar icon. We want
|
||||
|
|
@ -20,8 +23,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
|||
content: Adw.TabOverview tab_overview {
|
||||
create-tab => $overview_create_tab();
|
||||
notify::open => $overview_notify_open();
|
||||
enable-new-tab: true;
|
||||
view: tab_view;
|
||||
enable-new-tab: true;
|
||||
// Disable the title buttons (close, maximize, minimize, ...)
|
||||
// *inside* the tab overview if CSDs are disabled.
|
||||
// We do spare the search button, though.
|
||||
show-start-title-buttons: bind template.decorated;
|
||||
show-end-title-buttons: bind template.decorated;
|
||||
|
||||
Adw.ToolbarView toolbar {
|
||||
top-bar-style: bind template.toolbar-style;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ const gdk = @import("gdk");
|
|||
const Config = @import("../../config.zig").Config;
|
||||
const input = @import("../../input.zig");
|
||||
const key = @import("key.zig");
|
||||
|
||||
// TODO: As we get to these APIs the compiler should tell us
|
||||
const ApprtWindow = void;
|
||||
const ApprtWindow = @import("class/window.zig").Window;
|
||||
|
||||
pub const noop = @import("winproto/noop.zig");
|
||||
pub const x11 = @import("winproto/x11.zig");
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const gdk = @import("gdk");
|
|||
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const input = @import("../../../input.zig");
|
||||
const ApprtWindow = void; // TODO: fix
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
|
||||
const log = std.log.scoped(.winproto_noop);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const wayland = @import("wayland");
|
|||
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const input = @import("../../../input.zig");
|
||||
const ApprtWindow = void; // TODO: fix
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
|
||||
const wl = wayland.client.wl;
|
||||
const org = wayland.client.org;
|
||||
|
|
@ -127,7 +127,7 @@ pub const App = struct {
|
|||
}
|
||||
|
||||
pub fn initQuickTerminal(_: *App, apprt_window: *ApprtWindow) !void {
|
||||
const window = apprt_window.window.as(gtk.Window);
|
||||
const window = apprt_window.as(gtk.Window);
|
||||
|
||||
layer_shell.initForWindow(window);
|
||||
layer_shell.setLayer(window, .top);
|
||||
|
|
@ -257,7 +257,7 @@ pub const Window = struct {
|
|||
) !Window {
|
||||
_ = alloc;
|
||||
|
||||
const gtk_native = apprt_window.window.as(gtk.Native);
|
||||
const gtk_native = apprt_window.as(gtk.Native);
|
||||
const gdk_surface = gtk_native.getSurface() orelse return error.NotWaylandSurface;
|
||||
|
||||
// This should never fail, because if we're being called at this point
|
||||
|
|
@ -364,7 +364,11 @@ pub const Window = struct {
|
|||
/// Update the blur state of the window.
|
||||
fn syncBlur(self: *Window) !void {
|
||||
const manager = self.app_context.kde_blur_manager orelse return;
|
||||
const blur = self.apprt_window.config.background_blur;
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return;
|
||||
const blur = config.@"background-blur";
|
||||
|
||||
if (self.blur_token) |tok| {
|
||||
// Only release token when transitioning from blurred -> not blurred
|
||||
|
|
@ -392,7 +396,12 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
fn getDecorationMode(self: Window) org.KdeKwinServerDecorationManager.Mode {
|
||||
return switch (self.apprt_window.config.window_decoration) {
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return .Client;
|
||||
|
||||
return switch (config.@"window-decoration") {
|
||||
.auto => self.app_context.default_deco_mode orelse .Client,
|
||||
.client => .Client,
|
||||
.server => .Server,
|
||||
|
|
@ -401,12 +410,15 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
fn syncQuickTerminal(self: *Window) !void {
|
||||
const window = self.apprt_window.window.as(gtk.Window);
|
||||
const config = &self.apprt_window.config;
|
||||
const window = self.apprt_window.as(gtk.Window);
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return;
|
||||
|
||||
layer_shell.setKeyboardMode(
|
||||
window,
|
||||
switch (config.quick_terminal_keyboard_interactivity) {
|
||||
switch (config.@"quick-terminal-keyboard-interactivity") {
|
||||
.none => .none,
|
||||
.@"on-demand" => on_demand: {
|
||||
if (layer_shell.getProtocolVersion() < 4) {
|
||||
|
|
@ -419,7 +431,7 @@ pub const Window = struct {
|
|||
},
|
||||
);
|
||||
|
||||
const anchored_edge: ?layer_shell.ShellEdge = switch (config.quick_terminal_position) {
|
||||
const anchored_edge: ?layer_shell.ShellEdge = switch (config.@"quick-terminal-position") {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.top => .top,
|
||||
|
|
@ -470,14 +482,14 @@ pub const Window = struct {
|
|||
monitor: *gdk.Monitor,
|
||||
apprt_window: *ApprtWindow,
|
||||
) callconv(.c) void {
|
||||
const window = apprt_window.window.as(gtk.Window);
|
||||
const config = &apprt_window.config;
|
||||
const window = apprt_window.as(gtk.Window);
|
||||
const config = if (apprt_window.getConfig()) |v| v.get() else return;
|
||||
|
||||
var monitor_size: gdk.Rectangle = undefined;
|
||||
monitor.getGeometry(&monitor_size);
|
||||
|
||||
const dims = config.quick_terminal_size.calculate(
|
||||
config.quick_terminal_position,
|
||||
const dims = config.@"quick-terminal-size".calculate(
|
||||
config.@"quick-terminal-position",
|
||||
.{
|
||||
.width = @intCast(monitor_size.f_width),
|
||||
.height = @intCast(monitor_size.f_height),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub const c = @cImport({
|
|||
|
||||
const input = @import("../../../input.zig");
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const ApprtWindow = void; // TODO: fix
|
||||
const ApprtWindow = @import("../class/window.zig").Window;
|
||||
|
||||
const log = std.log.scoped(.gtk_x11);
|
||||
|
||||
|
|
@ -170,8 +170,7 @@ pub const App = struct {
|
|||
|
||||
pub const Window = struct {
|
||||
app: *App,
|
||||
config: *const ApprtWindow.DerivedConfig,
|
||||
gtk_window: *adw.ApplicationWindow,
|
||||
apprt_window: *ApprtWindow,
|
||||
x11_surface: *gdk_x11.X11Surface,
|
||||
|
||||
blur_region: Region = .{},
|
||||
|
|
@ -183,9 +182,8 @@ pub const Window = struct {
|
|||
) !Window {
|
||||
_ = alloc;
|
||||
|
||||
const surface = apprt_window.window.as(
|
||||
gtk.Native,
|
||||
).getSurface() orelse return error.NotX11Surface;
|
||||
const surface = apprt_window.as(gtk.Native).getSurface() orelse
|
||||
return error.NotX11Surface;
|
||||
|
||||
const x11_surface = gobject.ext.cast(
|
||||
gdk_x11.X11Surface,
|
||||
|
|
@ -194,8 +192,7 @@ pub const Window = struct {
|
|||
|
||||
return .{
|
||||
.app = app,
|
||||
.config = &apprt_window.config,
|
||||
.gtk_window = apprt_window.window,
|
||||
.apprt_window = apprt_window,
|
||||
.x11_surface = x11_surface,
|
||||
};
|
||||
}
|
||||
|
|
@ -221,10 +218,10 @@ pub const Window = struct {
|
|||
var x: f64 = 0;
|
||||
var y: f64 = 0;
|
||||
|
||||
self.gtk_window.as(gtk.Native).getSurfaceTransform(&x, &y);
|
||||
self.apprt_window.as(gtk.Native).getSurfaceTransform(&x, &y);
|
||||
|
||||
// Transform surface coordinates to device coordinates.
|
||||
const scale: f64 = @floatFromInt(self.gtk_window.as(gtk.Widget).getScaleFactor());
|
||||
const scale: f64 = @floatFromInt(self.apprt_window.as(gtk.Widget).getScaleFactor());
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
|
||||
|
|
@ -242,7 +239,12 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return switch (self.config.window_decoration) {
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return true;
|
||||
|
||||
return switch (config.@"window-decoration") {
|
||||
.auto, .client => true,
|
||||
.server, .none => false,
|
||||
};
|
||||
|
|
@ -257,14 +259,15 @@ pub const Window = struct {
|
|||
// and I think it's not really noticeable enough to justify the effort.
|
||||
// (Wayland also has this visual artifact anyway...)
|
||||
|
||||
const gtk_widget = self.gtk_window.as(gtk.Widget);
|
||||
const gtk_widget = self.apprt_window.as(gtk.Widget);
|
||||
const config = if (self.apprt_window.getConfig()) |v| v.get() else return;
|
||||
|
||||
// Transform surface coordinates to device coordinates.
|
||||
const scale = self.gtk_window.as(gtk.Widget).getScaleFactor();
|
||||
const scale = gtk_widget.getScaleFactor();
|
||||
self.blur_region.width = gtk_widget.getWidth() * scale;
|
||||
self.blur_region.height = gtk_widget.getHeight() * scale;
|
||||
|
||||
const blur = self.config.background_blur;
|
||||
const blur = config.@"background-blur";
|
||||
log.debug("set blur={}, window xid={}, region={}", .{
|
||||
blur,
|
||||
self.x11_surface.getXid(),
|
||||
|
|
@ -286,6 +289,11 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
fn syncDecorations(self: *Window) !void {
|
||||
const config = if (self.apprt_window.getConfig()) |v|
|
||||
v.get()
|
||||
else
|
||||
return;
|
||||
|
||||
var hints: MotifWMHints = .{};
|
||||
|
||||
self.getWindowProperty(
|
||||
|
|
@ -306,7 +314,7 @@ pub const Window = struct {
|
|||
};
|
||||
|
||||
hints.flags.decorations = true;
|
||||
hints.decorations.all = switch (self.config.window_decoration) {
|
||||
hints.decorations.all = switch (config.@"window-decoration") {
|
||||
.server => true,
|
||||
.auto, .client, .none => false,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue