Merge remote-tracking branch 'upstream/main' into jacob/uucode

pull/8757/head
Jacob Sandlund 2025-07-28 08:18:57 -04:00
commit 1c445fae9b
18 changed files with 366 additions and 131 deletions

View File

@ -42,7 +42,7 @@ jobs:
/nix
/zig
- name: Setup Nix
uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -57,7 +57,7 @@ jobs:
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -211,7 +211,7 @@ jobs:
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -89,7 +89,7 @@ jobs:
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
@ -130,7 +130,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -112,7 +112,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -164,7 +164,7 @@ jobs:
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -384,7 +384,7 @@ jobs:
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -564,7 +564,7 @@ jobs:
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -77,7 +77,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -108,7 +108,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -144,7 +144,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -173,7 +173,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -206,7 +206,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -250,7 +250,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -279,7 +279,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -362,7 +362,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -516,7 +516,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -561,7 +561,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -610,7 +610,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -658,7 +658,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -678,7 +678,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -711,7 +711,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -739,7 +739,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -766,7 +766,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -793,7 +793,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -820,7 +820,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -847,7 +847,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -881,7 +881,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -908,7 +908,7 @@ jobs:
path: |
/nix
/zig
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -945,7 +945,7 @@ jobs:
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
@ -1002,7 +1002,7 @@ jobs:
/nix
/zig
- name: Setup Nix
uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -29,7 +29,7 @@ jobs:
/zig
- name: Setup Nix
uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16

View File

@ -46,6 +46,84 @@ pub fn Common(
}
}).private else {};
/// A helper that can be used to create a property that reads and
/// writes a private boxed gobject field type.
///
/// Reading the property will result in allocating a pointer and
/// setting it will free the previous pointer.
///
/// The object class (Self) must still free the private field
/// in finalize!
pub fn privateBoxedFieldAccessor(
comptime name: []const u8,
) gobject.ext.Accessor(
Self,
@FieldType(Private.?, name),
) {
return .{
.getter = &struct {
fn get(self: *Self, value: *gobject.Value) void {
gobject.ext.Value.set(
value,
@field(private(self), name),
);
}
}.get,
.setter = &struct {
fn set(self: *Self, value: *const gobject.Value) void {
const priv = private(self);
if (@field(priv, name)) |v| {
glib.ext.destroy(v);
}
const T = @TypeOf(@field(priv, name));
@field(
priv,
name,
) = gobject.ext.Value.dup(value, T);
}
}.set,
};
}
/// A helper that can be used to create a property that reads and
/// writes a private field gobject field type (reference counted).
///
/// Reading the property will result in taking a reference to the
/// value and writing the property will unref the previous value.
///
/// The object class (Self) must still free the private field
/// in finalize!
pub fn privateObjFieldAccessor(
comptime name: []const u8,
) gobject.ext.Accessor(
Self,
@FieldType(Private.?, name),
) {
return .{
.getter = &struct {
fn get(self: *Self, value: *gobject.Value) void {
gobject.ext.Value.set(
value,
@field(private(self), name),
);
}
}.get,
.setter = &struct {
fn set(self: *Self, value: *const gobject.Value) void {
const priv = private(self);
if (@field(priv, name)) |v| v.unref();
const T = @TypeOf(@field(priv, name));
@field(
priv,
name,
) = gobject.ext.Value.dup(value, T);
}
}.set,
};
}
/// A helper that can be used to create a property that reads and
/// writes a private `?[:0]const u8` field type.
///

View File

