Merge remote-tracking branch 'upstream/main' into jacob/uucode
|
|
@ -258,7 +258,7 @@ recreated the next time you run the VM.
|
|||
|
||||
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
|
||||
|
||||
1. The should be different enough from existing VM definitions that they represent a distinct
|
||||
1. They should be different enough from existing VM definitions that they represent a distinct
|
||||
user (and developer) experience.
|
||||
2. There's a significant Ghostty user population that uses a similar environment.
|
||||
3. The VMs can be built using only packages from the current stable NixOS release.
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@
|
|||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/92f20650771384b82f981fb0f249e5fbdcb69e9f.tar.gz",
|
||||
.hash = "N-V-__8AAGHcWgSXHA9Fw-E0Hbe5EZWyYyI1AvW9O_HBbkRH",
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
||||
.hash = "N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@
|
|||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||
},
|
||||
"N-V-__8AAGHcWgSXHA9Fw-E0Hbe5EZWyYyI1AvW9O_HBbkRH": {
|
||||
"N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/92f20650771384b82f981fb0f249e5fbdcb69e9f.tar.gz",
|
||||
"hash": "sha256-sQ5IWKQdEU3MOHzxovjA4DO6f/ryvtW18aITN4Bkog0="
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
||||
"hash": "sha256-w/biUQZ+AJv0atXypwQxJlKkHRUaFS0AlE/VlBJXlVU="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
|
|
|
|||
|
|
@ -162,11 +162,11 @@ in
|
|||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAGHcWgSXHA9Fw-E0Hbe5EZWyYyI1AvW9O_HBbkRH";
|
||||
name = "N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/92f20650771384b82f981fb0f249e5fbdcb69e9f.tar.gz";
|
||||
hash = "sha256-sQ5IWKQdEU3MOHzxovjA4DO6f/ryvtW18aITN4Bkog0=";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz";
|
||||
hash = "sha256-w/biUQZ+AJv0atXypwQxJlKkHRUaFS0AlE/VlBJXlVU=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21a
|
|||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/92f20650771384b82f981fb0f249e5fbdcb69e9f.tar.gz
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz
|
||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@
|
|||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/92f20650771384b82f981fb0f249e5fbdcb69e9f.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAGHcWgSXHA9Fw-E0Hbe5EZWyYyI1AvW9O_HBbkRH",
|
||||
"sha256": "b10e4858a41d114dcc387cf1a2f8c0e033ba7ffaf2bed5b5f1a213378064a20d"
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv",
|
||||
"sha256": "c3f6e251067e009bf46ad5f2a704312652a41d151a152d00944fd59412579555"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 496 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -493,7 +493,7 @@ class AppDelegate: NSObject,
|
|||
self.menuMoveSplitDividerDown?.setImageIfDesired(systemSymbolName: "arrow.down.to.line")
|
||||
self.menuMoveSplitDividerLeft?.setImageIfDesired(systemSymbolName: "arrow.left.to.line")
|
||||
self.menuMoveSplitDividerRight?.setImageIfDesired(systemSymbolName: "arrow.right.to.line")
|
||||
self.menuFloatOnTop?.setImageIfDesired(systemSymbolName: "square.3.layers.3d.top.filled")
|
||||
self.menuFloatOnTop?.setImageIfDesired(systemSymbolName: "square.filled.on.square")
|
||||
}
|
||||
|
||||
/// Sync all of our menu item keyboard shortcuts with the Ghostty configuration.
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Satrio Bayu Aji <halosatrio@gmail.com>, 2025.
|
||||
# Mikail Muzakki <mikailmmuzakki@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-20 15:19+0700\n"
|
||||
"Last-Translator: Satrio Bayu Aji <halosatrio@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-01 10:15+0700\n"
|
||||
"Last-Translator: Mikail Muzakki <mikailmmuzakki@gmail.com>\n"
|
||||
"Language-Team: Indonesian <translation-team-id@lists.sourceforge.net>\n"
|
||||
"Language: id\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -86,7 +87,7 @@ msgstr "Belah kanan"
|
|||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Eksekusi perintah…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
|
|
@ -102,7 +103,7 @@ msgstr "Tempel"
|
|||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||
msgid "Clear"
|
||||
msgstr "Hapus"
|
||||
msgstr "Bersihkan"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||
|
|
@ -159,7 +160,7 @@ msgstr "Buka konfigurasi"
|
|||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Palet perintah"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
|
|
@ -195,7 +196,7 @@ msgstr ""
|
|||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "Menyangkal"
|
||||
msgstr "Tolak"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
|
|
@ -207,12 +208,12 @@ msgstr "Izinkan"
|
|||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Ingat pilihan untuk belahan ini"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Muat ulang konfigurasi untuk menampilkan pesan ini lagi"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
|
|
@ -225,7 +226,7 @@ msgstr ""
|
|||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
msgstr "Peringatan: Tempelan yang berpotensi tidak aman"
|
||||
msgstr "Peringatan: Tempelan berpotensi tidak aman"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
|
||||
msgid ""
|
||||
|
|
@ -277,15 +278,15 @@ msgstr "Disalin ke papan klip"
|
|||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Papan klip dibersihkan"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Perintah berhasil"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Perintah gagal"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
|
@ -297,7 +298,7 @@ msgstr "Lihat tab terbuka"
|
|||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Belahan baru"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ msgstr ""
|
|||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-07-09 16:11-0400\n"
|
||||
"Last-Translator: Hojin You <dev.hojin@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-03 20:42+0900\n"
|
||||
"Last-Translator: Jinhyeok Lee <zenyr@zenyr.com>\n"
|
||||
"Language-Team: Korean <translation-team-ko@googlegroups.com>\n"
|
||||
"Language: ko\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -46,7 +46,7 @@ msgid ""
|
|||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"설정에 하나 이상의 문제가 발견되었습니다. 아래 오류(를)들을 확인한 후 설정을 "
|
||||
"설정에 하나 이상의 문제가 발견되었습니다. 아래 오류를 확인한 후 설정을 "
|
||||
"다시 불러오거나 무시하세요."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
|
|
@ -232,9 +232,7 @@ msgstr "경고: 잠재적으로 안전하지 않은 붙여넣기"
|
|||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"이 텍스트를 터미널에 붙여넣는 것은 위험할 수 있습니다. 일부 명령이 실행될 수 "
|
||||
"있는 것으로 보입니다."
|
||||
msgstr "이 텍스트를 터미널에 붙여넣으면 일부 명령이 실행될 수 있어 위험할 수 있습니다."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
|
|
@ -278,15 +276,15 @@ msgstr "클립보드에 복사됨"
|
|||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "클립보드 지워짐"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "명령 성공"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "명령 실패"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const build_config = @import("../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const apprt = @import("../apprt.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
|
|
@ -533,6 +534,16 @@ pub const SizeLimit = extern struct {
|
|||
pub const InitialSize = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
/// 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.defineBoxed(
|
||||
InitialSize,
|
||||
.{ .name = "GhosttyApprtInitialSize" },
|
||||
),
|
||||
|
||||
.none => void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const CellSize = extern struct {
|
||||
|
|
|
|||
|
|
@ -37,20 +37,21 @@ pub const blueprints: []const Blueprint = &.{
|
|||
.{ .major = 1, .minor = 4, .name = "clipboard-confirmation-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "close-confirmation-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "debug-warning" },
|
||||
.{ .major = 1, .minor = 3, .name = "debug-warning" },
|
||||
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||
.{ .major = 1, .minor = 3, .name = "surface-child-exited" },
|
||||
.{ .major = 1, .minor = 5, .name = "tab" },
|
||||
.{ .major = 1, .minor = 5, .name = "window" },
|
||||
.{ .major = 1, .minor = 2, .name = "debug-warning" },
|
||||
.{ .major = 1, .minor = 3, .name = "debug-warning" },
|
||||
};
|
||||
|
||||
/// CSS files in css_path
|
||||
pub const css = [_][]const u8{
|
||||
"style.css",
|
||||
// "style-dark.css",
|
||||
// "style-hc.css",
|
||||
// "style-hc-dark.css",
|
||||
"style-dark.css",
|
||||
"style-hc.css",
|
||||
"style-hc-dark.css",
|
||||
};
|
||||
|
||||
pub const Blueprint = struct {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const xev = @import("../../../global.zig").xev;
|
|||
const CoreConfig = configpkg.Config;
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
|
||||
const ext = @import("../ext.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
const winprotopkg = @import("../winproto.zig");
|
||||
|
|
@ -128,6 +129,15 @@ pub const Application = extern struct {
|
|||
/// outside of our own lifecycle and that's okay.
|
||||
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .{},
|
||||
|
||||
/// glib source for our signal handler.
|
||||
signal_source: ?c_uint = null,
|
||||
|
||||
/// CSS Provider for any styles based on Ghostty configuration values.
|
||||
css_provider: *gtk.CssProvider,
|
||||
|
||||
/// Providers for loading custom stylesheets defined by user
|
||||
custom_css_providers: std.ArrayListUnmanaged(*gtk.CssProvider) = .empty,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
|
|
@ -263,6 +273,16 @@ pub const Application = extern struct {
|
|||
const config_obj: *Config = try .new(alloc, &config);
|
||||
errdefer config_obj.unref();
|
||||
|
||||
// Internally, GTK ensures that only one instance of this provider
|
||||
// exists in the provider list for the display.
|
||||
const css_provider = gtk.CssProvider.new();
|
||||
gtk.StyleContext.addProviderForDisplay(
|
||||
display,
|
||||
css_provider.as(gtk.StyleProvider),
|
||||
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
|
||||
);
|
||||
errdefer css_provider.unref();
|
||||
|
||||
// Initialize the app.
|
||||
const self = gobject.ext.newInstance(Self, .{
|
||||
.application_id = app_id.ptr,
|
||||
|
|
@ -283,8 +303,22 @@ pub const Application = extern struct {
|
|||
.core_app = core_app,
|
||||
.config = config_obj,
|
||||
.winproto = wp,
|
||||
.css_provider = css_provider,
|
||||
.custom_css_providers = .empty,
|
||||
};
|
||||
|
||||
// Signals
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self,
|
||||
*Self,
|
||||
propConfig,
|
||||
self,
|
||||
.{ .detail = "config" },
|
||||
);
|
||||
|
||||
// Trigger initial config changes
|
||||
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
@ -299,6 +333,22 @@ pub const Application = extern struct {
|
|||
priv.config.unref();
|
||||
priv.winproto.deinit(alloc);
|
||||
if (priv.transient_cgroup_base) |base| alloc.free(base);
|
||||
if (gdk.Display.getDefault()) |display| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
priv.css_provider.as(gtk.StyleProvider),
|
||||
);
|
||||
|
||||
for (priv.custom_css_providers.items) |provider| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
);
|
||||
}
|
||||
}
|
||||
priv.css_provider.unref();
|
||||
for (priv.custom_css_providers.items) |provider| provider.unref();
|
||||
priv.custom_css_providers.deinit(alloc);
|
||||
}
|
||||
|
||||
/// The global allocator that all other classes should use by
|
||||
|
|
@ -493,10 +543,20 @@ pub const Application = extern struct {
|
|||
value.config,
|
||||
),
|
||||
|
||||
.desktop_notification => Action.desktopNotification(self, target, value),
|
||||
|
||||
.goto_tab => return Action.gotoTab(target, value),
|
||||
|
||||
.initial_size => return Action.initialSize(target, value),
|
||||
|
||||
.mouse_over_link => Action.mouseOverLink(target, value),
|
||||
.mouse_shape => Action.mouseShape(target, value),
|
||||
.mouse_visibility => Action.mouseVisibility(target, value),
|
||||
|
||||
.move_tab => return Action.moveTab(target, value),
|
||||
|
||||
.new_tab => return Action.newTab(target),
|
||||
|
||||
.new_window => try Action.newWindow(
|
||||
self,
|
||||
switch (target) {
|
||||
|
|
@ -505,14 +565,20 @@ pub const Application = extern struct {
|
|||
},
|
||||
),
|
||||
|
||||
.open_config => return Action.openConfig(self),
|
||||
|
||||
.open_url => Action.openUrl(self, value),
|
||||
|
||||
.pwd => Action.pwd(target, value),
|
||||
|
||||
.present_terminal => return Action.presentTerminal(target),
|
||||
|
||||
.progress_report => return Action.progressReport(target, value),
|
||||
|
||||
.quit => self.quit(),
|
||||
|
||||
.quit_timer => try Action.quitTimer(self, value),
|
||||
|
||||
.progress_report => return Action.progressReport(target, value),
|
||||
|
||||
.reload_config => try Action.reloadConfig(self, target, value),
|
||||
|
||||
.render => Action.render(target),
|
||||
|
|
@ -525,30 +591,31 @@ pub const Application = extern struct {
|
|||
|
||||
.show_gtk_inspector => Action.showGtkInspector(),
|
||||
|
||||
.size_limit => return Action.sizeLimit(target, value),
|
||||
|
||||
.toggle_maximize => Action.toggleMaximize(target),
|
||||
.toggle_fullscreen => Action.toggleFullscreen(target),
|
||||
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
|
||||
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
||||
.toggle_window_decorations => return Action.toggleWindowDecorations(target),
|
||||
|
||||
// Unimplemented but todo on gtk-ng branch
|
||||
.new_tab,
|
||||
.goto_tab,
|
||||
.move_tab,
|
||||
.prompt_title,
|
||||
.toggle_command_palette,
|
||||
.inspector,
|
||||
// TODO: splits
|
||||
.new_split,
|
||||
.resize_split,
|
||||
.equalize_splits,
|
||||
.goto_split,
|
||||
.open_config,
|
||||
.inspector,
|
||||
.desktop_notification,
|
||||
.present_terminal,
|
||||
.initial_size,
|
||||
.size_limit,
|
||||
.toggle_tab_overview,
|
||||
.toggle_split_zoom,
|
||||
.toggle_window_decorations,
|
||||
.prompt_title,
|
||||
.toggle_quick_terminal,
|
||||
.toggle_command_palette,
|
||||
.open_url,
|
||||
=> {
|
||||
log.warn("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
|
||||
// Unimplemented
|
||||
.secure_input,
|
||||
.close_all_windows,
|
||||
.float_window,
|
||||
.toggle_visibility,
|
||||
|
|
@ -565,13 +632,6 @@ pub const Application = extern struct {
|
|||
log.warn("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
|
||||
// Unimplemented
|
||||
.secure_input,
|
||||
=> {
|
||||
log.warn("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
// Assume it was handled. The unhandled case must be explicit
|
||||
|
|
@ -645,6 +705,155 @@ pub const Application = extern struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn loadRuntimeCss(
|
||||
self: *Self,
|
||||
) Allocator.Error!void {
|
||||
const alloc = self.allocator();
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
const writer = buf.writer(alloc);
|
||||
|
||||
const config = self.private().config.get();
|
||||
const window_theme = config.@"window-theme";
|
||||
const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background;
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.print(
|
||||
\\widget.unfocused-split {{
|
||||
\\ opacity: {d:.2};
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
, .{
|
||||
1.0 - config.@"unfocused-split-opacity",
|
||||
unfocused_fill.r,
|
||||
unfocused_fill.g,
|
||||
unfocused_fill.b,
|
||||
});
|
||||
|
||||
if (config.@"split-divider-color") |color| {
|
||||
try writer.print(
|
||||
\\.terminal-window .notebook separator {{
|
||||
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\}}
|
||||
, .{
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.@"window-title-font-family") |font_family| {
|
||||
try writer.print(
|
||||
\\.window headerbar {{
|
||||
\\ font-family: "{[font_family]s}";
|
||||
\\}}
|
||||
, .{ .font_family = font_family });
|
||||
}
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\:root {{
|
||||
\\ --ghostty-fg: rgb({d},{d},{d});
|
||||
\\ --ghostty-bg: rgb({d},{d},{d});
|
||||
\\ --headerbar-fg-color: var(--ghostty-fg);
|
||||
\\ --headerbar-bg-color: var(--ghostty-bg);
|
||||
\\ --headerbar-backdrop-color: oklab(from var(--headerbar-bg-color) calc(l * 0.9) a b / alpha);
|
||||
\\ --overview-fg-color: var(--ghostty-fg);
|
||||
\\ --overview-bg-color: var(--ghostty-bg);
|
||||
\\ --popover-fg-color: var(--ghostty-fg);
|
||||
\\ --popover-bg-color: var(--ghostty-bg);
|
||||
\\ --window-fg-color: var(--ghostty-fg);
|
||||
\\ --window-bg-color: var(--ghostty-bg);
|
||||
\\}}
|
||||
\\windowhandle {{
|
||||
\\ background-color: var(--headerbar-bg-color);
|
||||
\\ color: var(--headerbar-fg-color);
|
||||
\\}}
|
||||
\\windowhandle:backdrop {{
|
||||
\\ background-color: var(--headerbar-backdrop-color);
|
||||
\\}}
|
||||
, .{
|
||||
headerbar_foreground.r,
|
||||
headerbar_foreground.g,
|
||||
headerbar_foreground.b,
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
|
||||
const data = try alloc.dupeZ(u8, buf.items);
|
||||
defer alloc.free(data);
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
fn loadCustomCss(self: *Self) !void {
|
||||
const priv = self.private();
|
||||
const alloc = self.allocator();
|
||||
const display = gdk.Display.getDefault() orelse {
|
||||
log.warn("unable to get display", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
// unload the previously loaded style providers
|
||||
for (priv.custom_css_providers.items) |provider| {
|
||||
gtk.StyleContext.removeProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
);
|
||||
provider.unref();
|
||||
}
|
||||
priv.custom_css_providers.clearRetainingCapacity();
|
||||
|
||||
const config = priv.config.getMut();
|
||||
for (config.@"gtk-custom-css".value.items) |p| {
|
||||
const path, const optional = switch (p) {
|
||||
.optional => |path| .{ path, true },
|
||||
.required => |path| .{ path, false },
|
||||
};
|
||||
const file = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
||||
if (err != error.FileNotFound or !optional) {
|
||||
log.warn(
|
||||
"error opening gtk-custom-css file {s}: {}",
|
||||
.{ path, err },
|
||||
);
|
||||
}
|
||||
continue;
|
||||
};
|
||||
defer file.close();
|
||||
|
||||
log.info("loading gtk-custom-css path={s}", .{path});
|
||||
const contents = try file.reader().readAllAlloc(
|
||||
alloc,
|
||||
5 * 1024 * 1024, // 5MB,
|
||||
);
|
||||
defer alloc.free(contents);
|
||||
|
||||
const data = try alloc.dupeZ(u8, contents);
|
||||
defer alloc.free(data);
|
||||
|
||||
const provider = gtk.CssProvider.new();
|
||||
errdefer provider.unref();
|
||||
try priv.custom_css_providers.append(alloc, provider);
|
||||
loadCssProviderFromData(provider, data);
|
||||
gtk.StyleContext.addProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
gtk.STYLE_PROVIDER_PRIORITY_USER,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
|
|
@ -670,6 +879,28 @@ pub const Application = extern struct {
|
|||
self.showConfigErrorsDialog();
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
_: *Application,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// Load our runtime and custom CSS. If this fails then our window is
|
||||
// just stuck with the old CSS but we don't want to fail the entire
|
||||
// config change operation.
|
||||
self.loadRuntimeCss() catch |err| switch (err) {
|
||||
error.OutOfMemory => log.warn(
|
||||
"out of memory loading runtime CSS, no runtime CSS applied",
|
||||
.{},
|
||||
),
|
||||
};
|
||||
self.loadCustomCss() catch |err| {
|
||||
log.warn(
|
||||
"failed to load custom CSS, no custom CSS applied, err={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Libghostty Callbacks
|
||||
|
||||
|
|
@ -698,6 +929,9 @@ pub const Application = extern struct {
|
|||
// Setup our style manager (light/dark mode)
|
||||
self.startupStyleManager();
|
||||
|
||||
// Setup some signal handlers
|
||||
self.startupSignals();
|
||||
|
||||
// Setup our action map
|
||||
self.startupActionMap();
|
||||
|
||||
|
|
@ -786,6 +1020,17 @@ pub const Application = extern struct {
|
|||
);
|
||||
}
|
||||
|
||||
/// Setup signal handlers
|
||||
fn startupSignals(self: *Self) void {
|
||||
const priv = self.private();
|
||||
assert(priv.signal_source == null);
|
||||
priv.signal_source = glib.unixSignalAdd(
|
||||
std.posix.SIG.USR2,
|
||||
handleSigusr2,
|
||||
self,
|
||||
);
|
||||
}
|
||||
|
||||
/// Setup our action map.
|
||||
fn startupActionMap(self: *Self) void {
|
||||
const t_variant_type = glib.ext.VariantType.newFor(u64);
|
||||
|
|
@ -804,6 +1049,8 @@ pub const Application = extern struct {
|
|||
const actions = .{
|
||||
.{ "new-window", actionNewWindow, null },
|
||||
.{ "new-window-command", actionNewWindow, as_variant_type },
|
||||
.{ "open-config", actionOpenConfig, null },
|
||||
.{ "present-surface", actionPresentSurface, t_variant_type },
|
||||
.{ "quit", actionQuit, null },
|
||||
.{ "reload-config", actionReloadConfig, null },
|
||||
};
|
||||
|
|
@ -914,6 +1161,12 @@ pub const Application = extern struct {
|
|||
diag.close();
|
||||
diag.unref(); // strong ref from get()
|
||||
}
|
||||
if (priv.signal_source) |v| {
|
||||
if (glib.Source.remove(v) == 0) {
|
||||
log.warn("unable to remove signal source", .{});
|
||||
}
|
||||
priv.signal_source = null;
|
||||
}
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
|
|
@ -932,6 +1185,26 @@ pub const Application = extern struct {
|
|||
//---------------------------------------------------------------
|
||||
// Signal Handlers
|
||||
|
||||
/// SIGUSR2 signal handler via g_unix_signal_add
|
||||
fn handleSigusr2(ud: ?*anyopaque) callconv(.c) c_int {
|
||||
const self: *Self = @ptrCast(@alignCast(ud orelse
|
||||
return @intFromBool(glib.SOURCE_CONTINUE)));
|
||||
|
||||
log.info("received SIGUSR2, reloading configuration", .{});
|
||||
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.
|
||||
log.warn("error reloading config: {}", .{err});
|
||||
};
|
||||
|
||||
return @intFromBool(glib.SOURCE_CONTINUE);
|
||||
}
|
||||
|
||||
fn handleCloseConfirmation(
|
||||
_: *CloseConfirmationDialog,
|
||||
self: *Self,
|
||||
|
|
@ -1098,6 +1371,58 @@ pub const Application = extern struct {
|
|||
}, .{ .forever = {} });
|
||||
}
|
||||
|
||||
pub fn actionOpenConfig(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
_ = self.core().mailbox.push(.open_config, .forever);
|
||||
}
|
||||
|
||||
fn actionPresentSurface(
|
||||
_: *gio.SimpleAction,
|
||||
parameter_: ?*glib.Variant,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const parameter = parameter_ orelse return;
|
||||
|
||||
const t = glib.ext.VariantType.newFor(u64);
|
||||
defer glib.VariantType.free(t);
|
||||
|
||||
// Make sure that we've receiived a u64 from the system.
|
||||
if (glib.Variant.isOfType(parameter, t) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert that u64 to pointer to a core surface. A value of zero
|
||||
// means that there was no target surface for the notification so
|
||||
// we don't focus any surface.
|
||||
//
|
||||
// This is admittedly SUPER SUS and we should instead do what we
|
||||
// do on macOS which is generate a UUID per surface and then pass
|
||||
// that around. But, we do validate the pointer below so at worst
|
||||
// this may result in focusing the wrong surface if the pointer was
|
||||
// reused for a surface.
|
||||
const ptr_int = parameter.getUint64();
|
||||
if (ptr_int == 0) return;
|
||||
const surface: *CoreSurface = @ptrFromInt(ptr_int);
|
||||
|
||||
// Send a message through the core app mailbox rather than presenting the
|
||||
// surface directly so that it can validate that the surface pointer is
|
||||
// valid. We could get an invalid pointer if a desktop notification outlives
|
||||
// a Ghostty instance and a new one starts up, or there are multiple Ghostty
|
||||
// instances running.
|
||||
_ = self.core().mailbox.push(
|
||||
.{
|
||||
.surface_message = .{
|
||||
.surface = surface,
|
||||
.message = .present_surface,
|
||||
},
|
||||
},
|
||||
.forever,
|
||||
);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Boilerplate/Noise
|
||||
|
||||
|
|
@ -1171,6 +1496,91 @@ const Action = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn desktopNotification(
|
||||
self: *Application,
|
||||
target: apprt.Target,
|
||||
n: apprt.action.DesktopNotification,
|
||||
) void {
|
||||
// TODO: We should move the surface target to a function call
|
||||
// on Surface and emit a signal that embedders can connect to. This
|
||||
// will let us handle notifications differently depending on where
|
||||
// a surface is presented. At the time of writing this, we always
|
||||
// want to show the notification AND the logic below was directly
|
||||
// ported from "legacy" GTK so this is fine, but I want to leave this
|
||||
// note so we can do it one day.
|
||||
|
||||
// Set a default title if we don't already have one
|
||||
const t = switch (n.title.len) {
|
||||
0 => "Ghostty",
|
||||
else => n.title,
|
||||
};
|
||||
|
||||
const notification = gio.Notification.new(t);
|
||||
defer notification.unref();
|
||||
notification.setBody(n.body);
|
||||
|
||||
const icon = gio.ThemedIcon.new("com.mitchellh.ghostty");
|
||||
defer icon.unref();
|
||||
notification.setIcon(icon.as(gio.Icon));
|
||||
|
||||
const pointer = glib.Variant.newUint64(switch (target) {
|
||||
.app => 0,
|
||||
.surface => |v| @intFromPtr(v),
|
||||
});
|
||||
notification.setDefaultActionAndTargetValue(
|
||||
"app.present-surface",
|
||||
pointer,
|
||||
);
|
||||
|
||||
// We set the notification ID to the body content. If the content is the
|
||||
// same, this notification may replace a previous notification
|
||||
const gio_app = self.as(gio.Application);
|
||||
gio_app.sendNotification(n.body, notification);
|
||||
}
|
||||
|
||||
pub fn gotoTab(
|
||||
target: apprt.Target,
|
||||
tab: apprt.action.GotoTab,
|
||||
) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
const window = ext.getAncestor(
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
|
||||
return window.selectTab(switch (tab) {
|
||||
.previous => .previous,
|
||||
.next => .next,
|
||||
.last => .last,
|
||||
else => .{ .n = @intCast(@intFromEnum(tab)) },
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialSize(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.InitialSize,
|
||||
) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
surface.setDefaultSize(.{
|
||||
.width = value.width,
|
||||
.height = value.height,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouseOverLink(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.MouseOverLink,
|
||||
|
|
@ -1229,12 +1639,68 @@ const Action = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn moveTab(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.MoveTab,
|
||||
) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
const window = ext.getAncestor(
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
|
||||
return window.moveTab(
|
||||
surface,
|
||||
@intCast(value.amount),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newTab(target: apprt.Target) bool {
|
||||
switch (target) {
|
||||
.app => {
|
||||
log.warn("new tab to app is unexpected", .{});
|
||||
return false;
|
||||
},
|
||||
|
||||
.surface => |core| {
|
||||
// Get the window ancestor of the surface. Surfaces shouldn't
|
||||
// be aware they might be in windows but at the app level we
|
||||
// can do this.
|
||||
const surface = core.rt_surface.surface;
|
||||
const window = ext.getAncestor(
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
window.newTab(core);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newWindow(
|
||||
self: *Application,
|
||||
parent: ?*CoreSurface,
|
||||
) !void {
|
||||
const win = Window.new(self, parent);
|
||||
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.
|
||||
|
|
@ -1246,10 +1712,44 @@ const Action = struct {
|
|||
.{},
|
||||
);
|
||||
|
||||
// Create a new tab
|
||||
win.newTab(parent);
|
||||
|
||||
// Show the window
|
||||
gtk.Window.present(win.as(gtk.Window));
|
||||
}
|
||||
|
||||
pub fn openConfig(self: *Application) bool {
|
||||
// Get the config file path
|
||||
const alloc = self.allocator();
|
||||
const path = configpkg.edit.openPath(alloc) catch |err| {
|
||||
log.warn("error getting config file path: {}", .{err});
|
||||
return false;
|
||||
};
|
||||
defer alloc.free(path);
|
||||
|
||||
// Open it using openURL. "path" isn't actually a URL but
|
||||
// at the time of writing that works just fine for GTK.
|
||||
openUrl(self, .{ .kind = .text, .url = path });
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn openUrl(
|
||||
self: *Application,
|
||||
value: apprt.action.OpenUrl,
|
||||
) void {
|
||||
// TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html
|
||||
|
||||
// Fallback to the minimal cross-platform way of opening a URL.
|
||||
// This is always a safe fallback and enables for example Windows
|
||||
// to open URLs (GTK on Windows via WSL is a thing).
|
||||
internal_os.open(
|
||||
self.allocator(),
|
||||
value.kind,
|
||||
value.url,
|
||||
) catch |err| log.warn("unable to open url: {}", .{err});
|
||||
}
|
||||
|
||||
pub fn pwd(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.Pwd,
|
||||
|
|
@ -1278,6 +1778,18 @@ const Action = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn presentTerminal(
|
||||
target: apprt.Target,
|
||||
) bool {
|
||||
return switch (target) {
|
||||
.app => false,
|
||||
.surface => |v| surface: {
|
||||
v.rt_surface.surface.present();
|
||||
break :surface true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn progressReport(
|
||||
target: apprt.Target,
|
||||
value: terminal.osc.Command.ProgressReport,
|
||||
|
|
@ -1378,6 +1890,26 @@ const Action = struct {
|
|||
gtk.Window.setInteractiveDebugging(@intFromBool(true));
|
||||
}
|
||||
|
||||
pub fn sizeLimit(
|
||||
target: apprt.Target,
|
||||
value: apprt.action.SizeLimit,
|
||||
) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
// Note: we ignore the max size currently because we have
|
||||
// no mechanism to enforce it.
|
||||
const surface = core.rt_surface.surface;
|
||||
surface.setMinSize(.{
|
||||
.width = value.min_width,
|
||||
.height = value.min_height,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(target: apprt.Target) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
|
|
@ -1385,12 +1917,92 @@ 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 => {},
|
||||
.surface => |v| v.rt_surface.surface.toggleMaximize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggleTabOverview(target: apprt.Target) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
const window = ext.getAncestor(
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
|
||||
window.toggleTabOverview();
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggleWindowDecorations(target: apprt.Target) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
const window = ext.getAncestor(
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
|
||||
window.toggleWindowDecorations();
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// This sets various GTK-related environment variables as necessary
|
||||
|
|
@ -1507,3 +2119,8 @@ fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c)
|
|||
// Abusing integers to be enums and booleans is a terrible idea, C.
|
||||
return if (window.isActive() != 0) 0 else -1;
|
||||
}
|
||||
|
||||
fn loadCssProviderFromData(provider: *gtk.CssProvider, data: [:0]const u8) void {
|
||||
assert(gtk_version.runtimeAtLeast(4, 12, 0));
|
||||
provider.loadFromString(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@ pub const CloseConfirmationDialog = extern struct {
|
|||
void,
|
||||
);
|
||||
};
|
||||
|
||||
pub const cancel = struct {
|
||||
pub const name = "cancel";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
|
|
@ -72,14 +83,15 @@ pub const CloseConfirmationDialog = extern struct {
|
|||
|
||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
}
|
||||
|
||||
pub fn present(self: *Self, parent: ?*gtk.Widget) void {
|
||||
// Setup our title/body text.
|
||||
const priv = self.private();
|
||||
self.as(Dialog.Parent).setHeading(priv.target.title());
|
||||
self.as(Dialog.Parent).setBody(priv.target.body());
|
||||
}
|
||||
|
||||
pub fn present(self: *Self, parent: ?*gtk.Widget) void {
|
||||
// Show it
|
||||
self.as(Dialog).present(parent);
|
||||
}
|
||||
|
||||
|
|
@ -91,13 +103,21 @@ pub const CloseConfirmationDialog = extern struct {
|
|||
self: *Self,
|
||||
response_id: [*:0]const u8,
|
||||
) callconv(.C) void {
|
||||
if (std.mem.orderZ(u8, response_id, "close") != .eq) return;
|
||||
signals.@"close-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
if (std.mem.orderZ(u8, response_id, "close") == .eq) {
|
||||
signals.@"close-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
} else {
|
||||
signals.cancel.impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.C) void {
|
||||
|
|
@ -141,6 +161,7 @@ pub const CloseConfirmationDialog = extern struct {
|
|||
|
||||
// Signals
|
||||
signals.@"close-request".impl.register(.{});
|
||||
signals.cancel.impl.register(.{});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
|
|
@ -158,11 +179,13 @@ pub const CloseConfirmationDialog = extern struct {
|
|||
/// together into one struct that is the sole source of truth.
|
||||
pub const Target = enum(c_int) {
|
||||
app,
|
||||
tab,
|
||||
window,
|
||||
|
||||
pub fn title(self: Target) [*:0]const u8 {
|
||||
return switch (self) {
|
||||
.app => i18n._("Quit Ghostty?"),
|
||||
.tab => i18n._("Close Tab?"),
|
||||
.window => i18n._("Close Window?"),
|
||||
};
|
||||
}
|
||||
|
|
@ -170,6 +193,7 @@ pub const Target = enum(c_int) {
|
|||
pub fn body(self: Target) [*:0]const u8 {
|
||||
return switch (self) {
|
||||
.app => i18n._("All terminal sessions will be terminated."),
|
||||
.tab => i18n._("All terminal sessions in this tab will be terminated."),
|
||||
.window => i18n._("All terminal sessions in this window will be terminated."),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const renderer = @import("../../../renderer.zig");
|
|||
const terminal = @import("../../../terminal/main.zig");
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const ext = @import("../ext.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gtk_key = @import("../key.zig");
|
||||
const ApprtSurface = @import("../Surface.zig");
|
||||
|
|
@ -25,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);
|
||||
|
||||
|
|
@ -75,6 +77,20 @@ pub const Surface = extern struct {
|
|||
);
|
||||
};
|
||||
|
||||
pub const @"default-size" = struct {
|
||||
pub const name = "default-size";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Size,
|
||||
.{
|
||||
.nick = "Default Size",
|
||||
.blurb = "The default size of the window for this surface.",
|
||||
.accessor = C.privateBoxedFieldAccessor("default_size"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"font-size-request" = struct {
|
||||
pub const name = "font-size-request";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
|
|
@ -109,6 +125,20 @@ pub const Surface = extern struct {
|
|||
);
|
||||
};
|
||||
|
||||
pub const @"min-size" = struct {
|
||||
pub const name = "min-size";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Size,
|
||||
.{
|
||||
.nick = "Minimum Size",
|
||||
.blurb = "The minimum size of the surface.",
|
||||
.accessor = C.privateBoxedFieldAccessor("min_size"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"mouse-hidden" = struct {
|
||||
pub const name = "mouse-hidden";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
|
|
@ -285,6 +315,31 @@ pub const Surface = extern struct {
|
|||
);
|
||||
};
|
||||
|
||||
/// Emitted after the surface is initialized.
|
||||
pub const init = struct {
|
||||
pub const name = "init";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
/// Emitted when the focus wants to be brought to the top and
|
||||
/// focused.
|
||||
pub const @"present-request" = struct {
|
||||
pub const name = "present-request";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
/// Emitted when this surface requests its container to toggle its
|
||||
/// fullscreen state.
|
||||
pub const @"toggle-fullscreen" = struct {
|
||||
|
|
@ -320,6 +375,13 @@ pub const Surface = extern struct {
|
|||
/// if `Application.transient_cgroup_base` is set.
|
||||
cgroup_path: ?[]const u8 = null,
|
||||
|
||||
/// The default size for a window that embeds this surface.
|
||||
default_size: ?*Size = null,
|
||||
|
||||
/// The minimum size for this surface. Embedders enforce this,
|
||||
/// not the surface itself.
|
||||
min_size: ?*Size = null,
|
||||
|
||||
/// The requested font size. This only applies to initialization
|
||||
/// and has no effect later.
|
||||
font_size_request: ?*font.face.DesiredSize = null,
|
||||
|
|
@ -578,6 +640,17 @@ pub const Surface = extern struct {
|
|||
return @intFromBool(glib.SOURCE_REMOVE);
|
||||
}
|
||||
|
||||
/// Request that this terminal come to the front and become focused.
|
||||
/// It is up to the embedding widget to react to this.
|
||||
pub fn present(self: *Self) void {
|
||||
signals.@"present-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Key press event (press or release).
|
||||
///
|
||||
/// At a high level, we want to construct an `input.KeyEvent` and
|
||||
|
|
@ -975,7 +1048,13 @@ pub const Surface = extern struct {
|
|||
}
|
||||
|
||||
pub fn getSize(self: *Self) apprt.SurfaceSize {
|
||||
return self.private().size;
|
||||
const priv = self.private();
|
||||
// By the time this is called, we should be in a widget tree.
|
||||
// This should not be called before that. We ensure this by initializing
|
||||
// the surface in `glareaResize`. This is VERY important because it
|
||||
// avoids the pty having an incorrect initial size.
|
||||
assert(priv.size.width >= 0 and priv.size.height >= 0);
|
||||
return priv.size;
|
||||
}
|
||||
|
||||
pub fn getCursorPos(self: *Self) apprt.CursorPos {
|
||||
|
|
@ -983,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();
|
||||
|
|
@ -1021,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;
|
||||
}
|
||||
|
||||
|
|
@ -1050,6 +1135,13 @@ pub const Surface = extern struct {
|
|||
);
|
||||
}
|
||||
|
||||
/// Focus this surface. This properly focuses the input part of
|
||||
/// our surface.
|
||||
pub fn grabFocus(self: *Self) void {
|
||||
const priv = self.private();
|
||||
_ = priv.gl_area.as(gtk.Widget).grabFocus();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual Methods
|
||||
|
||||
|
|
@ -1065,12 +1157,7 @@ pub const Surface = extern struct {
|
|||
priv.mouse_shape = .text;
|
||||
priv.mouse_hidden = false;
|
||||
priv.focused = true;
|
||||
priv.size = .{
|
||||
// Funky numbers on purpose so they stand out if for some reason
|
||||
// our size doesn't get properly set.
|
||||
.width = 111,
|
||||
.height = 111,
|
||||
};
|
||||
priv.size = .{ .width = 0, .height = 0 };
|
||||
|
||||
// If our configuration is null then we get the configuration
|
||||
// from the application.
|
||||
|
|
@ -1150,10 +1237,18 @@ pub const Surface = extern struct {
|
|||
glib.free(@constCast(@ptrCast(v)));
|
||||
priv.mouse_hover_url = null;
|
||||
}
|
||||
if (priv.default_size) |v| {
|
||||
ext.boxedFree(Size, v);
|
||||
priv.default_size = null;
|
||||
}
|
||||
if (priv.font_size_request) |v| {
|
||||
glib.ext.destroy(v);
|
||||
priv.font_size_request = null;
|
||||
}
|
||||
if (priv.min_size) |v| {
|
||||
ext.boxedFree(Size, v);
|
||||
priv.min_size = null;
|
||||
}
|
||||
if (priv.pwd) |v| {
|
||||
glib.free(@constCast(@ptrCast(v)));
|
||||
priv.pwd = null;
|
||||
|
|
@ -1191,6 +1286,50 @@ pub const Surface = extern struct {
|
|||
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
|
||||
}
|
||||
|
||||
/// Return the default size, if set.
|
||||
pub fn getDefaultSize(self: *Self) ?*Size {
|
||||
const priv = self.private();
|
||||
return priv.default_size;
|
||||
}
|
||||
|
||||
/// Set the default size for a window that contains this surface.
|
||||
/// This is up to the embedding widget to respect this. Generally, only
|
||||
/// the first surface in a window respects this.
|
||||
pub fn setDefaultSize(self: *Self, size: Size) void {
|
||||
const priv = self.private();
|
||||
if (priv.default_size) |v| ext.boxedFree(
|
||||
Size,
|
||||
v,
|
||||
);
|
||||
priv.default_size = ext.boxedCopy(
|
||||
Size,
|
||||
&size,
|
||||
);
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"default-size".impl.param_spec);
|
||||
}
|
||||
|
||||
/// Return the min size, if set.
|
||||
pub fn getMinSize(self: *Self) ?*Size {
|
||||
const priv = self.private();
|
||||
return priv.min_size;
|
||||
}
|
||||
|
||||
/// Set the min size for a window that contains this surface.
|
||||
/// This is up to the embedding widget to respect this. Generally, only
|
||||
/// the first surface in a window respects this.
|
||||
pub fn setMinSize(self: *Self, size: Size) void {
|
||||
const priv = self.private();
|
||||
if (priv.min_size) |v| ext.boxedFree(
|
||||
Size,
|
||||
v,
|
||||
);
|
||||
priv.min_size = ext.boxedCopy(
|
||||
Size,
|
||||
&size,
|
||||
);
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"min-size".impl.param_spec);
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
|
|
@ -1832,15 +1971,32 @@ pub const Surface = extern struct {
|
|||
) callconv(.c) void {
|
||||
log.debug("realize", .{});
|
||||
|
||||
// Setup our core surface
|
||||
self.realizeSurface() catch |err| {
|
||||
log.warn("surface failed to realize err={}", .{err});
|
||||
};
|
||||
// If we already have an initialized surface then we notify it.
|
||||
// If we don't, we'll initialize it on the first resize so we have
|
||||
// our proper initial dimensions.
|
||||
const priv = self.private();
|
||||
if (priv.core_surface) |v| realize: {
|
||||
// We need to make the context current so we can call GL functions.
|
||||
// This is required for all surface operations.
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||
log.warn("this error is usually due to a driver or gtk bug", .{});
|
||||
log.warn("this is a common cause of this issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/4950", .{});
|
||||
break :realize;
|
||||
}
|
||||
|
||||
v.renderer.displayRealized() catch |err| {
|
||||
log.warn("core displayRealized failed err={}", .{err});
|
||||
break :realize;
|
||||
};
|
||||
|
||||
self.redraw();
|
||||
}
|
||||
|
||||
// Setup our input method. We do this here because this will
|
||||
// create a strong reference back to ourself and we want to be
|
||||
// able to release that in unrealize.
|
||||
const priv = self.private();
|
||||
priv.im_context.as(gtk.IMContext).setClientWidget(self.as(gtk.Widget));
|
||||
}
|
||||
|
||||
|
|
@ -1942,49 +2098,24 @@ pub const Surface = extern struct {
|
|||
|
||||
// Setup our resize overlay if configured
|
||||
self.resizeOverlaySchedule();
|
||||
}
|
||||
}
|
||||
|
||||
fn resizeOverlaySchedule(self: *Self) void {
|
||||
const priv = self.private();
|
||||
const surface = priv.core_surface orelse return;
|
||||
|
||||
// Only show the resize overlay if its enabled
|
||||
const config = if (priv.config) |c| c.get() else return;
|
||||
switch (config.@"resize-overlay") {
|
||||
.always, .@"after-first" => {},
|
||||
.never => return,
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have resize overlays enabled, setup an idler
|
||||
// to show that. We do this in an idle tick because doing it
|
||||
// during the resize results in flickering.
|
||||
var buf: [32]u8 = undefined;
|
||||
priv.resize_overlay.setLabel(text: {
|
||||
const grid_size = surface.size.grid();
|
||||
break :text std.fmt.bufPrintZ(
|
||||
&buf,
|
||||
"{d} x {d}",
|
||||
.{
|
||||
grid_size.columns,
|
||||
grid_size.rows,
|
||||
},
|
||||
) catch |err| err: {
|
||||
log.warn("unable to format text: {}", .{err});
|
||||
break :err "";
|
||||
};
|
||||
});
|
||||
priv.resize_overlay.schedule();
|
||||
// If we don't have a surface, then we initialize it.
|
||||
self.initSurface() catch |err| {
|
||||
log.warn("surface failed to initialize err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
const RealizeError = Allocator.Error || error{
|
||||
const InitError = Allocator.Error || error{
|
||||
GLAreaError,
|
||||
RendererError,
|
||||
SurfaceError,
|
||||
};
|
||||
|
||||
fn realizeSurface(self: *Self) RealizeError!void {
|
||||
fn initSurface(self: *Self) InitError!void {
|
||||
const priv = self.private();
|
||||
assert(priv.core_surface == null);
|
||||
const gl_area = priv.gl_area;
|
||||
|
||||
// We need to make the context current so we can call GL functions.
|
||||
|
|
@ -1997,16 +2128,6 @@ pub const Surface = extern struct {
|
|||
return error.GLAreaError;
|
||||
}
|
||||
|
||||
// If we already have an initialized surface then we just notify.
|
||||
if (priv.core_surface) |v| {
|
||||
v.renderer.displayRealized() catch |err| {
|
||||
log.warn("core displayRealized failed err={}", .{err});
|
||||
return error.RendererError;
|
||||
};
|
||||
self.redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
const app = Application.default();
|
||||
const alloc = app.allocator();
|
||||
|
||||
|
|
@ -2048,6 +2169,46 @@ pub const Surface = extern struct {
|
|||
|
||||
// Store it!
|
||||
priv.core_surface = surface;
|
||||
|
||||
// Emit the signal that we initialized the surface.
|
||||
Surface.signals.init.impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
fn resizeOverlaySchedule(self: *Self) void {
|
||||
const priv = self.private();
|
||||
const surface = priv.core_surface orelse return;
|
||||
|
||||
// Only show the resize overlay if its enabled
|
||||
const config = if (priv.config) |c| c.get() else return;
|
||||
switch (config.@"resize-overlay") {
|
||||
.always, .@"after-first" => {},
|
||||
.never => return,
|
||||
}
|
||||
|
||||
// If we have resize overlays enabled, setup an idler
|
||||
// to show that. We do this in an idle tick because doing it
|
||||
// during the resize results in flickering.
|
||||
var buf: [32]u8 = undefined;
|
||||
priv.resize_overlay.setLabel(text: {
|
||||
const grid_size = surface.size.grid();
|
||||
break :text std.fmt.bufPrintZ(
|
||||
&buf,
|
||||
"{d} x {d}",
|
||||
.{
|
||||
grid_size.columns,
|
||||
grid_size.rows,
|
||||
},
|
||||
) catch |err| err: {
|
||||
log.warn("unable to format text: {}", .{err});
|
||||
break :err "";
|
||||
};
|
||||
});
|
||||
priv.resize_overlay.schedule();
|
||||
}
|
||||
|
||||
fn ecUrlMouseEnter(
|
||||
|
|
@ -2136,8 +2297,10 @@ pub const Surface = extern struct {
|
|||
gobject.ext.registerProperties(class, &.{
|
||||
properties.config.impl,
|
||||
properties.@"child-exited".impl,
|
||||
properties.@"default-size".impl,
|
||||
properties.@"font-size-request".impl,
|
||||
properties.focused.impl,
|
||||
properties.@"min-size".impl,
|
||||
properties.@"mouse-shape".impl,
|
||||
properties.@"mouse-hidden".impl,
|
||||
properties.@"mouse-hover-url".impl,
|
||||
|
|
@ -2151,6 +2314,8 @@ pub const Surface = extern struct {
|
|||
signals.bell.impl.register(.{});
|
||||
signals.@"clipboard-read".impl.register(.{});
|
||||
signals.@"clipboard-write".impl.register(.{});
|
||||
signals.init.impl.register(.{});
|
||||
signals.@"present-request".impl.register(.{});
|
||||
signals.@"toggle-fullscreen".impl.register(.{});
|
||||
signals.@"toggle-maximize".impl.register(.{});
|
||||
|
||||
|
|
@ -2182,6 +2347,17 @@ pub const Surface = extern struct {
|
|||
.{ .name = "GhosttySurfaceCloseScope" },
|
||||
);
|
||||
};
|
||||
|
||||
/// Simple dimensions struct for the surface used by various properties.
|
||||
pub const Size = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub const getGObjectType = gobject.ext.defineBoxed(
|
||||
Size,
|
||||
.{ .name = "GhosttySurfaceSize" },
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/// The state of the key event while we're doing IM composition.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,289 @@
|
|||
const std = @import("std");
|
||||
const build_config = @import("../../../build_config.zig");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
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");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_window);
|
||||
|
||||
pub const Tab = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = gtk.Box;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyTab",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
/// The active surface is the focus that should be receiving all
|
||||
/// surface-targeted actions. This is usually the focused surface,
|
||||
/// but may also not be focused if the user has selected a non-surface
|
||||
/// widget.
|
||||
pub const @"active-surface" = struct {
|
||||
pub const name = "active-surface";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Surface,
|
||||
.{
|
||||
.nick = "Active Surface",
|
||||
.blurb = "The currently active surface.",
|
||||
.accessor = gobject.ext.typedAccessor(
|
||||
Self,
|
||||
?*Surface,
|
||||
.{
|
||||
.getter = Self.getActiveSurface,
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const config = struct {
|
||||
pub const name = "config";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Config,
|
||||
.{
|
||||
.nick = "Config",
|
||||
.blurb = "The configuration that this surface is using.",
|
||||
.accessor = C.privateObjFieldAccessor("config"),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const title = struct {
|
||||
pub const name = "title";
|
||||
pub const get = impl.get;
|
||||
pub const set = impl.set;
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?[:0]const u8,
|
||||
.{
|
||||
.nick = "Title",
|
||||
.blurb = "The title of the active surface.",
|
||||
.default = null,
|
||||
.accessor = C.privateStringFieldAccessor("title"),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const signals = struct {
|
||||
/// Emitted whenever the tab would like to be closed.
|
||||
pub const @"close-request" = struct {
|
||||
pub const name = "close-request";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The configuration that this surface is using.
|
||||
config: ?*Config = null,
|
||||
|
||||
/// The title to show for this tab. This is usually set to a binding
|
||||
/// with the active surface but can be manually set to anything.
|
||||
title: ?[:0]const u8 = null,
|
||||
|
||||
/// The binding groups for the current active surface.
|
||||
surface_bindings: *gobject.BindingGroup,
|
||||
|
||||
// Template bindings
|
||||
surface: *Surface,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
/// Set the parent of this tab page. This only affects the first surface
|
||||
/// ever created for a tab. If a surface was already created this does
|
||||
/// nothing.
|
||||
pub fn setParent(
|
||||
self: *Self,
|
||||
parent: *CoreSurface,
|
||||
) void {
|
||||
const priv = self.private();
|
||||
priv.surface.setParent(parent);
|
||||
}
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
// If our configuration is null then we get the configuration
|
||||
// from the application.
|
||||
const priv = self.private();
|
||||
if (priv.config == null) {
|
||||
const app = Application.default();
|
||||
priv.config = app.getConfig();
|
||||
}
|
||||
|
||||
// Setup binding groups for surface properties
|
||||
priv.surface_bindings = gobject.BindingGroup.new();
|
||||
priv.surface_bindings.bind(
|
||||
"title",
|
||||
self.as(gobject.Object),
|
||||
"title",
|
||||
.{},
|
||||
);
|
||||
|
||||
// TODO: Eventually this should be set dynamically based on the
|
||||
// current active surface.
|
||||
priv.surface_bindings.setSource(priv.surface.as(gobject.Object));
|
||||
|
||||
// We need to do this so that the title initializes properly,
|
||||
// I think because its a dynamic getter.
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Properties
|
||||
|
||||
/// Get the currently active surface. See the "active-surface" property.
|
||||
/// This does not ref the value.
|
||||
pub fn getActiveSurface(self: *Self) *Surface {
|
||||
const priv = self.private();
|
||||
return priv.surface;
|
||||
}
|
||||
|
||||
/// Returns true if this tab needs confirmation before quitting based
|
||||
/// on the various Ghostty configurations.
|
||||
pub fn getNeedsConfirmQuit(self: *Self) bool {
|
||||
const surface = self.getActiveSurface();
|
||||
const core_surface = surface.core() orelse return false;
|
||||
return core_surface.needsConfirmQuit();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual methods
|
||||
|
||||
fn dispose(self: *Self) callconv(.C) void {
|
||||
const priv = self.private();
|
||||
if (priv.config) |v| {
|
||||
v.unref();
|
||||
priv.config = null;
|
||||
}
|
||||
priv.surface_bindings.setSource(null);
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.C) void {
|
||||
const priv = self.private();
|
||||
if (priv.title) |v| {
|
||||
glib.free(@constCast(@ptrCast(v)));
|
||||
priv.title = null;
|
||||
}
|
||||
priv.surface_bindings.unref();
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
// Signal handlers
|
||||
|
||||
fn surfaceCloseRequest(
|
||||
_: *Surface,
|
||||
scope: *const Surface.CloseScope,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
switch (scope.*) {
|
||||
// Handled upstream... we don't control our window close.
|
||||
.window => return,
|
||||
|
||||
// Presently both the same, results in the tab closing.
|
||||
.surface, .tab => {
|
||||
signals.@"close-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
gobject.ext.ensureType(Surface);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 5,
|
||||
.name = "tab",
|
||||
}),
|
||||
);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.@"active-surface".impl,
|
||||
properties.config.impl,
|
||||
properties.title.impl,
|
||||
});
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("surface", .{});
|
||||
|
||||
// Template Callbacks
|
||||
class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest);
|
||||
|
||||
// Signals
|
||||
signals.@"close-request".impl.register(.{});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
//! Extensions/helpers for GTK objects, following a similar naming
|
||||
//! style to zig-gobject. These should, wherever possible, be Zig-friendly
|
||||
//! wrappers around existing GTK functionality, rather than complex new
|
||||
//! helpers.
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
|
||||
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
|
||||
const copy = gobject.boxedCopy(T.getGObjectType(), ptr);
|
||||
return @ptrCast(@alignCast(copy));
|
||||
}
|
||||
|
||||
/// Wrapper around `gobject.boxedFree` to free a boxed type `T`.
|
||||
pub fn boxedFree(comptime T: type, ptr: ?*T) void {
|
||||
if (ptr) |p| gobject.boxedFree(
|
||||
T.getGObjectType(),
|
||||
p,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
const ancestor_ = widget.getAncestor(gobject.ext.typeFor(T));
|
||||
const ancestor = ancestor_ orelse return null;
|
||||
// We can assert the unwrap because getAncestor above
|
||||
return gobject.ext.cast(T, ancestor).?;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Gtk 4.0;
|
||||
|
||||
template $GhosttyTab: Box {
|
||||
styles [
|
||||
"tab",
|
||||
]
|
||||
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
// A tab currently just contains a surface directly. When we introduce
|
||||
// splits we probably want to replace this with the split widget type.
|
||||
$GhosttySurface surface {
|
||||
close-request => $surface_close_request();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,49 +7,97 @@ 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
|
||||
// to disable this so that terminal programs can capture F10 (such as htop)
|
||||
handle-menubar-accel: false;
|
||||
title: bind (template.active-surface as <$GhosttySurface>).title;
|
||||
|
||||
content: Box {
|
||||
orientation: vertical;
|
||||
content: Adw.TabOverview tab_overview {
|
||||
create-tab => $overview_create_tab();
|
||||
notify::open => $overview_notify_open();
|
||||
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.HeaderBar {
|
||||
visible: bind template.headerbar-visible;
|
||||
Adw.ToolbarView toolbar {
|
||||
top-bar-style: bind template.toolbar-style;
|
||||
bottom-bar-style: bind template.toolbar-style;
|
||||
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: bind (template.active-surface as <$GhosttySurface>).title;
|
||||
};
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
visible: bind template.headerbar-visible;
|
||||
|
||||
[end]
|
||||
Gtk.Box {
|
||||
Gtk.MenuButton {
|
||||
notify::active => $notify_menu_active();
|
||||
icon-name: "open-menu-symbolic";
|
||||
menu-model: main_menu;
|
||||
tooltip-text: _("Main Menu");
|
||||
can-focus: false;
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: bind template.title;
|
||||
};
|
||||
|
||||
[start]
|
||||
Adw.SplitButton {
|
||||
clicked => $new_tab();
|
||||
icon-name: "tab-new-symbolic";
|
||||
tooltip-text: _("New Tab");
|
||||
dropdown-tooltip: _("New Split");
|
||||
menu-model: split_menu;
|
||||
}
|
||||
|
||||
[end]
|
||||
Gtk.Box {
|
||||
Gtk.ToggleButton {
|
||||
icon-name: "view-grid-symbolic";
|
||||
tooltip-text: _("View Open Tabs");
|
||||
active: bind tab_overview.open bidirectional;
|
||||
can-focus: false;
|
||||
focus-on-click: false;
|
||||
}
|
||||
|
||||
Gtk.MenuButton {
|
||||
notify::active => $notify_menu_active();
|
||||
icon-name: "open-menu-symbolic";
|
||||
menu-model: main_menu;
|
||||
tooltip-text: _("Main Menu");
|
||||
can-focus: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$GhosttyDebugWarning {
|
||||
visible: bind template.debug;
|
||||
}
|
||||
[top]
|
||||
Adw.TabBar tab_bar {
|
||||
autohide: bind template.tabs-autohide;
|
||||
expand-tabs: bind template.tabs-wide;
|
||||
view: tab_view;
|
||||
visible: bind template.tabs-visible;
|
||||
}
|
||||
|
||||
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();
|
||||
Box {
|
||||
orientation: vertical;
|
||||
|
||||
$GhosttyDebugWarning {
|
||||
visible: bind template.debug;
|
||||
}
|
||||
|
||||
Adw.ToastOverlay toast_overlay {
|
||||
Adw.TabView tab_view {
|
||||
notify::n-pages => $notify_n_pages();
|
||||
notify::selected-page => $notify_selected_page();
|
||||
close-page => $close_page();
|
||||
page-attached => $page_attached();
|
||||
page-detached => $page_detached();
|
||||
create-window => $tab_create_window();
|
||||
shortcuts: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -157,11 +157,19 @@ pub const App = struct {
|
|||
const ctx_fields = @typeInfo(Context).@"struct".fields;
|
||||
|
||||
switch (event) {
|
||||
.global => |v| global: {
|
||||
.global => |v| {
|
||||
log.debug("found global {s}", .{v.interface});
|
||||
|
||||
// We don't actually do anything with this other than checking
|
||||
// for its existence, so we process this separately.
|
||||
if (std.mem.orderZ(u8, v.interface, "xdg_wm_dialog_v1") == .eq)
|
||||
if (std.mem.orderZ(
|
||||
u8,
|
||||
v.interface,
|
||||
"xdg_wm_dialog_v1",
|
||||
) == .eq) {
|
||||
context.xdg_wm_dialog_present = true;
|
||||
return;
|
||||
}
|
||||
|
||||
inline for (ctx_fields) |field| {
|
||||
const T = getInterfaceType(field) orelse continue;
|
||||
|
|
@ -170,19 +178,21 @@ pub const App = struct {
|
|||
u8,
|
||||
v.interface,
|
||||
T.interface.name,
|
||||
) != .eq) break :global;
|
||||
) == .eq) {
|
||||
log.debug("matched {}", .{T});
|
||||
|
||||
@field(context, field.name) = registry.bind(
|
||||
v.name,
|
||||
T,
|
||||
T.generated_version,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"error binding interface {s} error={}",
|
||||
.{ v.interface, err },
|
||||
);
|
||||
return;
|
||||
};
|
||||
@field(context, field.name) = registry.bind(
|
||||
v.name,
|
||||
T,
|
||||
T.generated_version,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"error binding interface {s} error={}",
|
||||
.{ v.interface, err },
|
||||
);
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -247,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
|
||||
|
|
@ -354,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
|
||||
|
|
@ -382,7 +396,7 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
fn getDecorationMode(self: Window) org.KdeKwinServerDecorationManager.Mode {
|
||||
return switch (self.apprt_window.config.window_decoration) {
|
||||
return switch (self.apprt_window.getWindowDecoration()) {
|
||||
.auto => self.app_context.default_deco_mode orelse .Client,
|
||||
.client => .Client,
|
||||
.server => .Server,
|
||||
|
|
@ -391,12 +405,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) {
|
||||
|
|
@ -409,7 +426,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,
|
||||
|
|
@ -460,14 +477,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,7 @@ pub const Window = struct {
|
|||
}
|
||||
|
||||
pub fn clientSideDecorationEnabled(self: Window) bool {
|
||||
return switch (self.config.window_decoration) {
|
||||
return switch (self.apprt_window.getWindowDecoration()) {
|
||||
.auto, .client => true,
|
||||
.server, .none => false,
|
||||
};
|
||||
|
|
@ -257,14 +254,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(),
|
||||
|
|
@ -306,7 +304,7 @@ pub const Window = struct {
|
|||
};
|
||||
|
||||
hints.flags.decorations = true;
|
||||
hints.decorations.all = switch (self.config.window_decoration) {
|
||||
hints.decorations.all = switch (self.apprt_window.getWindowDecoration()) {
|
||||
.server => true,
|
||||
.auto, .client, .none => false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -157,11 +157,19 @@ pub const App = struct {
|
|||
const ctx_fields = @typeInfo(Context).@"struct".fields;
|
||||
|
||||
switch (event) {
|
||||
.global => |v| global: {
|
||||
.global => |v| {
|
||||
log.debug("found global {s}", .{v.interface});
|
||||
|
||||
// We don't actually do anything with this other than checking
|
||||
// for its existence, so we process this separately.
|
||||
if (std.mem.orderZ(u8, v.interface, "xdg_wm_dialog_v1") == .eq)
|
||||
if (std.mem.orderZ(
|
||||
u8,
|
||||
v.interface,
|
||||
"xdg_wm_dialog_v1",
|
||||
) == .eq) {
|
||||
context.xdg_wm_dialog_present = true;
|
||||
return;
|
||||
}
|
||||
|
||||
inline for (ctx_fields) |field| {
|
||||
const T = getInterfaceType(field) orelse continue;
|
||||
|
|
@ -170,19 +178,21 @@ pub const App = struct {
|
|||
u8,
|
||||
v.interface,
|
||||
T.interface.name,
|
||||
) != .eq) break :global;
|
||||
) == .eq) {
|
||||
log.debug("matched {}", .{T});
|
||||
|
||||
@field(context, field.name) = registry.bind(
|
||||
v.name,
|
||||
T,
|
||||
T.generated_version,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"error binding interface {s} error={}",
|
||||
.{ v.interface, err },
|
||||
);
|
||||
return;
|
||||
};
|
||||
@field(context, field.name) = registry.bind(
|
||||
v.name,
|
||||
T,
|
||||
T.generated_version,
|
||||
) catch |err| {
|
||||
log.warn(
|
||||
"error binding interface {s} error={}",
|
||||
.{ v.interface, err },
|
||||
);
|
||||
return;
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ pub const RepeatableStringMap = @import("config/RepeatableStringMap.zig");
|
|||
pub const RepeatablePath = Config.RepeatablePath;
|
||||
pub const Path = Config.Path;
|
||||
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
||||
pub const WindowDecoration = Config.WindowDecoration;
|
||||
pub const WindowPaddingColor = Config.WindowPaddingColor;
|
||||
pub const BackgroundImagePosition = Config.BackgroundImagePosition;
|
||||
pub const BackgroundImageFit = Config.BackgroundImageFit;
|
||||
|
|
|
|||
|
|
@ -7434,12 +7434,22 @@ pub const BackgroundBlur = union(enum) {
|
|||
};
|
||||
|
||||
/// See window-decoration
|
||||
pub const WindowDecoration = enum {
|
||||
pub const WindowDecoration = enum(c_int) {
|
||||
auto,
|
||||
client,
|
||||
server,
|
||||
none,
|
||||
|
||||
/// 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(
|
||||
WindowDecoration,
|
||||
.{ .name = "GhosttyConfigWindowDecoration" },
|
||||
),
|
||||
|
||||
.none => void,
|
||||
};
|
||||
|
||||
pub fn parseCLI(input_: ?[]const u8) !WindowDecoration {
|
||||
const input = input_ orelse return .auto;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
# Author: Ryan Caloras (ryan@bashhub.com)
|
||||
# Forked from Original Author: Glyph Lefkowitz
|
||||
#
|
||||
# V0.5.0
|
||||
# V0.6.0
|
||||
#
|
||||
|
||||
# General Usage:
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
# Make sure this is bash that's running and return otherwise.
|
||||
# Use POSIX syntax for this line:
|
||||
if [ -z "${BASH_VERSION-}" ]; then
|
||||
return 1;
|
||||
return 1
|
||||
fi
|
||||
|
||||
# We only support Bash 3.1+.
|
||||
|
|
@ -76,13 +76,13 @@ __bp_install_string=$'__bp_trap_string="$(trap -p DEBUG)"\ntrap - DEBUG\n__bp_in
|
|||
# Fails if any of the given variables are readonly
|
||||
# Reference https://stackoverflow.com/a/4441178
|
||||
__bp_require_not_readonly() {
|
||||
local var
|
||||
for var; do
|
||||
if ! ( unset "$var" 2> /dev/null ); then
|
||||
echo "bash-preexec requires write access to ${var}" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
local var
|
||||
for var; do
|
||||
if ! ( unset "$var" 2> /dev/null ); then
|
||||
echo "bash-preexec requires write access to ${var}" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Remove ignorespace and or replace ignoreboth from HISTCONTROL
|
||||
|
|
@ -95,7 +95,7 @@ __bp_adjust_histcontrol() {
|
|||
# Replace ignoreboth with ignoredups
|
||||
if [[ "$histcontrol" == *"ignoreboth"* ]]; then
|
||||
histcontrol="ignoredups:${histcontrol//ignoreboth}"
|
||||
fi;
|
||||
fi
|
||||
export HISTCONTROL="$histcontrol"
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ __bp_sanitize_string() {
|
|||
# It sets a variable to indicate that the prompt was just displayed,
|
||||
# to allow the DEBUG trap to know that the next command is likely interactive.
|
||||
__bp_interactive_mode() {
|
||||
__bp_preexec_interactive_mode="on";
|
||||
__bp_preexec_interactive_mode="on"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ __bp_precmd_invoke_cmd() {
|
|||
# prompt command" by another precmd execution loop. This avoids infinite
|
||||
# recursion.
|
||||
if (( __bp_inside_precmd > 0 )); then
|
||||
return
|
||||
return
|
||||
fi
|
||||
local __bp_inside_precmd=1
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ __bp_preexec_invoke_exec() {
|
|||
__bp_last_argument_prev_command="${1:-}"
|
||||
# Don't invoke preexecs if we are inside of another preexec.
|
||||
if (( __bp_inside_preexec > 0 )); then
|
||||
return
|
||||
return
|
||||
fi
|
||||
local __bp_inside_preexec=1
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ __bp_preexec_invoke_exec() {
|
|||
__bp_install() {
|
||||
# Exit if we already have this installed.
|
||||
if [[ "${PROMPT_COMMAND[*]:-}" == *"__bp_precmd_invoke_cmd"* ]]; then
|
||||
return 1;
|
||||
return 1
|
||||
fi
|
||||
|
||||
trap '__bp_preexec_invoke_exec "$_"' DEBUG
|
||||
|
|
@ -300,7 +300,7 @@ __bp_install() {
|
|||
unset __bp_trap_string
|
||||
if [[ -n "$prior_trap" ]]; then
|
||||
eval '__bp_original_debug_trap() {
|
||||
'"$prior_trap"'
|
||||
'"$prior_trap"'
|
||||
}'
|
||||
preexec_functions+=(__bp_original_debug_trap)
|
||||
fi
|
||||
|
|
@ -323,7 +323,7 @@ __bp_install() {
|
|||
# Set so debug trap will work be invoked in subshells.
|
||||
set -o functrace > /dev/null 2>&1
|
||||
shopt -s extdebug > /dev/null 2>&1
|
||||
fi;
|
||||
fi
|
||||
|
||||
local existing_prompt_command
|
||||
# Remove setting our trap install string and sanitize the existing prompt command string
|
||||
|
|
@ -371,7 +371,7 @@ __bp_install_after_session_init() {
|
|||
if [[ -n "$sanitized_prompt_command" ]]; then
|
||||
# shellcheck disable=SC2178 # PROMPT_COMMAND is not an array in bash <= 5.0
|
||||
PROMPT_COMMAND=${sanitized_prompt_command}$'\n'
|
||||
fi;
|
||||
fi
|
||||
# shellcheck disable=SC2179 # PROMPT_COMMAND is not an array in bash <= 5.0
|
||||
PROMPT_COMMAND+=${__bp_install_string}
|
||||
}
|
||||
|
|
@ -379,4 +379,4 @@ __bp_install_after_session_init() {
|
|||
# Run our install so long as we're not delaying it.
|
||||
if [[ -z "${__bp_delay_install:-}" ]]; then
|
||||
__bp_install_after_session_init
|
||||
fi;
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
|
|||
end
|
||||
|
||||
# Execute SSH with TERM environment variable
|
||||
env TERM="$ssh_term" command ssh $ssh_opts $argv
|
||||
TERM="$ssh_term" command ssh $ssh_opts $argv
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@
|
|||
# You must gracefully exit Ghostty (do not SIGINT) by closing all windows
|
||||
# and quitting. Otherwise, we leave a number of GTK resources around.
|
||||
|
||||
{
|
||||
GTK CSS Provider Leak
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
fun:g_malloc0
|
||||
fun:gtk_css_value_alloc
|
||||
fun:_gtk_css_reference_value_new
|
||||
fun:parse_ruleset
|
||||
fun:gtk_css_provider_load_internal
|
||||
fun:gtk_css_provider_load_from_bytes
|
||||
fun:gtk_css_provider_load_from_string
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GDK SVG Loading Leaks
|
||||
Memcheck:Leak
|
||||
|
|
@ -135,13 +150,34 @@
|
|||
...
|
||||
fun:gsk_gpu_node_processor_process
|
||||
fun:gsk_gpu_frame_render
|
||||
fun:gsk_gpu_renderer_render
|
||||
fun:gsk_renderer_render
|
||||
fun:gtk_widget_render
|
||||
fun:surface_render
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GDK GLArea
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
fun:*alloc
|
||||
...
|
||||
fun:gdk_memory_texture_from_texture
|
||||
fun:gdk_gl_texture_release
|
||||
fun:delete_one_texture
|
||||
fun:g_list_foreach
|
||||
fun:g_list_free_full
|
||||
fun:gtk_gl_area_unrealize
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GDK GLArea Snapshot
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:*alloc
|
||||
...
|
||||
fun:gtk_gl_area_snapshot
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GSK GPU Rendering
|
||||
Memcheck:Leak
|
||||
|
|
@ -351,6 +387,7 @@
|
|||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
fun:*alloc
|
||||
...
|
||||
fun:FcFontSet*
|
||||
...
|
||||
fun:fc_thread_func
|
||||
|
|
|
|||