22 Commits
tip ... v1.1.3

Author SHA1 Message Date
Mitchell Hashimoto
889478f310 PACKAGING.md: remove the note about 1.1.0/1.1.1/1.1.2 2025-03-24 07:06:47 -07:00
Jeffrey C. Ollie
8bd3073cb7 build: generate a build.zig.zon.txt file for easy zig fetch scripting
This fixes a regression in 1.1.1/1.1.2 where our PACKAGING docs mention
using `fetch-zig-cache.sh` but it was removed. This commit adds it back,
generating its contents from the build.zig.zon file (via zon2nix which
we use for our Nix packaging).

For packagers, there are no dependency changes: you still need Zig and
POSIX sh. For release time, Ghostty has a new dependency on `jq` but
otherwise the release process is the same. The check-zig-cache.sh script
is updated to generate the new build.zig.zon.txt file.
2025-03-23 14:41:55 -07:00
Mitchell Hashimoto
acafe881a0 Add back fetch-zig-cache.sh for packaging
See the comment on more details, which also covers when and how we can
remove this.
2025-03-23 14:40:50 -07:00
Mitchell Hashimoto
80030f20f0 backport(1.1.x) gtk: ensure that the content scale is always positive (#6890)
If GTK reports a negative or zero scale, we ignore that and use 1.0 for
the scale.

Backport of #5954
2025-03-23 14:38:59 -07:00
Aaron Ruan
602b47ff4e fix: use surfaceConfig.backgroundColor to determine if the theme is light or dark
Signed-off-by: Aaron Ruan <i@ar212.com>
2025-03-23 14:33:40 -07:00
Bryan Lee
3a7fb03ef9 Fix new window focus when quick terminal is open 2025-03-23 14:33:01 -07:00
Yappaholic
3423cfdc71 Fix elvish sudo integration and update documentation 2025-03-23 14:31:55 -07:00
Mikhail Borisov
b2a8211f9e Fix Terminal Inspector option turns inactive if toggled in the Quick Terminal 2025-03-23 14:31:19 -07:00
Jeffrey C. Ollie
8fdb3e6e99 gtk: ensure that the content scale is always positive
If GTK reports a negative or zero scale, we ignore that and use 1.0 for
the scale.

Backport of #5954
2025-03-23 16:30:49 -05:00
Bryan Lee
f1c510f9fe Make equalize_splits action only affect current window 2025-03-23 14:29:36 -07:00
Mitchell Hashimoto
3264051f8d apprt/embedded: utf8 encoding buffer lifetime must extend beyond call
Fixes #6821

UTF8 translation using KeymapDarwin requires a buffer and the buffer was
stack allocated in the coreKeyEvent call and returned from the function.
We need the buffer to live longer than this.

Long term, we're removing KeymapDarwin (there is a whole TODO comment in
there about how to do it), but this fixes a real problem today.
2025-03-23 14:28:37 -07:00
Mitchell Hashimoto
b2ad90fe18 apprt/gtk: any preedit change should note a composing state
Fixes #6772

When typing Korean with the fcitx5-hangful input method, moving between
graphemes does not trigger a preedit end/start cycle and instead just
clears the preexisting preedit and reuses the started state.

Every other input method we've tested up until now doesn't do this. We
need to mark composing set to "false" in "commit" because some input
methods on the contrary fail to ever call END.

What is the point of start/end events if they are just ignored depending
on the whim of the input method? Nothing. That's what. Its all a mess
that GTK should be protecting us from but it doesn't and now its the app
developer's problem. I'm frustrated because I feel like the point of an
app framework is to mask this kind of complexity from the app developer
and I'm playing whack-a-mole with input methods.

Well, here's another whack. Let's see if it works.
2025-03-23 14:26:10 -07:00
Qwerasd
9d5e64a76f fix(Metal): force a full rebuild in setFontGrid
This was causing garbled text due to a non-rebuilt rows referencing an
outdated atlas when the DPI changed but not the grid dimensions, which
could be caused by a variety of things such as the quick terminal
slide-in, dpi scaling changes on sleep/wake, moving windows between
displays because of closing/opening the laptop lid, etc.
2025-03-23 14:23:10 -07:00
Mitchell Hashimoto
691fbccbfc terminal: increase CSI max params to 24 to accept Kakoune sequence
See #5930

Kakoune sends a real SGR sequence with 17 parameters. Our previous max
was 16 so we through away the entire sequence. This commit increases the
max rather than fundamentally addressing limitations.

Practically, it took us this long to witness a real world sequence that
exceeded our previous limit. We may need to revisit this in the future,
but this is an easy fix for now.

In the future, as the comment states in this diff, we should probably
look into a rare slow path where we heap allocate to accept up to some
larger size (but still would need a cap to avoid DoS). For now,
increasing to 24 slightly increases our memory usage but shouldn't
result in any real world issues.
2025-03-23 14:21:33 -07:00
rhodes-b
92bd6b6244 dont use shift+equal combo 2025-03-23 14:19:38 -07:00
Tim Culverhouse
ccf72dfbf7 termio: use modified backend
In Termio.init, we make a copy of backend and modify it by calling
initTerminal. However, we used the original in the struct definition.
This lead to the pty being opened with a size 0,0.
2025-03-23 14:18:48 -07:00
Tim Culverhouse
196bc55757 termio: prevent responses to non-query OSC 21 sequences
The Ghostty implementation of OSC 21 (Kitty color protocol) currently
responds to *all* OSC 21 sequences. It should not respond to a set, nor
a reset command. Fix the implementation so that we only respond if a
query was received.
2025-03-23 14:18:13 -07:00
David Wales
f458d48f9b gtk: work around oversized drag handle for GtkPaned
Improves #3020.

Based on recommendation from upstream Gtk issue:
https://gitlab.gnome.org/GNOME/gtk/-/issues/4484#note_2362002

Without this, it's not possible to select the first character on the
right-hand side of a split.
2025-03-23 14:15:18 -07:00
Maciej Bartczak
adf7c87cb2 gtk: update the window title when grabbing tab focus 2025-03-23 14:13:47 -07:00
Mitchell Hashimoto
c9ca09af90 update build.zig.zon.nix 2025-03-23 14:04:59 -07:00
Mitchell Hashimoto
e30b5efd5d apprt/gtk: make GL context current in unrealize
Fixes #6872

This commit explicitly acquires the GL context in the `unrealize`
signal handler of the GTK Surface prior to cleaning up GPU resources.

A GLArea only guarantees that the associated GdkContext is current for
the `render` signal (see the docs[1]). This is why our OpenGL renderer
"defers" various operations such as resize, font grid changing, etc.
(see the `deferred_`-prefix fields in `renderer/OpenGL.zig`).
However, we missed a spot.

The `gtk-widget::unrealize` signal is emitted when the widget is
destroyed, and it is the last chance we have to clean up our GPU
resources. But it is not guaranteed that the GL context is current at
that point, and we weren't making it current. On the NGL GTK renderer,
this was freeing GPU resources we didn't own.