@ -361,11 +361,15 @@ pub const Application = extern struct {
//
// https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302
const priv = self.private();
const config = priv.config.get();
if (config.@"initial-window") switch (config.@"launched-from".?) {
.desktop, .cli => self.as(gio.Application).activate(),
.dbus, .systemd => {},
};
{
// We need to scope any config access because once we run our
// event loop, this can change out from underneath us.
const config = priv.config.get();
if (config.@"initial-window") switch (config.@"launched-from".?) {
.desktop, .cli => self.as(gio.Application).activate(),
.dbus, .systemd => {},
};
}
// If we are NOT the primary instance, then we never want to run.
// This means that another instance of the GTK app is running and
@ -393,6 +397,7 @@ pub const Application = extern struct {
// Check if we must quit based on the current state.
const must_quit = q: {
// If we are configured to always stay running, don't quit.
const config = priv.config.get();
if (!config.@"quit-after-last-window-closed") break :q false;
// If the quit timer has expired, quit.
@ -508,6 +513,8 @@ pub const Application = extern struct {
.progress_report => return Action.progressReport(target, value),
.reload_config => try Action.reloadConfig(self, target, value),
.render => Action.render(target),
.ring_bell => Action.ringBell(target),
@ -530,7 +537,6 @@ pub const Application = extern struct {
.equalize_splits,
.goto_split,
.open_config,
.reload_config,
.inspector,
.desktop_notification,
.present_terminal,
@ -573,29 +579,6 @@ pub const Application = extern struct {
return true;
}
/// Reload the configuration for the application and propagate it
/// across the entire application and all terminals.
pub fn reloadConfig(self: *Self) !void {
const alloc = self.allocator();
// Read our new config. We can always deinit this because
// we'll clone and store it if libghostty accepts it and
// emits a `config_change` action.
var config = try CoreConfig.load(alloc);
defer config.deinit();
// Notify the app that we've updated.
const priv = self.private();
try priv.core_app.updateConfig(priv.rt_app, &config);
}
/// Returns the configuration for this application.
///
/// The reference count is increased.
pub fn getConfig(self: *Self) *Config {
return self.private().config.ref();
}
/// Returns the core app associated with this application. This is
/// not a reference-counted type so you should not store this.
pub fn core(self: *Self) *CoreApp {
@ -662,6 +645,31 @@ pub const Application = extern struct {
}
}
//---------------------------------------------------------------
// Properties
/// Returns the configuration for this application.
///
/// The reference count is increased.
pub fn getConfig(self: *Self) *Config {
return self.private().config.ref();
}
/// Set the configuration for this application. The reference count
/// is increased on the new configuration and the old one is
/// unreferenced.
///
/// If the config has errors this may show the config errors dialog.
fn setConfig(self: *Self, config: *Config) void {
const priv = self.private();
priv.config.unref();
priv.config = config.ref();
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
// Show our errors if we have any
self.showConfigErrorsDialog();
}
//---------------------------------------------------------------
// Libghostty Callbacks
@ -794,9 +802,10 @@ pub const Application = extern struct {
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{
.{ "quit", actionQuit, null },
.{ "new-window", actionNewWindow, null },
.{ "new-window-command", actionNewWindow, as_variant_type },
.{ "quit", actionQuit, null },
.{ "reload-config", actionReloadConfig, null },
};
const action_map = self.as(gio.ActionMap);
@ -961,7 +970,12 @@ pub const Application = extern struct {
const priv = self.private();
priv.config_errors_dialog.set(null);
self.reloadConfig() catch |err| {
// Reload our config as if the app reloaded.
Action.reloadConfig(
self,
.app,
.{},
) catch |err| {
// If we fail to reload the configuration, then we want the
// user to know it. For now we log but we should show another
// GUI.
@ -1016,6 +1030,17 @@ pub const Application = extern struct {
dialog.present(null);
}
fn actionReloadConfig(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
const priv = self.private();
priv.core_app.performAction(self.rt(), .reload_config) catch |err| {
log.warn("error reloading config err={}", .{err});
};
}
fn actionQuit(
_: *gio.SimpleAction,
_: ?*glib.Variant,
@ -1138,21 +1163,11 @@ const Action = struct {
// Wrap our config in a GObject. This will clone it.
const alloc = self.allocator();
const config_obj: *Config = try .new(alloc, new_config);
errdefer config_obj.unref();
defer config_obj.unref();
switch (target) {
// TODO: when we implement surfaces in gtk-ng
.surface => @panic("TODO"),
.app => {
// Set it on our private
const priv = self.private();
priv.config.unref();
priv.config = config_obj;
// Show our errors if we have any
self.showConfigErrorsDialog();
},
.surface => |core| core.rt_surface.surface.setConfig(config_obj),
.app => self.setConfig(config_obj),
}
}
@ -1219,6 +1234,19 @@ const Action = struct {
parent: ?*CoreSurface,
) !void {
const win = Window.new(self, parent);
// 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.
_ = gobject.Object.bindProperty(
self.as(gobject.Object),
"config",
win.as(gobject.Object),
"config",
.{},
);
// Show the window
gtk.Window.present(win.as(gtk.Window));
}
@ -1263,6 +1291,47 @@ const Action = struct {
};
}
/// Reload the configuration for the application and propagate it
/// across the entire application and all terminals.
pub fn reloadConfig(
self: *Application,
target: apprt.Target,
opts: apprt.action.ReloadConfig,
) !void {
// Tell systemd that reloading has started.
systemd.notify.reloading();
// When we exit this function tell systemd that reloading has finished.
defer systemd.notify.ready();
// Get our config object.
const config: *Config = config: {
// Soft-reloading applies conditional logic to the existing loaded
// config so we return that as-is (but take a reference).
if (opts.soft) {
break :config self.private().config.ref();
}
// Hard reload, load a new config completely.
const alloc = self.allocator();
var config = try CoreConfig.load(alloc);
defer config.deinit();
break :config try .new(alloc, &config);
};
defer config.unref();
// Update the proper target. This will trigger a `confige_change`
// apprt action which will propagate the config properly to our
// property system.
switch (target) {
.app => try self.core().updateConfig(
self.rt(),
config.get(),
),
.surface => |core| try core.updateConfig(config.get()),
}
}
pub fn render(target: apprt.Target) void {
switch (target) {
.app => {},

View File

@ -59,12 +59,7 @@ pub const ClipboardConfirmationDialog = extern struct {
.{
.nick = "Request",
.blurb = "The clipboard request.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"request",
),
.accessor = C.privateBoxedFieldAccessor("request"),
},
);
};
@ -78,12 +73,7 @@ pub const ClipboardConfirmationDialog = extern struct {
.{
.nick = "Clipboard Contents",
.blurb = "The clipboard contents being read/written.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"clipboard_contents",
),
.accessor = C.privateObjFieldAccessor("clipboard_contents"),
},
);
};

View File

@ -50,12 +50,7 @@ pub const Surface = extern struct {
.{
.nick = "Config",
.blurb = "The configuration that this surface is using.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"config",
),
.accessor = C.privateObjFieldAccessor("config"),
},
);
};
@ -89,12 +84,7 @@ pub const Surface = extern struct {
.{
.nick = "Desired Font Size",
.blurb = "The desired font size, only affects initialization.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"font_size_request",
),
.accessor = C.privateBoxedFieldAccessor("font_size_request"),
},
);
};
@ -275,7 +265,10 @@ pub const Surface = extern struct {
const impl = gobject.ext.defineSignal(
name,
Self,
&.{},
&.{
apprt.Clipboard,
[*:0]const u8,
},
void,
);
};
@ -1190,6 +1183,14 @@ pub const Surface = extern struct {
return self.private().pwd;
}
/// Change the configuration for this surface.
pub fn setConfig(self: *Self, config: *Config) void {
const priv = self.private();
if (priv.config) |c| c.unref();
priv.config = config.ref();
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
}
fn propConfig(
self: *Self,
_: *gobject.ParamSpec,
@ -2236,7 +2237,7 @@ const Clipboard = struct {
Surface.signals.@"clipboard-write".impl.emit(
self,
null,
.{},
.{ clipboard_type, val.ptr },
null,
);

View File

@ -42,12 +42,7 @@ const SurfaceChildExitedBanner = extern struct {
.{
.nick = "Data",
.blurb = "The child exit data.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"data",
),
.accessor = C.privateBoxedFieldAccessor("data"),
},
);
};

View File

@ -8,6 +8,7 @@ const gobject = @import("gobject");
const gtk = @import("gtk");
const i18n = @import("../../../os/main.zig").i18n;
const apprt = @import("../../../apprt.zig");
const input = @import("../../../input.zig");
const CoreSurface = @import("../../../Surface.zig");
const gtk_version = @import("../gtk_version.zig");
@ -68,12 +69,7 @@ pub const Window = extern struct {
.{
.nick = "Config",
.blurb = "The configuration that this surface is using.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"config",
),
.accessor = C.privateObjFieldAccessor("config"),
},
);
};
@ -115,6 +111,23 @@ pub const Window = extern struct {
},
);
};
pub const @"background-opaque" = struct {
pub const name = "background-opaque";
const impl = gobject.ext.defineProperty(
name,
Self,
bool,
.{
.nick = "Background Opaque",
.blurb = "True if the background should be opaque.",
.default = true,
.accessor = gobject.ext.typedAccessor(Self, bool, .{
.getter = Self.getBackgroundOpaque,
}),
},
);
};
};
const Private = struct {
@ -122,7 +135,8 @@ pub const Window = extern struct {
config: ?*Config = null,
// Template bindings
surface: *Surface = undefined,
surface: *Surface,
toast_overlay: *adw.ToastOverlay,
pub var offset: c_int = 0;
};
@ -212,6 +226,16 @@ pub const Window = extern struct {
// Trigger our headerbar visibility to refresh
self.as(gobject.Object).notifyByPspec(properties.@"headerbar-visible".impl.param_spec);
// Trigger background opacity to refresh
self.as(gobject.Object).notifyByPspec(properties.@"background-opaque".impl.param_spec);
}
fn toggleCssClass(self: *Window, class: [:0]const u8, value: bool) void {
const widget = self.as(gtk.Widget);
if (value)
widget.addCssClass(class.ptr)
else
widget.removeCssClass(class.ptr);
}
/// Perform a binding action on the window's active surface.
@ -227,6 +251,18 @@ pub const Window = extern struct {
};
}
/// Queue a simple text-based toast. All text-based toasts share the
/// same timeout for consistency.
///
// This is not `pub` because we should be using signals emitted by
// other widgets to trigger our toasts. Other objects should not
// trigger toasts directly.
fn addToast(self: *Window, title: [*:0]const u8) void {
const toast = adw.Toast.new(title);
toast.setTimeout(3);
self.private().toast_overlay.addToast(toast);
}
//---------------------------------------------------------------
// Properties
@ -259,11 +295,18 @@ pub const Window = extern struct {
return config.@"gtk-titlebar";
}
fn getBackgroundOpaque(self: *Self) bool {
const priv = self.private();
const config = (priv.config orelse return true).get();
return config.@"background-opacity" >= 1.0;
}
fn propConfig(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
self.addToast(i18n._("Reloaded the configuration"));
self.syncAppearance();
}
@ -315,6 +358,16 @@ pub const Window = extern struct {
action.setEnabled(@intFromBool(has_selection));
}
/// Add or remove "background" CSS class depending on if the background
/// should be opaque.
fn propBackgroundOpaque(
_: *adw.ApplicationWindow,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
self.toggleCssClass("background", self.getBackgroundOpaque());
}
//---------------------------------------------------------------
// Virtual methods
@ -377,6 +430,29 @@ pub const Window = extern struct {
self.as(gtk.Window).destroy();
}
fn surfaceClipboardWrite(
_: *Surface,
clipboard_type: apprt.Clipboard,
text: [*:0]const u8,
self: *Self,
) callconv(.c) void {
// We only toast for the standard clipboard.
if (clipboard_type != .standard) return;
// We only toast if configured to
const priv = self.private();
const config_obj = priv.config orelse return;
const config = config_obj.get();
if (!config.@"app-notifications".@"clipboard-copy") {
return;
}
if (text[0] != 0)
self.addToast(i18n._("Copied to clipboard"))
else
self.addToast(i18n._("Cleared clipboard"));
}
fn surfaceCloseRequest(
surface: *Surface,
scope: *const Surface.CloseScope,
@ -538,13 +614,16 @@ pub const Window = extern struct {
properties.config.impl,
properties.debug.impl,
properties.@"headerbar-visible".impl,
properties.@"background-opaque".impl,
});
// Bindings
class.bindTemplateChildPrivate("surface", .{});
class.bindTemplateChildPrivate("toast_overlay", .{});
// Template Callbacks
class.bindTemplateCallback("close_request", &windowCloseRequest);
class.bindTemplateCallback("surface_clipboard_write", &surfaceClipboardWrite);
class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest);
class.bindTemplateCallback("surface_toggle_fullscreen", &surfaceToggleFullscreen);
class.bindTemplateCallback("surface_toggle_maximize", &surfaceToggleMaximize);
@ -552,6 +631,7 @@ pub const Window = extern struct {
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
class.bindTemplateCallback("notify_maximized", &propMaximized);
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
// Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose);

View File

@ -29,12 +29,12 @@ label.url-overlay.right {
/*
* GhosttySurface resize overlay
*/
.size-overlay label {
label.resize-overlay {
padding: 4px 8px 4px 8px;
border-radius: 6px 6px 6px 6px;
outline-style: solid;
outline-width: 1px;
outline-color: #555555;
outline-width: 1px;
}
/*

View File

@ -11,6 +11,11 @@ template $GhosttyResizeOverlay: Adw.Bin {
// See surface.blp for why we need to wrap this.
Adw.Bin {
Label label {
styles [
"background",
"resize-overlay",
]
focusable: false;
focus-on-click: false;
justify: center;

View File

@ -47,15 +47,12 @@ template $GhosttySurface: Adw.Bin {
}
[overlay]
$GhosttyResizeOverlay resize_overlay {
styles [
"size-overlay",
]
}
$GhosttyResizeOverlay resize_overlay {}
[overlay]
Label url_left {
styles [
"background",
"url-overlay",
]
@ -73,6 +70,7 @@ template $GhosttySurface: Adw.Bin {
[overlay]
Label url_right {
styles [
"background",
"url-overlay",
]

View File

@ -10,6 +10,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
notify::config => $notify_config();
notify::fullscreened => $notify_fullscreened();
notify::maximized => $notify_maximized();
notify::background-opaque => $notify_background_opaque();
default-width: 800;
default-height: 600;
// GTK4 grabs F10 input by default to focus the menubar icon. We want
@ -43,10 +44,13 @@ template $GhosttyWindow: Adw.ApplicationWindow {
visible: bind template.debug;
}
$GhosttySurface surface {
close-request => $surface_close_request();
toggle-fullscreen => $surface_toggle_fullscreen();
toggle-maximize => $surface_toggle_maximize();
Adw.ToastOverlay toast_overlay {
$GhosttySurface surface {
close-request => $surface_close_request();
clipboard-write => $surface_clipboard_write();
toggle-fullscreen => $surface_toggle_fullscreen();
toggle-maximize => $surface_toggle_maximize();
}
}
};
}

View File

@ -29,10 +29,27 @@ pub const IMEPos = struct {
/// The clipboard type.
///
/// If this is changed, you must also update ghostty.h
pub const Clipboard = enum(u2) {
pub const Clipboard = enum(Backing) {
standard = 0, // ctrl+c/v
selection = 1,
primary = 2,
// Our backing isn't is as small as we can in Zig, but a full
// C int if we're binding to C APIs.
const Backing = switch (build_config.app_runtime) {
.gtk, .@"gtk-ng" => c_int,
else => u2,
};
/// Make this a valid gobject if we're in a GTK environment.
pub const getGObjectType = switch (build_config.app_runtime) {
.gtk, .@"gtk-ng" => @import("gobject").ext.defineEnum(
Clipboard,
.{ .name = "GhosttyApprtClipboard" },
),
.none => void,
};
};
pub const ClipboardRequestType = enum(u8) {

View File

@ -133,7 +133,6 @@
fun:gsk_gpu_frame_do_upload_texture
fun:gsk_gpu_lookup_texture
...
fun:gsk_gpu_node_processor_add_first_node
fun:gsk_gpu_node_processor_process
fun:gsk_gpu_frame_render
fun:gsk_gpu_renderer_render
@ -353,8 +352,7 @@
match-leak-kinds: possible
fun:*alloc
fun:FcFontSet*
fun:FcFontSet*
fun:sort_in_thread.isra.0
...
fun:fc_thread_func
fun:g_thread_proxy
fun:start_thread