mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-14 18:01:58 +00:00
Merge remote-tracking branch 'upstream/main' into add-serbian-translation
This commit is contained in:
6
.github/VOUCHED.td
vendored
6
.github/VOUCHED.td
vendored
@@ -46,6 +46,7 @@ alexfeijoo44
|
||||
alexjuca
|
||||
alosarjos
|
||||
amadeus
|
||||
and-rs
|
||||
andrejdaskalov
|
||||
anhthang
|
||||
anmitalidev
|
||||
@@ -160,6 +161,7 @@ j-c-m
|
||||
j0hnm4r5
|
||||
jacobsandlund
|
||||
jake-stewart
|
||||
jakeriksen
|
||||
-jamesarch Bad AI etiquette
|
||||
jamylak
|
||||
jarred-sumner
|
||||
@@ -183,6 +185,7 @@ khipp
|
||||
kierancanter
|
||||
kirwiisp
|
||||
kjvdven
|
||||
kleshzz
|
||||
kloneets
|
||||
knu
|
||||
-kody-w
|
||||
@@ -211,6 +214,7 @@ marrocco-simone
|
||||
masterflitzer
|
||||
matkotiric
|
||||
mattn
|
||||
mgsloan
|
||||
micaeljarniac
|
||||
michielvk
|
||||
miguelelgallo
|
||||
@@ -301,6 +305,7 @@ simonbcn
|
||||
slsrepo
|
||||
steven-tk
|
||||
sunshine-syz
|
||||
svector-anu
|
||||
svmhdvn
|
||||
tacherasasi
|
||||
-tangivis
|
||||
@@ -309,6 +314,7 @@ tbrundige
|
||||
tdgroot
|
||||
tdslot
|
||||
thirstycrow
|
||||
-thomas-trijindev No human in the loop
|
||||
thoutbeckers
|
||||
ticclick
|
||||
tnagatomi
|
||||
|
||||
4
.github/workflows/release-tag.yml
vendored
4
.github/workflows/release-tag.yml
vendored
@@ -160,7 +160,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.9.0
|
||||
SPARKLE_VERSION: 2.9.6
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -328,7 +328,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.9.0
|
||||
SPARKLE_VERSION: 2.9.6
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
||||
6
.github/workflows/release-tip.yml
vendored
6
.github/workflows/release-tip.yml
vendored
@@ -591,7 +591,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.9.0
|
||||
SPARKLE_VERSION: 2.9.6
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -847,7 +847,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.9.0
|
||||
SPARKLE_VERSION: 2.9.6
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -1044,7 +1044,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.9.0
|
||||
SPARKLE_VERSION: 2.9.6
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
||||
@@ -125,8 +125,8 @@
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.android_ndk = .{ .path = "./pkg/android-ndk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz",
|
||||
.hash = "N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl",
|
||||
.url = "https://deps.files.ghostty.org/ghostty-themes-release-20260824-153547-75c93ee.tgz",
|
||||
.hash = "N-V-__8AAGZkBACH0haGC9R-hfNPYWxu16hr-ydEsolad9GV",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
||||
6
build.zig.zon.json
generated
6
build.zig.zon.json
generated
@@ -59,10 +59,10 @@
|
||||
"url": "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz",
|
||||
"hash": "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs="
|
||||
},
|
||||
"N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl": {
|
||||
"N-V-__8AAGZkBACH0haGC9R-hfNPYWxu16hr-ydEsolad9GV": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz",
|
||||
"hash": "sha256-6ph4RxQg7lsS5/L/SACZyVTqUOVzob34P0PhBcm+Y/A="
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-release-20260824-153547-75c93ee.tgz",
|
||||
"hash": "sha256-sf0fMQV/l8EnTbA8bORJYIN6GkH1InXLxkruLm40654="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
|
||||
6
build.zig.zon.nix
generated
6
build.zig.zon.nix
generated
@@ -206,11 +206,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl";
|
||||
name = "N-V-__8AAGZkBACH0haGC9R-hfNPYWxu16hr-ydEsolad9GV";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz";
|
||||
hash = "sha256-6ph4RxQg7lsS5/L/SACZyVTqUOVzob34P0PhBcm+Y/A=";
|
||||
url = "https://deps.files.ghostty.org/ghostty-themes-release-20260824-153547-75c93ee.tgz";
|
||||
hash = "sha256-sf0fMQV/l8EnTbA8bORJYIN6GkH1InXLxkruLm40654=";
|
||||
unpack = false;
|
||||
};
|
||||
}
|
||||
|
||||
2
build.zig.zon.txt
generated
2
build.zig.zon.txt
generated
@@ -9,7 +9,7 @@ https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918
|
||||
https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz
|
||||
https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz
|
||||
https://deps.files.ghostty.org/gettext-0.24.tar.gz
|
||||
https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz
|
||||
https://deps.files.ghostty.org/ghostty-themes-release-20260824-153547-75c93ee.tgz
|
||||
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
|
||||
https://deps.files.ghostty.org/gobject-2026-07-28-36-1.tar.zst
|
||||
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-release-20260810-152212-0173c3c.tgz",
|
||||
"dest": "vendor/p/N-V-__8AAPpcBAD4_75xLBbLiYqdojOwQP74eoWmpL3jPrBl",
|
||||
"sha256": "ea9878471420ee5b12e7f2ff480099c954ea50e573a1bdf83f43e105c9be63f0"
|
||||
"url": "https://deps.files.ghostty.org/ghostty-themes-release-20260824-153547-75c93ee.tgz",
|
||||
"dest": "vendor/p/N-V-__8AAGZkBACH0haGC9R-hfNPYWxu16hr-ydEsolad9GV",
|
||||
"sha256": "b1fd1f31057f97c1274db03c6ce44960837a1a41f52275cbc64aee2e6e34eb9e"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
||||
@@ -1099,8 +1099,8 @@
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sparkle-project/Sparkle";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.5.1;
|
||||
kind = exactVersion;
|
||||
version = 2.9.6;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "21d8df80440b1ca3b65fa82e40782f1e5a9e6ba2",
|
||||
"version" : "2.9.0"
|
||||
"revision" : "ac2def288cbff5cfc7df3ffef6abdf45b72bcb0a",
|
||||
"version" : "2.9.6"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -2378,31 +2378,29 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||
fn setSelectionAndCopy(self: *Surface, sel: terminal.Selection) !void {
|
||||
try self.setSelection(sel);
|
||||
|
||||
// If copy on select is false then exit early.
|
||||
if (self.config.copy_on_select == .false) return;
|
||||
|
||||
switch (self.config.copy_on_select) {
|
||||
.false => unreachable, // handled above with an early exit
|
||||
.none => {},
|
||||
|
||||
// The selection clipboard is set if supported, otherwise nothing is copied.
|
||||
.primary => try self.copySelectionToClipboards(
|
||||
sel,
|
||||
&.{.selection},
|
||||
.mixed,
|
||||
),
|
||||
|
||||
// Only the standard clipboard is set.
|
||||
.clipboard => try self.copySelectionToClipboards(
|
||||
sel,
|
||||
&.{.standard},
|
||||
.mixed,
|
||||
),
|
||||
|
||||
// Both standard and selection clipboards are set.
|
||||
.clipboard => try self.copySelectionToClipboards(
|
||||
.both => try self.copySelectionToClipboards(
|
||||
sel,
|
||||
&.{ .standard, .selection },
|
||||
.mixed,
|
||||
),
|
||||
|
||||
// The selection clipboard is set if supported, otherwise the standard.
|
||||
.true => {
|
||||
const clipboard: apprt.Clipboard = if (self.rt_surface.supportsClipboard(.selection))
|
||||
.selection
|
||||
else
|
||||
.standard;
|
||||
try self.copySelectionToClipboards(
|
||||
sel,
|
||||
&.{clipboard},
|
||||
.mixed,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3891,7 +3889,7 @@ pub fn mouseButtonCallback(
|
||||
// The selection clipboard is only updated for left-click drag when
|
||||
// the left button is released. This is to avoid the clipboard
|
||||
// being updated on every mouse move which would be noisy.
|
||||
if (self.config.copy_on_select != .false) {
|
||||
if (self.config.copy_on_select != .none) {
|
||||
const prev_ = self.io.terminal.screens.active.selection;
|
||||
if (prev_) |prev| {
|
||||
try self.setSelectionAndCopy(terminal.Selection.init(
|
||||
@@ -4049,22 +4047,16 @@ pub fn mouseButtonCallback(
|
||||
}
|
||||
}
|
||||
|
||||
// Middle-click paste source follows copy-on-select: when copy-on-select
|
||||
// targets the selection clipboard, middle-click reads from it; when
|
||||
// copy-on-select targets the system clipboard, middle-click reads from
|
||||
// that instead. Falls back to the standard clipboard on platforms that
|
||||
// do not support the selection clipboard.
|
||||
// Middle-click action, either ignore, or paste from clipboard or paste from the selection clipboard if supported.
|
||||
if (button == .middle and action == .press) switch (self.config.middle_click_action) {
|
||||
.ignore => {},
|
||||
.@"clipboard-paste" => {
|
||||
_ = try self.startClipboardRequest(.standard, .{ .paste = .standard });
|
||||
},
|
||||
.@"primary-paste" => {
|
||||
const clipboard: apprt.Clipboard = switch (self.config.copy_on_select) {
|
||||
.clipboard => .standard,
|
||||
.true, .false => if (self.rt_surface.supportsClipboard(.selection))
|
||||
.selection
|
||||
else
|
||||
.standard,
|
||||
};
|
||||
_ = try self.startClipboardRequest(clipboard, .{ .paste = clipboard });
|
||||
if (self.rt_surface.supportsClipboard(.selection)) {
|
||||
_ = try self.startClipboardRequest(.selection, .{ .paste = .selection });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -853,14 +853,24 @@ pub const Surface = extern struct {
|
||||
|
||||
pub fn bindIsSplit(self: *Self, tree: *SplitTree) void {
|
||||
const priv = self.private();
|
||||
if (priv.is_split_binding) |bind| bind.unbind();
|
||||
if (priv.is_split_binding) |binding| {
|
||||
binding.unbind();
|
||||
binding.unref();
|
||||
priv.is_split_binding = null;
|
||||
}
|
||||
|
||||
priv.is_split_binding = tree.as(gobject.Object).bindProperty(
|
||||
const binding = tree.as(gobject.Object).bindProperty(
|
||||
"is-split",
|
||||
self.as(gobject.Object),
|
||||
"is-split",
|
||||
.{ .sync_create = true },
|
||||
);
|
||||
// The ref created by bindProperty is owned by the binding itself.
|
||||
// We need another ref to prevent the binding object from being
|
||||
// freed if the source object (SplitTree) is finalized. Otherwise
|
||||
// our pointer to the binding could become stale.
|
||||
binding.ref();
|
||||
priv.is_split_binding = binding;
|
||||
}
|
||||
|
||||
/// Callback used to determine whether unfocused-split-fill / unfocused-split-opacity
|
||||
@@ -1567,7 +1577,11 @@ pub const Surface = extern struct {
|
||||
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/a7738a4d269bfdf4d8d5429ca73ccdd9b2450421
|
||||
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/9759d3fd81129608dd78116001928f2aed974ead
|
||||
if (gtk_xft_dpi <= 0) {
|
||||
log.warn("gtk-xft-dpi has invalid value ({}), using default", .{gtk_xft_dpi});
|
||||
// -1 is a valid value which specifies default scale.
|
||||
// https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html
|
||||
if (gtk_xft_dpi != -1) {
|
||||
log.warn("gtk-xft-dpi has invalid value ({}), using default", .{gtk_xft_dpi});
|
||||
}
|
||||
break :xft_scale 1.0;
|
||||
}
|
||||
|
||||
@@ -1881,6 +1895,12 @@ pub const Surface = extern struct {
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
if (priv.is_split_binding) |binding| {
|
||||
binding.unbind();
|
||||
binding.unref();
|
||||
priv.is_split_binding = null;
|
||||
}
|
||||
|
||||
if (priv.vadj_signal_group) |group| {
|
||||
group.setTarget(null);
|
||||
group.as(gobject.Object).unref();
|
||||
|
||||
@@ -89,7 +89,8 @@ pub const SurfaceScrolledWindow = extern struct {
|
||||
const priv = self.private();
|
||||
|
||||
if (priv.config_binding) |binding| {
|
||||
binding.as(gobject.Object).unref();
|
||||
binding.unbind();
|
||||
binding.unref();
|
||||
priv.config_binding = null;
|
||||
}
|
||||
|
||||
@@ -168,18 +169,23 @@ pub const SurfaceScrolledWindow = extern struct {
|
||||
|
||||
// Unbind old config binding if it exists
|
||||
if (priv.config_binding) |binding| {
|
||||
binding.as(gobject.Object).unref();
|
||||
binding.unbind();
|
||||
binding.unref();
|
||||
priv.config_binding = null;
|
||||
}
|
||||
|
||||
// Bind config from surface to our config property
|
||||
if (priv.surface) |surface| {
|
||||
priv.config_binding = surface.as(gobject.Object).bindProperty(
|
||||
const binding = surface.as(gobject.Object).bindProperty(
|
||||
properties.config.name,
|
||||
self.as(gobject.Object),
|
||||
properties.config.name,
|
||||
.{ .sync_create = true },
|
||||
);
|
||||
// Keep another ref, otherwise the binding would be freed and
|
||||
// our pointer become stale if the surface gets finalized.
|
||||
binding.ref();
|
||||
priv.config_binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ pub const compatibility = std.StaticStringMap(
|
||||
// Ghostty 1.4 renamed `scrollback-limit` to `scrollback-limit-bytes`
|
||||
// when `scrollback-limit-lines` was added so the units are explicit.
|
||||
.{ "scrollback-limit", cli.compatibilityRenamed(Config, "scrollback-limit-bytes") },
|
||||
|
||||
// Ghostty 1.4 updated "copy-on-select", allow copying to the selection
|
||||
// clipboard (on supported operating systems), the system clipboard, or
|
||||
// both. The semantics also changed but this is the correct mapping.
|
||||
// See: https://github.com/ghostty-org/ghostty/pull/12604
|
||||
.{ "copy-on-select", compatCopyOnSelect },
|
||||
});
|
||||
|
||||
/// Set Ghostty's graphical user interface language to a language other than the
|
||||
@@ -2517,25 +2523,28 @@ keybind: Keybinds = .{},
|
||||
/// limit per surface is double.
|
||||
@"image-storage-limit": u32 = 320 * 1000 * 1000,
|
||||
|
||||
/// Whether to automatically copy selected text to the clipboard. `true`
|
||||
/// will prefer to copy to the selection clipboard, otherwise it will copy to
|
||||
/// the system clipboard.
|
||||
/// Whether to automatically copy selected text to the clipboard.
|
||||
///
|
||||
/// The value `clipboard` will always copy text to the selection clipboard
|
||||
/// as well as the system clipboard.
|
||||
/// Valid values:
|
||||
///
|
||||
/// Middle-click primary paste (see `middle-click-action`) is enabled by
|
||||
/// default even if this is `false`. The clipboard it pastes from follows
|
||||
/// this setting: with `true` (or `false`) it reads from the selection
|
||||
/// clipboard (falling back to the system clipboard on platforms without a
|
||||
/// selection clipboard); with `clipboard` it reads from the system
|
||||
/// clipboard.
|
||||
/// * `none` - Do not copy selected text automatically.
|
||||
///
|
||||
/// The default value is true on Linux and macOS.
|
||||
/// * `primary` - On Linux, copy to the selection clipboard only. This has no
|
||||
/// effect on macOS. (Available since: 1.4.0)
|
||||
///
|
||||
/// * `clipboard` - Copy text to the system clipboard only.
|
||||
///
|
||||
/// * `both` - Copy to both clipboards on Linux, and only the system clipboard
|
||||
/// on macOS. (Available since: 1.4.0)
|
||||
///
|
||||
/// For backward compatibility and convenience, a value of `true` is the same as
|
||||
/// `primary` on Linux and `clipboard` on macOS, and `false` is an alias for
|
||||
/// `none`.
|
||||
///
|
||||
/// The default value is `primary` on Linux and `none` otherwise.
|
||||
@"copy-on-select": CopyOnSelect = switch (builtin.os.tag) {
|
||||
.linux => .true,
|
||||
.macos => .true,
|
||||
else => .false,
|
||||
.linux => .primary,
|
||||
else => .none,
|
||||
},
|
||||
|
||||
/// The action to take when the user right-clicks on the terminal surface.
|
||||
@@ -2554,8 +2563,9 @@ keybind: Keybinds = .{},
|
||||
/// The action to take when the user middle-clicks on the terminal surface.
|
||||
///
|
||||
/// Valid values:
|
||||
/// * `primary-paste` - Paste from the selection (or system) clipboard per
|
||||
/// `copy-on-select`.
|
||||
/// * `primary-paste` - Paste from the selection clipboard on Linux.
|
||||
/// Does nothing on macOS.
|
||||
/// * `clipboard-paste` - Paste from the system clipboard.
|
||||
/// * `ignore` - Do nothing, ignore the middle click.
|
||||
///
|
||||
/// The default value is `primary-paste`.
|
||||
@@ -5047,6 +5057,31 @@ fn compatMacOSDockDropBehavior(
|
||||
return false;
|
||||
}
|
||||
|
||||
fn compatCopyOnSelect(
|
||||
self: *Config,
|
||||
alloc: Allocator,
|
||||
key: []const u8,
|
||||
value: ?[]const u8,
|
||||
) bool {
|
||||
_ = alloc;
|
||||
assert(std.mem.eql(u8, key, "copy-on-select"));
|
||||
|
||||
if (std.mem.eql(u8, value orelse "", "true")) {
|
||||
self.@"copy-on-select" = switch (builtin.os.tag) {
|
||||
.linux, .freebsd => .primary,
|
||||
else => .clipboard,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, value orelse "", "false")) {
|
||||
self.@"copy-on-select" = .none;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Add a diagnostic message to the config with the given string.
|
||||
/// This is always added with a location of "none".
|
||||
pub fn addDiagnosticFmt(
|
||||
@@ -8728,16 +8763,20 @@ pub const RepeatableLink = struct {
|
||||
|
||||
/// Options for copy on select behavior.
|
||||
pub const CopyOnSelect = enum {
|
||||
/// Disables copy on select entirely.
|
||||
false,
|
||||
/// Disables copy on select entirely. This is the default on macOS.
|
||||
none,
|
||||
|
||||
/// Copy on select is enabled, but goes to the selection clipboard.
|
||||
/// This is not supported on platforms such as macOS. This is the default.
|
||||
true,
|
||||
/// This is not supported on platforms such as macOS. This is the default
|
||||
/// on Linux.
|
||||
primary,
|
||||
|
||||
/// Copy on select is enabled and goes to the system clipboard.
|
||||
clipboard,
|
||||
|
||||
/// Copy on select is enabled and goes to both the system clipboard
|
||||
/// and the selection clipboard (for Linux).
|
||||
clipboard,
|
||||
both,
|
||||
};
|
||||
|
||||
/// Options for right-click actions.
|
||||
@@ -8761,8 +8800,10 @@ pub const RightClickAction = enum {
|
||||
|
||||
/// Options for middle-click actions.
|
||||
pub const MiddleClickAction = enum {
|
||||
/// Paste from the selection/standard clipboard per `copy-on-select`.
|
||||
/// Paste from the selection clipboard.
|
||||
@"primary-paste",
|
||||
/// Paste from the standard clipboard.
|
||||
@"clipboard-paste",
|
||||
|
||||
/// No action is taken on middle click.
|
||||
ignore,
|
||||
|
||||
@@ -228,7 +228,7 @@ pub const std_options: std.Options = .{
|
||||
.allow_stack_tracing = if (builtin.target.os.tag.isDarwin() and builtin.target.os.tag != .macos)
|
||||
false
|
||||
else
|
||||
builtin.strip_debug_info,
|
||||
!builtin.strip_debug_info,
|
||||
};
|
||||
|
||||
test {
|
||||
|
||||
@@ -1194,8 +1194,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
const should_run =
|
||||
// Non-visible windows never vsync
|
||||
self.visible and
|
||||
// Non-focused windows only render on-demand
|
||||
self.focused and
|
||||
// Only vsync if we have cell changes or animation
|
||||
(self.cells_rebuilt or self.animationWake() != null);
|
||||
|
||||
|
||||
@@ -210,6 +210,15 @@ pub const LoadingImage = struct {
|
||||
return error.UnsupportedMedium;
|
||||
}
|
||||
|
||||
// POSIX shared memory names must begin with a slash, contain at
|
||||
// least one character after it, contain no other slashes, and fit
|
||||
// within NAME_MAX. Some shm_open implementations accept names
|
||||
// without the leading slash, but the Kitty protocol does not.
|
||||
if (!validSharedMemoryName(path, posix.NAME_MAX)) {
|
||||
log.warn("invalid shared memory name", .{});
|
||||
return error.InvalidData;
|
||||
}
|
||||
|
||||
// Since we're only supporting posix then max_path_bytes should
|
||||
// be enough to stack allocate the path.
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
@@ -831,6 +840,16 @@ pub const Rect = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns whether a name follows the POSIX shared memory name format.
|
||||
fn validSharedMemoryName(name: []const u8, name_max: usize) bool {
|
||||
if (name.len < 2 or name.len > name_max or name[0] != '/') return false;
|
||||
for (name[1..]) |c| {
|
||||
if (c == '/' or c == 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns true if `path` is `dir` or is contained within it, requiring a
|
||||
/// path-separator boundary so similarly prefixed directories do not match.
|
||||
fn isPathInDir(dir: []const u8, path: []const u8) bool {
|
||||
@@ -852,6 +871,53 @@ test "temporary file path must be inside directory" {
|
||||
try testing.expect(!isPathInDir("/custom/tmp", "/custom/tmp-suffix/tty-graphics-protocol-image.data"));
|
||||
}
|
||||
|
||||
test "shared memory names follow POSIX rules" {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expect(validSharedMemoryName("/kitty", 8));
|
||||
try testing.expect(validSharedMemoryName("/1234567", 8));
|
||||
|
||||
try testing.expect(!validSharedMemoryName("", 8));
|
||||
try testing.expect(!validSharedMemoryName("/", 8));
|
||||
try testing.expect(!validSharedMemoryName("kitty", 8));
|
||||
try testing.expect(!validSharedMemoryName("/kitty/image", 16));
|
||||
try testing.expect(!validSharedMemoryName("/kitty\x00image", 16));
|
||||
try testing.expect(!validSharedMemoryName("/12345678", 8));
|
||||
}
|
||||
|
||||
test "image load rejects invalid POSIX shared memory names" {
|
||||
if (comptime builtin.abi.isAndroid() or
|
||||
builtin.target.os.tag == .windows or
|
||||
!builtin.link_libc)
|
||||
{
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var cmd: command.Command = .{
|
||||
.control = .{ .transmit = .{
|
||||
.format = .rgb,
|
||||
.medium = .shared_memory,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.image_id = 31,
|
||||
} },
|
||||
.data = try alloc.dupe(u8, "kitty-without-leading-slash"),
|
||||
};
|
||||
defer cmd.deinit(alloc);
|
||||
|
||||
try testing.expectError(
|
||||
error.InvalidData,
|
||||
LoadingImage.init(testing.io, alloc, &cmd, .{
|
||||
.file = false,
|
||||
.temporary_file = .disabled,
|
||||
.shared_memory = true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
test "shared memory range with offset and size" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
||||
@@ -317,6 +317,19 @@ pub fn RefCountedSet(
|
||||
|
||||
if (id < self.next_id) {
|
||||
if (items[id].meta.ref == 0) {
|
||||
// Requested ID is dead, but if the value exists not under
|
||||
// another ID then ref count that and increase the ID.
|
||||
if (self.lookupContext(base, value, ctx)) |existing_id| {
|
||||
// Notify the context that the value is "deleted"
|
||||
// because we're reusing the existing value in the
|
||||
// set. This allows callers to clean up any
|
||||
// resources associated with the value.
|
||||
if (comptime @hasDecl(Context, "deleted")) ctx.deleted(value);
|
||||
|
||||
items[existing_id].meta.ref += 1;
|
||||
return existing_id;
|
||||
}
|
||||
|
||||
// See comment in `addContext` for details.
|
||||
if (self.psl_stats[self.psl_stats.len - 1] > 0) {
|
||||
@branchHint(.cold);
|
||||
@@ -324,8 +337,7 @@ pub fn RefCountedSet(
|
||||
}
|
||||
|
||||
self.deleteItem(base, id, ctx);
|
||||
|
||||
const added_id = self.upsert(base, value, id, ctx);
|
||||
const added_id = self.insert(base, value, id, ctx);
|
||||
|
||||
items[added_id].meta.ref += 1;
|
||||
|
||||
@@ -589,24 +601,6 @@ pub fn RefCountedSet(
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Find the provided value in the hash table, or add a new item
|
||||
/// for it if not present. If a new item is added, `new_id` will
|
||||
/// be used as the ID. If an existing item is found, the `new_id`
|
||||
/// is ignored and the existing item's ID is returned.
|
||||
fn upsert(self: *Self, base: anytype, value: T, new_id: Id, ctx: Context) Id {
|
||||
// If the item already exists, return it.
|
||||
if (self.lookupContext(base, value, ctx)) |id| {
|
||||
// Notify the context that the value is "deleted" because
|
||||
// we're reusing the existing value in the set. This allows
|
||||
// callers to clean up any resources associated with the value.
|
||||
if (comptime @hasDecl(Context, "deleted")) ctx.deleted(value);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
return self.insert(base, value, new_id, ctx);
|
||||
}
|
||||
|
||||
/// Insert the given value into the hash table with the given ID.
|
||||
///
|
||||
/// If runtime safety is enabled, asserts that
|
||||
@@ -764,6 +758,65 @@ pub fn RefCountedSet(
|
||||
};
|
||||
}
|
||||
|
||||
test "addWithId dead id resolving to an existing value" {
|
||||
const alloc = testing.allocator;
|
||||
const TestSet = RefCountedSet(
|
||||
u32,
|
||||
u16,
|
||||
u16,
|
||||
struct {
|
||||
pub fn hash(_: *const @This(), value: u32) u64 {
|
||||
return std.hash.int(value);
|
||||
}
|
||||
|
||||
pub fn eql(_: *const @This(), a: u32, b: u32) bool {
|
||||
return a == b;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const layout: TestSet.Layout = .init(8);
|
||||
const buf = try alloc.alignedAlloc(
|
||||
u8,
|
||||
TestSet.base_align,
|
||||
layout.total_size,
|
||||
);
|
||||
defer alloc.free(buf);
|
||||
|
||||
var set: TestSet = .init(.init(buf), layout, .{});
|
||||
|
||||
// Create a dead item between two live ones so that it can't
|
||||
// be reaped by the trim loop in `add`, then release it. This
|
||||
// mirrors a page style set after a styled run is erased.
|
||||
const live = try set.add(buf, 11);
|
||||
const released = try set.add(buf, 22);
|
||||
const last = try set.add(buf, 33);
|
||||
set.release(buf, released);
|
||||
try testing.expectEqual(@as(usize, 2), set.count());
|
||||
|
||||
// Request the dead ID for a value that is already live under
|
||||
// a different ID: we must resolve to the existing item and
|
||||
// take a reference, without changing the living count.
|
||||
const resolved = (try set.addWithId(buf, 11, released)).?;
|
||||
try testing.expectEqual(live, resolved);
|
||||
try testing.expectEqual(@as(u16, 2), set.refCount(buf, live));
|
||||
try testing.expectEqual(@as(usize, 2), set.count());
|
||||
|
||||
// The living count must agree with the iterator.
|
||||
var it = set.iterator(buf);
|
||||
var iterated: usize = 0;
|
||||
while (it.next()) |_| iterated += 1;
|
||||
try testing.expectEqual(set.count(), iterated);
|
||||
|
||||
// The dead slot is still reusable for a value that is not
|
||||
// in the set: the requested ID must be used (null return).
|
||||
try testing.expectEqual(@as(?u16, null), try set.addWithId(buf, 44, released));
|
||||
try testing.expectEqual(@as(u16, 1), set.refCount(buf, released));
|
||||
try testing.expectEqual(@as(usize, 3), set.count());
|
||||
|
||||
_ = last;
|
||||
}
|
||||
|
||||
test "iterator visits live entries in ID order" {
|
||||
const alloc = testing.allocator;
|
||||
const TestSet = RefCountedSet(
|
||||
|
||||
Reference in New Issue
Block a user