As best I can understand, the old GL renderer only ever used a handful
of GL resources that were early in the ID space, so by coincidence we
were probably freeing nothing and everything was fine. But with the new
NGL renderer uses a LOT more GL resources (make a few splits and the ID
space is already in the thousands, from GTK!), so we were freeing real
resources that we didn't own, which caused rendering issues. :)

I suspect the above also resulted in VRAM memory leaks (which would be
RAM memory leaks for unified memory GPUs). This potentially relates to

The fix is to explicitly make the GL context current in the `unrealize`
handler.

[1]: https://docs.gtk.org/gtk4/method.GLArea.make_current.html
2025-03-23 14:04:59 -07:00
Mitchell Hashimoto
67650c273a up versions for development 2025-03-23 13:47:56 -07:00
33 changed files with 594 additions and 102 deletions

2
.gitattributes vendored
View File

@@ -1,4 +1,6 @@
build.zig.zon.nix linguist-generated=true
build.zig.zon.txt linguist-generated=true
build.zig.zon2json-lock linguist-generated=true
vendor/** linguist-vendored
website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored

1
.gitignore vendored
View File

@@ -18,4 +18,3 @@ glad.zip
/Box_test.ppm
/Box_test_diff.ppm
/ghostty.qcow2
/build.zig.zon2json-lock

View File

@@ -23,13 +23,6 @@ https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz
https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz.minisig
```
> [!NOTE]
>
> **Version 1.0.0 the filename is `ghostty-source.tar.gz`.** Future
> versions will use the `ghostty-VERSION.tar.gz` format since it is more
> typical for source tarballs. But for version 1.0.0, the filename is
> `ghostty-source.tar.gz`.
Signature files are signed with
[minisign](https://jedisct1.github.io/minisign/)
using the following public key:

View File

@@ -1,6 +1,6 @@
.{
.name = "ghostty",
.version = "1.1.2",
.version = "1.1.3",
.paths = .{""},
.dependencies = .{
// Zig libs
@@ -14,11 +14,11 @@
.lazy = true,
},
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b",
.url = "https://github.com/rockorager/libvaxis/archive/6d729a2dc3b934818dffe06d2ba3ce02841ed74b.tar.gz",
.hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f",
},
.z2d = .{
.url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
.url = "https://github.com/vancluever/z2d/archive/4638bb02a9dc41cc2fb811f092811f6a951c752a.tar.gz",
.hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
},
.zig_objc = .{
@@ -38,7 +38,7 @@
.hash = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38",
},
.zf = .{
.url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd",
.url = "https://github.com/natecraddock/zf/archive/ed99ca18b02dda052e20ba467e90b623c04690dd.tar.gz",
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
},
.gobject = .{
@@ -76,7 +76,7 @@
.hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef",
},
.plasma_wayland_protocols = .{
.url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86",
.url = "https://github.com/KDE/plasma-wayland-protocols/archive/db525e8f9da548cffa2ac77618dd0fbe7f511b86.tar.gz",
.hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
},

22
build.zig.zon.nix generated
View File

@@ -143,8 +143,8 @@ in
name = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f";
path = fetchZigArtifact {
name = "vaxis";
url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b";
hash = "sha256-fFf79fCy4QQFVNcN722tSMjB6FyVEzCB36oH1olk9JQ=";
url = "https://github.com/rockorager/libvaxis/archive/6d729a2dc3b934818dffe06d2ba3ce02841ed74b.tar.gz";
hash = "sha256-OCNs6Gl2ruq5dBm4uIxs93hoXw/+n+x1+bIDfQGDx3s=";
};
}
{
@@ -167,8 +167,8 @@ in
name = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a";
path = fetchZigArtifact {
name = "z2d";
url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a";
hash = "sha256-YpWXn1J3JKQSCrWB25mAfzd1/T56QstEZnhPzBwxgoM=";
url = "https://github.com/vancluever/z2d/archive/4638bb02a9dc41cc2fb811f092811f6a951c752a.tar.gz";
hash = "sha256-P0UJ54RO/vVyDa+UkBl+QEOjzoMMEFSOTexQP/uBXfc=";
};
}
{
@@ -207,8 +207,8 @@ in
name = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8";
path = fetchZigArtifact {
name = "zf";
url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd";
hash = "sha256-t6QNrEJZ4GZZsYixjYvpdrYoCmNbG8TTUmGs2MFa4sU=";
url = "https://github.com/natecraddock/zf/archive/ed99ca18b02dda052e20ba467e90b623c04690dd.tar.gz";
hash = "sha256-/oLryY3VQfjbtQi+UP+n6FJTVA/YxIetjO+6Ovrh6/E=";
};
}
{
@@ -232,7 +232,7 @@ in
path = fetchZigArtifact {
name = "wayland";
url = "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz";
hash = "sha256-m9G72jdG30KH2yQhNBcBJ46OnekzuX0i2uIOyczkpLk=";
hash = "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0=";
};
}
{
@@ -247,8 +247,8 @@ in
name = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566";
path = fetchZigArtifact {
name = "plasma_wayland_protocols";
url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86";
hash = "sha256-iWRv3+OfmHxmeCJ8m0ChjgZX6mwXlcFmK8P/Vt8gDj4=";
url = "https://github.com/KDE/plasma-wayland-protocols/archive/db525e8f9da548cffa2ac77618dd0fbe7f511b86.tar.gz";
hash = "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE=";
};
}
{
@@ -367,8 +367,8 @@ in
name = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806";
path = fetchZigArtifact {
name = "pixels";
url = "git+https://github.com/make-github-pseudonymous-again/pixels?ref=main#d843c2714d32e15b48b8d7eeb480295af537f877";
hash = "sha256-kXYGO0qn2PfyOYCrRA49BHIgTzdmKhI8SNO1ZKfUYEE=";
url = "https://github.com/make-github-pseudonymous-again/pixels/archive/d843c2714d32e15b48b8d7eeb480295af537f877.tar.gz";
hash = "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro=";
};
}
{

38
build.zig.zon.txt generated Normal file
View File

@@ -0,0 +1,38 @@
git+https://github.com/rockorager/libvaxis/?ref=main#dc0a228a5544988d4a920cfb40be9cd28db41423
git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e
https://codeberg.org/atman/zg/archive/v0.13.2.tar.gz
https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz
https://github.com/KDE/plasma-wayland-protocols/archive/db525e8f9da548cffa2ac77618dd0fbe7f511b86.tar.gz
https://github.com/KhronosGroup/SPIRV-Cross/archive/476f384eb7d9e48613c45179e502a15ab95b6b49.tar.gz
https://github.com/KhronosGroup/glslang/archive/refs/tags/14.2.0.tar.gz
https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz
https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
https://github.com/getsentry/sentry-native/archive/refs/tags/0.7.8.tar.gz
https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.tar.gz
https://github.com/google/highway/archive/refs/tags/1.1.0.tar.gz
https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.9.tar.gz
https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.4.0.tar.gz
https://github.com/ianprime0509/zig-gobject/releases/download/v0.2.2/bindings-gnome47.tar.zst
https://github.com/kkos/oniguruma/archive/refs/tags/v6.9.9.tar.gz
https://github.com/madler/zlib/archive/refs/tags/v1.3.1.tar.gz
https://github.com/make-github-pseudonymous-again/pixels/archive/d843c2714d32e15b48b8d7eeb480295af537f877.tar.gz
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/db227d159adc265818f2e898da0f70ef8d7b580e.tar.gz
https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz
https://github.com/mitchellh/libxev/archive/31eed4e337fed7b0149319e5cdbb62b848c24fbd.tar.gz
https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz
https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz
https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz
https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz
https://github.com/mitchellh/xcode-frameworks/archive/69801c154c39d7ae6129ea1ba8fe1afe00585fc8.tar.gz
https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz
https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz
https://github.com/natecraddock/zf/archive/ed99ca18b02dda052e20ba467e90b623c04690dd.tar.gz
https://github.com/nemtrif/utfcpp/archive/refs/tags/v4.0.5.tar.gz
https://github.com/ocornut/imgui/archive/e391fe2e66eb1c96b1624ae8444dc64c23146ef4.tar.gz
https://github.com/rockorager/libvaxis/archive/6d729a2dc3b934818dffe06d2ba3ce02841ed74b.tar.gz
https://github.com/vancluever/z2d/archive/4638bb02a9dc41cc2fb811f092811f6a951c752a.tar.gz

192
build.zig.zon2json-lock generated Normal file
View File

@@ -0,0 +1,192 @@
{
"1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c": {
"name": "libxev",
"url": "https://github.com/mitchellh/libxev/archive/31eed4e337fed7b0149319e5cdbb62b848c24fbd.tar.gz",
"hash": "sha256-VHP90NTytIZ8UZsYRKOOxN490/I6yv6ec40sP8y5MJ8="
},
"12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62": {
"name": "mach_glfw",
"url": "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
"hash": "sha256-HhXIvWUS8/CHWY4VXPG2ZEo+we8XOn3o5rYJCQ1n8Nk="
},
"1220736fa4ba211162c7a0e46cc8fe04d95921927688bff64ab5da7420d098a7272d": {
"name": "glfw",
"url": "https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz",
"hash": "sha256-IeBVAOQmtyFqVxzuXPek1onuPwIamcOyYtxqKpPEQjU="
},
"12202adbfecdad671d585c9a5bfcbd5cdf821726779430047742ce1bf94ad67d19cb": {
"name": "xcode_frameworks",
"url": "https://github.com/mitchellh/xcode-frameworks/archive/69801c154c39d7ae6129ea1ba8fe1afe00585fc8.tar.gz",
"hash": "sha256-mP/I2coL57UJm/3+4Q8sPAgQwk8V4zM+S4VBBTrX2To="
},
"122004bfd4c519dadfb8e6281a42fc34fd1aa15aea654ea8a492839046f9894fa2cf": {
"name": "vulkan_headers",
"url": "https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz",
"hash": "sha256-K+zrRudgHFukOM6En1StRYRMNYkeRk+qHTXvrXaG+FU="
},
"1220b3164434d2ec9db146a40bf3a30f490590d68fa8529776a3138074f0da2c11ca": {
"name": "wayland_headers",
"url": "https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz",
"hash": "sha256-uFilLZinKkZt6RdVTV3lUmJpzpswDdFva22FvwU/XQI="
},
"122089c326186c84aa2fd034b16abc38f3ebf4862d9ae106dc1847ac44f557b36465": {
"name": "x11_headers",
"url": "https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz",
"hash": "sha256-EhV2bmTY/OMYN1wEul35gD0hQgS/Al262jO3pVr0O+c="
},
"12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f": {
"name": "vaxis",
"url": "https://github.com/rockorager/libvaxis/archive/6d729a2dc3b934818dffe06d2ba3ce02841ed74b.tar.gz",
"hash": "sha256-OCNs6Gl2ruq5dBm4uIxs93hoXw/+n+x1+bIDfQGDx3s="
},
"1220dd654ef941fc76fd96f9ec6adadf83f69b9887a0d3f4ee5ac0a1a3e11be35cf5": {
"name": "zigimg",
"url": "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e",
"hash": "sha256-oLf3YH3yeg4ikVO/GahMCDRMTU31AHkfSnF4rt7xTKo="
},
"122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40": {
"name": "zg",
"url": "https://codeberg.org/atman/zg/archive/v0.13.2.tar.gz",
"hash": "sha256-2x9hT7bYq9KJYWLVOf21a+QvTG/F7HWT+YK15IMRzNY="
},
"12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a": {
"name": "z2d",
"url": "https://github.com/vancluever/z2d/archive/4638bb02a9dc41cc2fb811f092811f6a951c752a.tar.gz",
"hash": "sha256-P0UJ54RO/vVyDa+UkBl+QEOjzoMMEFSOTexQP/uBXfc="
},
"1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634": {
"name": "zig_objc",
"url": "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz",
"hash": "sha256-H+HIbh2T23uzrsg9/1/vl9Ir1HCAa2pzeTx6zktJH9Q="
},
"12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc": {
"name": "zig_js",
"url": "https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz",
"hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0="
},
"12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25": {
"name": "ziglyph",
"url": "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz",
"hash": "sha256-cse98+Ft8QUjX+P88yyYfaxJOJGQ9M7Ymw7jFxDz89k="
},
"12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38": {
"name": "zig_wayland",
"url": "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
"hash": "sha256-RtAystqK/GRYIquTK1KfD7rRSCrfuzAvCD1Z9DE1ldc="
},
"1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8": {
"name": "zf",
"url": "https://github.com/natecraddock/zf/archive/ed99ca18b02dda052e20ba467e90b623c04690dd.tar.gz",
"hash": "sha256-/oLryY3VQfjbtQi+UP+n6FJTVA/YxIetjO+6Ovrh6/E="
},
"1220c72c1697dd9008461ead702997a15d8a1c5810247f02e7983b9f74c6c6e4c087": {
"name": "vaxis",
"url": "git+https://github.com/rockorager/libvaxis/?ref=main#dc0a228a5544988d4a920cfb40be9cd28db41423",
"hash": "sha256-QWN4jOrA91KlbqmeEHHJ4HTnCC9nmfxt8DHUXJpAzLI="
},
"12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d": {
"name": "gobject",
"url": "https://github.com/ianprime0509/zig-gobject/releases/download/v0.2.2/bindings-gnome47.tar.zst",
"hash": "sha256-UU97kNv/bZzQPKz1djhEDLapLguvfBpFfWVb6FthtcI="
},
"12202cdac858abc52413a6c6711d5026d2d3c8e13f95ca2c327eade0736298bb021f": {
"name": "wayland",
"url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz",
"hash": "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0="
},
"12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef": {
"name": "wayland_protocols",
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
},
"12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566": {
"name": "plasma_wayland_protocols",
"url": "https://github.com/KDE/plasma-wayland-protocols/archive/db525e8f9da548cffa2ac77618dd0fbe7f511b86.tar.gz",
"hash": "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE="
},
"12203d2647e5daf36a9c85b969e03f422540786ce9ea624eb4c26d204fe1f46218f3": {
"name": "iterm2_themes",
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/db227d159adc265818f2e898da0f70ef8d7b580e.tar.gz",
"hash": "sha256-Iyf7U4rpvNkPX4AOEbYSYGte5+SjRwsWD2luOn1Hz8U="
},
"1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402": {
"name": "imgui",
"url": "https://github.com/ocornut/imgui/archive/e391fe2e66eb1c96b1624ae8444dc64c23146ef4.tar.gz",
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
},
"1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d": {
"name": "freetype",
"url": "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz",
"hash": "sha256-QnIB9dUVFnDQXB9bRb713aHy592XHvVPD+qqf/0quQw="
},
"1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66": {
"name": "libpng",
"url": "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.tar.gz",
"hash": "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo="
},
"1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb": {
"name": "zlib",
"url": "https://github.com/madler/zlib/archive/refs/tags/v1.3.1.tar.gz",
"hash": "sha256-F+iIY/NgBnKrSRgvIXKBtvxNPHYr3jYZNeQ2qVIU0Fw="
},
"12201149afb3326c56c05bb0a577f54f76ac20deece63aa2f5cd6ff31a4fa4fcb3b7": {
"name": "fontconfig",
"url": "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz",
"hash": "sha256-O6LdkhWHGKzsXKrxpxYEO1qgVcJ7CB2RSvPMtA3OilU="
},
"122032442d95c3b428ae8e526017fad881e7dc78eab4d558e9a58a80bfbd65a64f7d": {
"name": "libxml2",
"url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz",
"hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU="
},
"1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122": {
"name": "harfbuzz",
"url": "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.4.0.tar.gz",
"hash": "sha256-nxygiYE7BZRK0c6MfgGCEwJtNdybq0gKIeuHaDg5ZVY="
},
"12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b": {
"name": "highway",
"url": "https://github.com/google/highway/archive/refs/tags/1.1.0.tar.gz",
"hash": "sha256-NUqLRTm1iOcLmOxwhEJz4/J0EwLEw3e8xOgbPRhm98k="
},
"1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb": {
"name": "oniguruma",
"url": "https://github.com/kkos/oniguruma/archive/refs/tags/v6.9.9.tar.gz",
"hash": "sha256-ABqhIC54RI9MC/GkjHblVodrNvFtks4yB+zP1h2Z8qA="
},
"1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e": {
"name": "sentry",
"url": "https://github.com/getsentry/sentry-native/archive/refs/tags/0.7.8.tar.gz",
"hash": "sha256-KsZJfMjWGo0xCT5HrduMmyxFsWsHBbszSoNbZCPDGN8="
},
"12207fd37bb8251919c112dcdd8f616a491857b34a451f7e4486490077206dc2a1ea": {
"name": "breakpad",
"url": "https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
"hash": "sha256-bMqYlD0amQdmzvYQd8Ca/1k4Bj/heh7+EijlQSttatk="
},
"1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641": {
"name": "utfcpp",
"url": "https://github.com/nemtrif/utfcpp/archive/refs/tags/v4.0.5.tar.gz",
"hash": "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8="
},
"122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd": {
"name": "wuffs",
"url": "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.9.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
"12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806": {
"name": "pixels",
"url": "https://github.com/make-github-pseudonymous-again/pixels/archive/d843c2714d32e15b48b8d7eeb480295af537f877.tar.gz",
"hash": "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro="
},
"12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1": {
"name": "glslang",
"url": "https://github.com/KhronosGroup/glslang/archive/refs/tags/14.2.0.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
"1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da": {
"name": "spirv_cross",
"url": "https://github.com/KhronosGroup/SPIRV-Cross/archive/476f384eb7d9e48613c45179e502a15ab95b6b49.tar.gz",
"hash": "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M="
}
}

View File

@@ -459,6 +459,11 @@ class QuickTerminalController: BaseTerminalController {
ghostty.toggleFullscreen(surface: surface)
}
@IBAction func toggleTerminalInspector(_ sender: Any?) {
guard let surface = focusedSurface?.surface else { return }
ghostty.toggleTerminalInspector(surface: surface)
}
// MARK: Notifications
@objc private func applicationWillTerminate(_ notification: Notification) {

View File

@@ -76,6 +76,12 @@ class TerminalController: BaseTerminalController {
selector: #selector(onFrameDidChange),
name: NSView.frameDidChangeNotification,
object: nil)
center.addObserver(
self,
selector: #selector(onEqualizeSplits),
name: Ghostty.Notification.didEqualizeSplits,
object: nil
)
}
required init?(coder: NSCoder) {
@@ -212,6 +218,9 @@ class TerminalController: BaseTerminalController {
// Set our explicit appearance if we need to based on the configuration.
window.appearance = surfaceConfig.windowAppearance
// Update our window light/darkness based on our updated background color
window.isLightTheme = OSColor(surfaceConfig.backgroundColor).isLightColor
// If our window is not visible, then we do nothing. Some things such as blurring
// have no effect if the window is not visible. Ultimately, we'll have this called
// at some point when a surface becomes focused.
@@ -801,6 +810,17 @@ class TerminalController: BaseTerminalController {
toggleFullscreen(mode: fullscreenMode)
}
@objc private func onEqualizeSplits(_ notification: Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
// Check if target surface is in current controller's tree
guard surfaceTree?.contains(view: target) ?? false else { return }
if case .split(let container) = surfaceTree {
_ = container.equalize()
}
}
struct DerivedConfig {
let backgroundColor: Color
let macosTitlebarStyle: String

View File

@@ -95,11 +95,8 @@ class TerminalManager {
}
}
// If our app isn't active, we make it active. All new_window actions
// force our app to be active.
if !NSApp.isActive {
// All new_window actions force our app to be active.
NSApp.activate(ignoringOtherApps: true)
}
// We're dispatching this async because otherwise the lastCascadePoint doesn't
// take effect. Our best theory is there is some next-event-loop-tick logic

View File

@@ -3,6 +3,10 @@ import Cocoa
class TerminalWindow: NSWindow {
@objc dynamic var keyEquivalent: String = ""
/// This is used to determine if certain elements should be drawn light or dark and should
/// be updated whenever the window background color or surrounding elements changes.
var isLightTheme: Bool = false
lazy var titlebarColor: NSColor = backgroundColor {
didSet {
guard let titlebarContainer else { return }
@@ -295,7 +299,6 @@ class TerminalWindow: NSWindow {
if newTabButtonImageLayer == nil {
let isLightTheme = backgroundColor.isLightColor
let fillColor: NSColor = isLightTheme ? .black.withAlphaComponent(0.85) : .white.withAlphaComponent(0.85)
let newImage = NSImage(size: newTabButtonImage.size, flipped: false) { rect in
newTabButtonImage.draw(in: rect)
@@ -714,7 +717,7 @@ fileprivate class WindowButtonsBackdropView: NSView {
init(window: TerminalWindow) {
self.terminalWindow = window
self.isLightTheme = window.backgroundColor.isLightColor
self.isLightTheme = window.isLightTheme
super.init(frame: .zero)

View File

@@ -50,7 +50,6 @@ extension Ghostty {
var body: some View {
let center = NotificationCenter.default
let pubZoom = center.publisher(for: Notification.didToggleSplitZoom)
let pubEqualize = center.publisher(for: Notification.didEqualizeSplits)
// If we're zoomed, we don't render anything, we are transparent. This
// ensures that the View stays around so we don't lose our state, but
@@ -76,7 +75,6 @@ extension Ghostty {
container: container
)
.onReceive(pubZoom) { onZoom(notification: $0) }
.onReceive(pubEqualize) { onEqualize(notification: $0) }
}
}
.navigationTitle(surfaceTitle ?? "Ghostty")
@@ -137,11 +135,6 @@ extension Ghostty {
}
}
}
func onEqualize(notification: SwiftUI.Notification) {
guard case .split(let c) = node else { return }
_ = c.equalize()
}
}
/// A noSplit leaf node of a split tree.

View File

@@ -1,4 +1,16 @@
#!/usr/bin/env bash
#
# This script checks if the build.zig.zon.nix file is up-to-date.
# If the `--update` flag is passed, it will update all necessary
# files to be up to date.
#
# The files owned by this are:
#
# - build.zig.zon.nix
# - build.zig.zon.txt
# - build.zig.zon2json-lock
#
# All of these are auto-generated and should not be edited manually.
# Nothing in this script should fail.
set -e
@@ -27,9 +39,11 @@ help() {
echo ""
}
BUILD_ZIG_ZON="$(realpath "$(dirname "$0")/../../build.zig.zon")"
BUILD_ZIG_ZON_LOCK="$(realpath "$(dirname "$0")/../../build.zig.zon2json-lock")"
BUILD_ZIG_ZON_NIX="$(realpath "$(dirname "$0")/../../build.zig.zon.nix")"
ROOT="$(realpath "$(dirname "$0")/../../")"
BUILD_ZIG_ZON="$ROOT/build.zig.zon"
BUILD_ZIG_ZON_LOCK="$ROOT/build.zig.zon2json-lock"
BUILD_ZIG_ZON_NIX="$ROOT/build.zig.zon.nix"
BUILD_ZIG_ZON_TXT="$ROOT/build.zig.zon.txt"
if [ -f "${BUILD_ZIG_ZON_NIX}" ]; then
OLD_HASH=$(sha512sum "${BUILD_ZIG_ZON_NIX}" | awk '{print $1}')
@@ -42,7 +56,6 @@ fi
rm -f "$BUILD_ZIG_ZON_LOCK"
zon2nix "$BUILD_ZIG_ZON" > "$WORK_DIR/build.zig.zon.nix"
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
rm -f "$BUILD_ZIG_ZON_LOCK"
NEW_HASH=$(sha512sum "$WORK_DIR/build.zig.zon.nix" | awk '{print $1}')
@@ -57,6 +70,7 @@ elif [ "$1" != "--update" ]; then
help
exit 1
else
jq -r '.[] .url' "$BUILD_ZIG_ZON_LOCK" | sort > "$BUILD_ZIG_ZON_TXT"
mv "$WORK_DIR/build.zig.zon.nix" "$BUILD_ZIG_ZON_NIX"
echo -e "\nOK: build.zig.zon.nix updated."
exit 0

View File

@@ -0,0 +1,27 @@
#!/bin/sh
# NOTE THIS IS A TEMPORARY SCRIPT TO SUPPORT PACKAGE MAINTAINERS.
#
# A future Zig version will hopefully fix the issue where
# `zig build --fetch` doesn't fetch transitive dependencies[1]. When that
# is resolved, we won't need any special machinery for the general use case
# at all and packagers can just use `zig build --fetch`.
#
# [1]: https://github.com/ziglang/zig/issues/20976
if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ]
then
echo "must set ZIG_GLOBAL_CACHE_DIR!"
exit 1
fi
# Go through each line of our build.zig.zon.txt and fetch it.
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
ZON_TXT_FILE="$SCRIPT_PATH/../../build.zig.zon.txt"
while IFS= read -r url; do
echo "Fetching: $url"
zig fetch "$url" >/dev/null 2>&1 || {
echo "Failed to fetch: $url" >&2
exit 1
}
done < "$ZON_TXT_FILE"

View File

@@ -49,6 +49,7 @@
simdutf,
zlib,
alejandra,
jq,
minisign,
pandoc,
hyperfine,
@@ -97,6 +98,7 @@ in
packages =
[
# For builds
jq
llvmPackages_latest.llvm
minisign
ncurses

View File

@@ -48,7 +48,7 @@
in
stdenv.mkDerivation (finalAttrs: {
pname = "ghostty";
version = "1.1.2";
version = "1.1.3";
# We limit source like this to try and reduce the amount of rebuilds as possible
# thus we only provide the source that is needed for the build

View File

@@ -8,7 +8,7 @@
},
.pixels = .{
.url = "git+https://github.com/make-github-pseudonymous-again/pixels?ref=main#d843c2714d32e15b48b8d7eeb480295af537f877",
.url = "https://github.com/make-github-pseudonymous-again/pixels/archive/d843c2714d32e15b48b8d7eeb480295af537f877.tar.gz",
.hash = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806",
},

View File

@@ -1693,7 +1693,7 @@ pub fn keyCallback(
self: *Surface,
event: input.KeyEvent,
) !InputEffect {
// log.debug("text keyCallback event={}", .{event});
// log.warn("text keyCallback event={}", .{event});
// Crash metadata in case we crash in here
crash.sentry.thread_state = self.crashThreadState();

View File

@@ -149,8 +149,17 @@ pub const App = struct {
}
/// Convert a C key event into a Zig key event.
///
/// The buffer is needed for possibly storing translated UTF-8 text.
/// This buffer may (or may not) be referenced by the resulting KeyEvent
/// so it should be valid for the lifetime of the KeyEvent.
///
/// The size of the buffer doesn't need to be large, we always
/// used to hardcode 128 bytes and never ran into issues. If it isn't
/// large enough an error will be returned.
fn coreKeyEvent(
self: *App,
buf: []u8,
target: KeyTarget,
event: KeyEvent,
) !?input.KeyEvent {
@@ -217,7 +226,6 @@ pub const App = struct {
// Translate our key using the keymap for our localized keyboard layout.
// We only translate for keydown events. Otherwise, we only care about
// the raw keycode.
var buf: [128]u8 = undefined;
const result: input.Keymap.Translation = if (is_down) translate: {
// If the event provided us with text, then we use this as a result
// and do not do manual translation.
@@ -226,7 +234,7 @@ pub const App = struct {
.composing = event.composing,
.mods = translate_mods,
} else try self.keymap.translate(
&buf,
buf,
switch (target) {
.app => &self.keymap_state,
.surface => |surface| &surface.keymap_state,
@@ -360,7 +368,9 @@ pub const App = struct {
event: KeyEvent,
) !bool {
// Convert our C key event into a Zig one.
var buf: [128]u8 = undefined;
const input_event: input.KeyEvent = (try self.coreKeyEvent(
&buf,
target,
event,
)) orelse return false;
@@ -1425,7 +1435,9 @@ pub const CAPI = struct {
app: *App,
event: KeyEvent,
) bool {
var buf: [128]u8 = undefined;
const core_event = app.coreKeyEvent(
&buf,
.app,
event.keyEvent(),
) catch |err| {
@@ -1677,7 +1689,9 @@ pub const CAPI = struct {
surface: *Surface,
event: KeyEvent,
) bool {
var buf: [128]u8 = undefined;
const core_event = surface.app.coreKeyEvent(
&buf,
// Note: this "app" target here looks like a bug, but it is
// intentional. coreKeyEvent uses the target only as a way to
// trigger preedit callbacks for keymap translation and we don't

View File

@@ -223,19 +223,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
_ = internal_os.setenv("GDK_DISABLE", value[0 .. value.len - 1 :0]);
}
if (version.runtimeAtLeast(4, 14, 0)) {
switch (config.@"gtk-gsk-renderer") {
.default => {},
else => |renderer| {
// Force the GSK renderer to a specific value. After GTK 4.14 the
// `ngl` renderer is used by default which causes artifacts when
// used with Ghostty so it should be avoided.
log.warn("setting GSK_RENDERER={s}", .{@tagName(renderer)});
_ = internal_os.setenv("GSK_RENDERER", @tagName(renderer));
},
}
}
c.gtk_init();
const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
// I'm unsure of any scenario where this happens. Because we don't

View File

@@ -817,16 +817,23 @@ pub fn shouldClose(self: *const Surface) bool {
}
pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
const gtk_scale: f32 = scale: {
// Future: detect GTK version 4.12+ and use gdk_surface_get_scale so we
// can support fractional scaling.
const gtk_scale: f32 = @floatFromInt(c.gtk_widget_get_scale_factor(@ptrCast(self.gl_area)));
const scale = c.gtk_widget_get_scale_factor(@ptrCast(self.gl_area));
if (scale <= 0) {
log.warn("gtk_widget_get_scale_factor returned a non-positive number: {d}", .{scale});
break :scale 1.0;
}
break :scale @floatFromInt(scale);
};
// Also scale using font-specific DPI, which is often exposed to the user
// via DE accessibility settings (see https://docs.gtk.org/gtk4/class.Settings.html).
const xft_dpi_scale = xft_scale: {
// gtk-xft-dpi is font DPI multiplied by 1024. See
// https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html
const settings = c.gtk_settings_get_default();
const settings = c.gtk_settings_get_default() orelse break :xft_scale 1.0;
var value: c.GValue = std.mem.zeroes(c.GValue);
defer c.g_value_unset(&value);
@@ -834,11 +841,21 @@ pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
c.g_object_get_property(@ptrCast(@alignCast(settings)), "gtk-xft-dpi", &value);
const gtk_xft_dpi = c.g_value_get_int(&value);
// Use a value of 1.0 for the XFT DPI scale if the setting is <= 0
// See:
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/a7738a4d269bfdf4d8d5429ca73ccdd9b2450421
// https://gitlab.gnome.org/GNOME/libadwaita/-/commit/9759d3fd81129608dd78116001928f2aed974ead
// ensure that the scale is never negative
if (gtk_xft_dpi <= 0) {
log.warn("gtk-xft-dpi was not set, using default value", .{});
break :xft_scale 1.0;
}
// As noted above gtk-xft-dpi is multiplied by 1024, so we divide by
// 1024, then divide by the default value (96) to derive a scale. Note
// gtk-xft-dpi can be fractional, so we use floating point math here.
const xft_dpi: f32 = @as(f32, @floatFromInt(gtk_xft_dpi)) / 1024;
break :xft_scale xft_dpi / 96;
const xft_dpi: f32 = @as(f32, @floatFromInt(gtk_xft_dpi)) / 1024.0;
break :xft_scale xft_dpi / 96.0;
};
const scale = gtk_scale * xft_dpi_scale;
@@ -1325,14 +1342,35 @@ fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
/// This is called when the underlying OpenGL resources must be released.
/// This is usually due to the OpenGL area changing GDK surfaces.
fn gtkUnrealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
_ = area;
log.debug("gl surface unrealized", .{});
const self = userdataSelf(ud.?);
self.core_surface.renderer.displayUnrealized();
log.debug("gl surface unrealized", .{});
// See gtkRealize for why we do this here.
c.gtk_im_context_set_client_widget(self.im_context, null);
// There is no guarantee that our GLArea context is current
// when unrealize is emitted, so we need to make it current.
c.gtk_gl_area_make_current(area);
if (c.gtk_gl_area_get_error(area)) |err| {
// I don't know a scenario this can happen, but it means
// we probably leaked memory because displayUnrealized
// below frees resources that aren't specifically OpenGL
// related. I didn't make the OpenGL renderer handle this
// scenario because I don't know if its even possible
// under valid circumstances, so let's log.
const message: []const u8 = if (err.*.message) |v|
std.mem.span(v)
else
"(no message)";
log.warn(
"gl_area_make_current failed in unrealize msg={s}",
.{message},
);
log.warn("OpenGL resources and memory likely leaked", .{});
return;
} else {
self.core_surface.renderer.displayUnrealized();
}
}
/// render signal
@@ -1914,6 +1952,14 @@ fn gtkInputPreeditChanged(
) callconv(.C) void {
const self = userdataSelf(ud.?);
// Any preedit change should mark that we're composing. Its possible this
// is false using fcitx5-hangul and typing "dkssud<space>" ("안녕"). The
// second "s" results in a "commit" for "안" which sets composing to false,
// but then immediately sends a preedit change for the next symbol. With
// composing set to false we won't commit this text. Therefore, we must
// ensure it is set here.
self.im_composing = true;
// Get our pre-edit string that we'll use to show the user.
var buf: [*c]u8 = undefined;
_ = c.gtk_im_context_get_preedit_string(ctx, &buf, null, null);

View File

@@ -596,6 +596,10 @@ pub fn focusCurrentTab(self: *Window) void {
const surface = tab.focus_child orelse return;
const gl_area = @as(*c.GtkWidget, @ptrCast(surface.gl_area));
_ = c.gtk_widget_grab_focus(gl_area);
if (surface.getTitle()) |title| {
self.setTitle(title);
}
}
pub fn onConfigReloaded(self: *Window) void {

View File

@@ -48,4 +48,18 @@ window.ssd.no-border-radius {
.terminal-window .notebook separator {
background-color: rgba(250, 250, 250, 1);
background-clip: content-box;
/* This works around the oversized drag area for the right side of GtkPaned.
*
* Upstream Gtk issue:
* https://gitlab.gnome.org/GNOME/gtk/-/issues/4484#note_2362002
*
* Ghostty issue:
* https://github.com/ghostty-org/ghostty/issues/3020
*
* Without this, it's not possible to select the first character on the
* right-hand side of a split.
*/
margin: 0;
padding: 0;
}

View File

@@ -19,7 +19,7 @@ const GitVersion = @import("GitVersion.zig");
/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly.
/// Until then this MUST match build.zig.zon and should always be the
/// _next_ version to release.
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 2 };
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 3 };
/// Standard build configuration options.
optimize: std.builtin.OptimizeMode,
@@ -70,8 +70,10 @@ pub fn init(b: *std.Build) !Config {
// If we're building for macOS and we're on macOS, we need to
// use a generic target to workaround compilation issues.
if (result.result.os.tag == .macos and builtin.target.isDarwin()) {
result = genericMacOSTarget(b, null);
if (result.result.os.tag == .macos and
builtin.target.os.tag.isDarwin())
{
result = genericMacOSTarget(b, result.query.cpu_arch);
}
// If we have no minimum OS version, we set the default based on

View File

@@ -2089,13 +2089,11 @@ keybind: Keybinds = .{},
/// debug builds, `false` for all others.
@"gtk-opengl-debug": bool = builtin.mode == .Debug,
/// After GTK 4.14.0, we need to force the GSK renderer to OpenGL as the default
/// GSK renderer is broken on some systems. If you would like to override
/// that bekavior, set `gtk-gsk-renderer=default` and either use your system's
/// default GSK renderer, or set the GSK_RENDERER environment variable to your
/// renderer of choice before launching Ghostty. This setting has no effect when
/// using versions of GTK earlier than 4.14.0.
@"gtk-gsk-renderer": GtkGskRenderer = .opengl,
/// Obsolete configuration that should not be set. This was deprecated in
/// Ghostty 1.1.3 and no longer has any effect. The configuration key will
/// be fully removed in 1.2.0. You can manually override the GSK renderer
/// using standard environment variables such as `GSK_RENDERER` (from GTK).
@"gtk-gsk-renderer": GtkGskRenderer = .default,
/// If `true`, the Ghostty GTK application will run in single-instance mode:
/// each new `ghostty` process launched will result in a new window if there is
@@ -2580,7 +2578,7 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
);
try result.keybind.set.put(
alloc,
.{ .key = .{ .translated = .equal }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .key = .{ .translated = .plus }, .mods = .{ .super = true, .ctrl = true, .shift = true } },
.{ .equalize_splits = {} },
);

View File

@@ -1019,6 +1019,10 @@ pub fn setFontGrid(self: *Metal, grid: *font.SharedGrid) void {
// out a better way to handle this.
log.err("error resizing cells buffer err={}", .{err});
};
// Reset our viewport to force a rebuild, since `setScreenSize` only
// does this when the number of cells changes, which isn't guaranteed.
self.cells_viewport = null;
}
/// Update the frame data.

View File

@@ -47,7 +47,19 @@ made available for use as modules by way of `use <filename>`.
Ghostty launches Elvish, passing the environment with `XDG_DATA_DIRS`prepended
with `$GHOSTTY_RESOURCES_DIR/src/shell-integration`. It contains
`./elvish/lib/ghostty-integration.elv`. The user can then import it
by `use ghostty-integration`, which will run the integration routines.
by `use ghostty-integration` every time after shell startup or
autostart integration in `$XDG_CONFIG_HOME/elvish/rc.elv`,
which will run the integration routines.
If you decide to autostart `ghostty-integration` with `rc.elv`, you should
detect whether the terminal is Ghostty or not. To do this, add this to the end
of your `rc.elv` file:
```elvish
if (eq $E:TERM "xterm-ghostty") {
use ghostty-integration
}
```
The [Elvish](https://elv.sh) shell integration is supported by
the community and is not officially supported by Ghostty. We distribute

View File

@@ -92,7 +92,7 @@
}
if (not $sudoedit) { set args = [ TERMINFO=$E:TERMINFO $@args ] }
command sudo $@args
(external sudo) $@args
}
defer {

View File

@@ -86,7 +86,9 @@ pub const Action = union(enum) {
final: u8,
/// The list of separators used for CSI params. The value of the
/// bit can be mapped to Sep.
/// bit can be mapped to Sep. The index of this bit set specifies
/// the separator AFTER that param. For example: 0;4:3 would have
/// index 1 set.
pub const SepList = std.StaticBitSet(MAX_PARAMS);
/// The separator used for CSI params.
@@ -192,7 +194,19 @@ pub const Action = union(enum) {
/// 4 because we also use the intermediates array for UTF8 decoding which
/// can be at most 4 bytes.
const MAX_INTERMEDIATE = 4;
const MAX_PARAMS = 16;
/// Maximum number of CSI parameters. This is arbitrary. Practically, the
/// only CSI command that uses more than 3 parameters is the SGR command
/// which can be infinitely long. 24 is a reasonable limit based on empirical
/// data. This used to be 16 but Kakoune has a SGR command that uses 17
/// parameters.
///
/// We could in the future make this the static limit and then allocate after
/// but that's a lot more work and practically its so rare to exceed this
/// number. I implore TUI authors to not use more than this number of CSI
/// params, but I suspect we'll introduce a slow path with heap allocation
/// one day.
const MAX_PARAMS = 24;
/// Current state of the state machine
state: State = .ground,
@@ -689,6 +703,64 @@ test "csi: SGR mixed colon and semicolon with blank" {
}
}
// This is from a Kakoune actual SGR sequence also.
test "csi: SGR mixed colon and semicolon setting underline, bg, fg" {
var p = init();
_ = p.next(0x1B);
for ("[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136") |c| {
const a = p.next(c);
try testing.expect(a[0] == null);
try testing.expect(a[1] == null);
try testing.expect(a[2] == null);
}
{
const a = p.next('m');
try testing.expect(p.state == .ground);
try testing.expect(a[0] == null);
try testing.expect(a[1].? == .csi_dispatch);
try testing.expect(a[2] == null);
const d = a[1].?.csi_dispatch;
try testing.expect(d.final == 'm');
try testing.expectEqual(17, d.params.len);
try testing.expectEqual(@as(u16, 4), d.params[0]);
try testing.expect(d.params_sep.isSet(0));
try testing.expectEqual(@as(u16, 3), d.params[1]);
try testing.expect(!d.params_sep.isSet(1));
try testing.expectEqual(@as(u16, 38), d.params[2]);
try testing.expect(!d.params_sep.isSet(2));
try testing.expectEqual(@as(u16, 2), d.params[3]);
try testing.expect(!d.params_sep.isSet(3));
try testing.expectEqual(@as(u16, 51), d.params[4]);
try testing.expect(!d.params_sep.isSet(4));
try testing.expectEqual(@as(u16, 51), d.params[5]);
try testing.expect(!d.params_sep.isSet(5));
try testing.expectEqual(@as(u16, 51), d.params[6]);
try testing.expect(!d.params_sep.isSet(6));
try testing.expectEqual(@as(u16, 48), d.params[7]);
try testing.expect(!d.params_sep.isSet(7));
try testing.expectEqual(@as(u16, 2), d.params[8]);
try testing.expect(!d.params_sep.isSet(8));
try testing.expectEqual(@as(u16, 170), d.params[9]);
try testing.expect(!d.params_sep.isSet(9));
try testing.expectEqual(@as(u16, 170), d.params[10]);
try testing.expect(!d.params_sep.isSet(10));
try testing.expectEqual(@as(u16, 170), d.params[11]);
try testing.expect(!d.params_sep.isSet(11));
try testing.expectEqual(@as(u16, 58), d.params[12]);
try testing.expect(!d.params_sep.isSet(12));
try testing.expectEqual(@as(u16, 2), d.params[13]);
try testing.expect(!d.params_sep.isSet(13));
try testing.expectEqual(@as(u16, 255), d.params[14]);
try testing.expect(!d.params_sep.isSet(14));
try testing.expectEqual(@as(u16, 97), d.params[15]);
try testing.expect(!d.params_sep.isSet(15));
try testing.expectEqual(@as(u16, 136), d.params[16]);
try testing.expect(!d.params_sep.isSet(16));
}
}
test "csi: colon for non-m final" {
var p = init();
_ = p.next(0x1B);

View File

@@ -103,14 +103,18 @@ pub const Parser = struct {
/// Next returns the next attribute or null if there are no more attributes.
pub fn next(self: *Parser) ?Attribute {
if (self.idx > self.params.len) return null;
// Implicitly means unset
if (self.params.len == 0) {
if (self.idx >= self.params.len) {
// If we're at index zero it means we must have an empty
// list and an empty list implicitly means unset.
if (self.idx == 0) {
// Add one to ensure we don't loop on unset
self.idx += 1;
return .unset;
}
return null;
}
const slice = self.params[self.idx..self.params.len];
const colon = self.params_sep.isSet(self.idx);
self.idx += 1;
@@ -788,7 +792,6 @@ test "sgr: direct fg colon with colorspace and extra param" {
{
const v = p.next().?;
std.log.warn("WHAT={}", .{v});
try testing.expect(v == .direct_color_fg);
try testing.expectEqual(@as(u8, 1), v.direct_color_fg.r);
try testing.expectEqual(@as(u8, 2), v.direct_color_fg.g);
@@ -864,3 +867,50 @@ test "sgr: kakoune input" {
//try testing.expect(p.next() == null);
}
// Discussion #5930, another input sent by kakoune
test "sgr: kakoune input issue underline, fg, and bg" {
// echo -e "\033[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136mset everything in one sequence, broken\033[m"
// This used to crash
var p: Parser = .{
.params = &[_]u16{ 4, 3, 38, 2, 51, 51, 51, 48, 2, 170, 170, 170, 58, 2, 255, 97, 136 },
.params_sep = sep: {
var list = SepList.initEmpty();
list.set(0);
break :sep list;
},
};
{
const v = p.next().?;
try testing.expect(v == .underline);
try testing.expectEqual(Attribute.Underline.curly, v.underline);
}
{
const v = p.next().?;
try testing.expect(v == .direct_color_fg);
try testing.expectEqual(@as(u8, 51), v.direct_color_fg.r);
try testing.expectEqual(@as(u8, 51), v.direct_color_fg.g);
try testing.expectEqual(@as(u8, 51), v.direct_color_fg.b);
}
{
const v = p.next().?;
try testing.expect(v == .direct_color_bg);
try testing.expectEqual(@as(u8, 170), v.direct_color_bg.r);
try testing.expectEqual(@as(u8, 170), v.direct_color_bg.g);
try testing.expectEqual(@as(u8, 170), v.direct_color_bg.b);
}
{
const v = p.next().?;
try testing.expect(v == .underline_color);
try testing.expectEqual(@as(u8, 255), v.underline_color.r);
try testing.expectEqual(@as(u8, 97), v.underline_color.g);
try testing.expectEqual(@as(u8, 136), v.underline_color.b);
}
try testing.expect(p.next() == null);
}

View File

@@ -932,7 +932,7 @@ pub fn Stream(comptime Handler: type) type {
// SGR - Select Graphic Rendition
'm' => switch (input.intermediates.len) {
0 => if (@hasDecl(T, "setAttribute")) {
// log.info("parse SGR params={any}", .{action.params});
// log.info("parse SGR params={any}", .{input.params});
var p: sgr.Parser = .{
.params = input.params,
.params_sep = input.params_sep,

View File

@@ -220,7 +220,7 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
.renderer_mailbox = opts.renderer_mailbox,
.surface_mailbox = opts.surface_mailbox,
.size = opts.size,
.backend = opts.backend,
.backend = backend,
.mailbox = opts.mailbox,
.terminal_stream = .{
.handler = handler,

View File

@@ -1418,11 +1418,13 @@ pub const StreamHandler = struct {
var buf = std.ArrayList(u8).init(self.alloc);
defer buf.deinit();
const writer = buf.writer();
try writer.writeAll("\x1b]21");
for (request.list.items) |item| {
switch (item) {
.query => |key| {
// If the writer buffer is empty, we need to write our prefix
if (buf.items.len == 0) try writer.writeAll("\x1b]21");
const color: terminal.color.RGB = switch (key) {
.palette => |palette| self.terminal.color_palette.colors[palette],
.special => |special| switch (special) {
@@ -1517,14 +1519,16 @@ pub const StreamHandler = struct {
}
}
// If we had any writes to our buffer, we queue them now
if (buf.items.len > 0) {
try writer.writeAll(request.terminator.string());
self.messageWriter(.{
.write_alloc = .{
.alloc = self.alloc,
.data = try buf.toOwnedSlice(),
},
});
}
// Note: we don't have to do a queueRender here because every
// processed stream will queue a render once it is done processing