581 Commits

Author SHA1 Message Date
Mitchell Hashimoto
f9b7ba36d9 wip: tests
Not meant for merging.
2025-03-18 09:28:45 -07:00
Jeffrey C. Ollie
0f2f0ab69f Update Adwaita version check for Box unref (#6796)
As of Adwaita 1.5.0, the GTK Box is not being properly unref'd when the
parent window is closed. Update the conditional to account for this.

Also add a couple of missing unref()s in errdefers.

This fixes an issue where Ghostty would not properly quit after closing
the last surface.
https://github.com/ghostty-org/ghostty/discussions/3807 is related
(though I'm not sure it's the exact same problem).
2025-03-18 10:42:00 -05:00
Gregory Anders
946c0c370f Update Adwaita version check for Box unref
As of Adwaita 1.5.0, the GTK Box is not being properly unref'd when the
parent window is closed. Update the conditional to account for this.

Also add a couple of missing unref()s in errdefers.
2025-03-18 10:24:42 -05:00
Jeffrey C. Ollie
ee78a3d345 gtk: remove c.zig (#6792)
It has been done.
2025-03-18 09:59:23 -05:00
Leah Amelia Chen
72017ea4d8 translations(zh_CN): update 2025-03-18 12:35:41 +01:00
Leah Amelia Chen
8c0ccfc5b3 translations: update 2025-03-18 12:35:41 +01:00
Leah Amelia Chen
a773588c99 gtk: remove c.zig
It has been done.
2025-03-18 12:35:41 +01:00
Leah Amelia Chen
73341b052b gtk: port ConfigErrorsWindow to dialogs 2025-03-18 12:35:41 +01:00
Leah Amelia Chen
1ee9c85954 gtk: port inspector & key handling to zig-gobject 2025-03-18 12:35:41 +01:00
Leah Amelia Chen
e3fbbe8fe3 ci(test/translations): ignore untranslated entries 2025-03-18 12:32:55 +01:00
Leah Amelia Chen
d75c5ec038 gtk: convert App to zig-gobject (#6787) 2025-03-18 09:14:19 +01:00
Jeffrey C. Ollie
ee95a5f3e0 gtk: convert App to zig-gobject 2025-03-17 23:39:45 -05:00
Leah Amelia Chen
742bca713d gtk: convert Window (and some related files) to zig-gobject (#6775) 2025-03-17 21:19:17 +01:00
Mitchell Hashimoto
899ab302e1 apprt/gtk: any preedit change should note a composing state (#6779)
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-17 11:59:33 -07:00
Mitchell Hashimoto
590eb60759 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-17 11:44:39 -07:00
Jeffrey C. Ollie
daa79c3598 gtk: address review comments
1. Remove usage of C header imports for gtk x11/wayland.
2. Move X11 C header imports to winproto_x11.zig
3. Clean up long line by breaking it up into multiple steps.
2025-03-17 12:35:31 -05:00
Jeffrey C. Ollie
29322535a5 gtk: convert Window (and some related files) to zig-gobject 2025-03-17 12:06:57 -05:00
Jeffrey C. Ollie
e0fe12cc05 Update Debian 12 Dockerfile (#6776)
1. Automatically detect the required Zig version rather than using a
hardcoded value.
2. Run `ghostty +version` after the build as a sanity check.
2025-03-17 11:09:47 -05:00
Jeffrey C. Ollie
1d040dd17d debian workflow: remove unused ZIG_VERSION arg 2025-03-17 10:54:44 -05:00
Jeffrey C. Ollie
7f7191dfec Update Debian 12 Dockerfile
1. Automatically detect the required Zig version rather than
using a hardcoded value.
2. Run `ghostty +version` after the build as a sanity check.
2025-03-17 10:41:17 -05:00
Jeffrey C. Ollie
a2df8e4b86 gtk: update Tab to use zig-gobject (#6729) 2025-03-17 10:39:13 -05:00
Mitchell Hashimoto
c344c320eb Update iTerm2 colorschemes (#6755)
Upstream revision:
e348884a00
2025-03-16 07:18:30 -07:00
mitchellh
291c2f541c deps: Update iTerm2 color schemes 2025-03-16 14:17:59 +00:00
Mitchell Hashimoto
f8590ce44f scroll: translate non-precision to precision (#6750)
Some wheel mice are capable of reporting fractional wheel ticks. These
mice don't necessarily report a corresponding precision scroll start
event, at least in Wayland + GTK. We can treat all discrete (ie
non-precision) events as the number of wheel ticks - for wheel mice,
yoff will be "1.0" per tick, while precision wheel mice may report
fractional values. This unifies handling of scroll events by normalizing
all events to "pixels to scroll".

We now report `mouse-scroll-multiplier` wheel or arrow events per wheel
tick (or per accumulated cell height). This means that applications
which subscribe to mouse button events will receive (by default) three
wheel events per wheel tick. For precision scrolls, they will receive
one wheel tick per line of scroll. In my opinion, this provides the best
user experience while also allowing customization of how much a
wheel tick should scroll

Reference: https://github.com/ghostty-org/ghostty/discussions/6677
2025-03-16 07:15:34 -07:00
Mitchell Hashimoto
d3424a922a update zon2nix (#6728)
Upstream is now mostly pure Zig and the build.zig.zon.* files are
generated directly by zon2nix. The JSON file is no longer used as an
intermediate file but is retained for downstream packager usage.
2025-03-16 07:13:24 -07:00
Jeffrey C. Ollie
5cd8ebdafd update zon2nix
Upstream is now mostly pure Zig and the build.zig.zon.* files are
generated directly by zon2nix. The JSON file is no longer used as an
intermediate file but is retained for downstream packager usage.
2025-03-16 01:09:52 -05:00
Jeffrey C. Ollie
3bc2b02303 build: update libvaxis and zf (#6752)
Fixes #6734
2025-03-15 23:42:49 -05:00
Jeffrey C. Ollie
ec4d110251 build: update libvaxis and zf
Fixes #6734
2025-03-15 22:06:18 -05:00
Tim Culverhouse
2018a8fd3c scroll: translate non-precision to precision
Some wheel mice are capable of reporting fractional wheel ticks. These
mice don't necessarily report a corresponding precision scroll start
event, at least in Wayland + GTK. We can treat all discrete (ie
non-precision) events as the number of wheel ticks - for wheel mice,
yoff will be "1.0" per tick, while precision wheel mice may report
fractional values. This unifies handling of scroll events by normalizing
all events to "pixels to scroll".

We now report `mouse-scroll-multiplier` wheel or arrow events per wheel
tick (or per accumulated cell height). This means that applications
which subscribe to mouse button events will receive (by default) three
wheel events per wheel tick. For precision scrolls, they will receive
one wheel tick per line of scroll. In my opinion, this provides the best
user experience while also allowing customization of how much a
wheel tick should scroll

Reference: https://github.com/ghostty-org/ghostty/discussions/6677
2025-03-15 21:35:39 -05:00
Jeffrey C. Ollie
4b1d1e0ed4 gtk: update Tab to use zig-gobject 2025-03-15 14:19:59 -05:00
Mitchell Hashimoto
644acdacdc termio, flatpak: implement process watcher with xev (#6658)
This allows `termio.Exec` to track processes spawned via
`FlatpakHostCommand`, finally allowing Ghostty to function as a Flatpak.

Alongside this is a few bug fixes:

* Don't add ghostty to PATH when running in flatpak mode since it's
unreachable.
* Correctly handle exit status returned by Flatpak. Previously this was
not processed and contains extra status bits.
* Use correct type for PID returned by Flatpak.
2025-03-15 08:09:54 -07:00
Mitchell Hashimoto
791d332a25 pkg/macos: clean up for Zig 0.14, consolidate C imports into one decl (#6736)
Fixes #6727

The major change in this commit is to consolidate all the C imports in a
single decl in main.zig. This is required for Zig 0.14. Without it, the
problem in #6727 will happen. I was never able to minimize why this
happens in order to open a Zig bug.

Beyond this, I fixed the build.zig and build.zig.zon to work with Zig
0.14 so that we can test building `pkg/macos` in isolation. There are no
downstream impacting changes in the build.zig files.
2025-03-15 07:30:05 -07:00
Leorize
009b53c45e termio, flatpak: implement process watcher with xev
This allows `termio.Exec` to track processes spawned via
`FlatpakHostCommand`, finally allowing Ghostty to function as a
Flatpak.

Alongside this is a few bug fixes:

* Don't add ghostty to PATH when running in flatpak mode since it's
  unreachable.
* Correctly handle exit status returned by Flatpak. Previously this was
  not processed and contains extra status bits.
* Use correct type for PID returned by Flatpak.
2025-03-15 07:29:13 -07:00
Mitchell Hashimoto
5ad8ea6b22 pkg/macos: clean up for Zig 0.14, consolidate C imports into one decl
Fixes #6727

The major change in this commit is to consolidate all the C imports in
a single decl in main.zig. This is required for Zig 0.14. Without it,
the problem in #6727 will happen. I was never able to minimize why this
happens in order to open a Zig bug.

Beyond this, I fixed the build.zig and build.zig.zon to work with Zig
0.14 so that we can test building `pkg/macos` in isolation. There are no
downstream impacting changes in the build.zig files.
2025-03-15 07:02:53 -07:00
Leah Amelia Chen
f8f9f7041a os: fix use of deprecated splitBackwards for Flatpak (#6733) 2025-03-15 08:02:41 +01:00
Leorize
2e6a2a148f os: fix use of deprecated splitBackwards for Flatpak 2025-03-14 23:48:59 -05:00
Mitchell Hashimoto
550edd4262 build: mark most dependencies as lazy (#6726)
Closes #6703 

Lazy dependencies are only fetched if the build script would actually
reach a usage of that dependency at runtime (when the `lazyDependency`
function is called). This can save a lot of network traffic, disk uage,
and time because we don't have to fetch and build dependencies that we
don't actually need.

Prior to this commit, Ghostty fetched almost everything for all
platforms and configurations all the time. This commit reverses that to
fetching almost nothing until it's actually needed.

There are very little downsides to doing this[1]. One downside is `zig
build --fetch` doesn't fetch lazy dependencies, but we don't rely on
this command for packaging and suggest using our custom shell script
that downloads a cached list of URLs (`build.zig.zon.txt`).

This commit doesn't cover 100% of dependencies, since some provide no
benefit to make lazy while the complexity to make them lazy is higher
(in code style typically).

Conversely, some simple dependencies are marked lazy even if they're
almost always needed if they don't introduce any real complexity to the
code, because there is very little downside to do so.

[1]: https://ziggit.dev/t/lazy-dependencies-best-dependencies/5509/5
2025-03-14 18:33:32 -07:00
Mitchell Hashimoto
cfea2ea12c build: mark most dependencies as lazy
Lazy dependencies are only fetched if the build script would actually
reach a usage of that dependency at runtime (when the `lazyDependency`
function is called). This can save a lot of network traffic, disk uage,
and time because we don't have to fetch and build dependencies that we
don't actually need.

Prior to this commit, Ghostty fetched almost everything for all
platforms and configurations all the time. This commit reverses that to
fetching almost nothing until it's actually needed.

There are very little downsides to doing this[1]. One downside is `zig
build --fetch` doesn't fetch lazy dependencies, but we don't rely on
this command for packaging and suggest using our custom shell script
that downloads a cached list of URLs (`build.zig.zon.txt`).

This commit doesn't cover 100% of dependencies, since some provide no
benefit to make lazy while the complexity to make them lazy is higher
(in code style typically).

Conversely, some simple dependencies are marked lazy even if they're
almost always needed if they don't introduce any real complexity to the
code, because there is very little downside to do so.

[1]: https://ziggit.dev/t/lazy-dependencies-best-dependencies/5509/5
2025-03-14 13:32:19 -07:00
Leah Amelia Chen
234b804872 contributing: fix link to Translator's Guide (#6712) 2025-03-14 17:29:10 +01:00
Jeffrey C. Ollie
f37d1fd7ed gtk: convert Surface to zig-gobject (#6634)
Marking this is as draft because I want to test this further before
saying that it's ready, but putting it out there for feedback.
2025-03-14 10:14:33 -05:00
Leah Amelia Chen
c2aac45848 fix: Use builtin source_env_if_exists for sourcing .envrc.local (#6717) 2025-03-14 13:33:37 +01:00
Uzair Aftab
b497400be6 fix: ignore .envrc.local 2025-03-14 13:15:29 +01:00
Uzair Aftab
ec5066988e fix: Use builtin source_env_if_exists for sourcing .envrc.local 2025-03-14 13:11:05 +01:00
Leah Amelia Chen
4ab3754a59 feat: source local .envrc if it exists (#6715) 2025-03-14 12:14:19 +01:00
Uzair Aftab
b9ea32b8ce Update .envrc
Co-authored-by: Kat <65649991+00-kat@users.noreply.github.com>
2025-03-14 12:08:37 +01:00
Uzair Aftab
69dfc8870d fix: use bashism in .envrc
Co-authored-by: Leah Amelia Chen <github@acc.pluie.me>
2025-03-14 12:00:25 +01:00
Uzair Aftab
09d538b620 feat: source .envrc.local if it exists
Co-authored-by: Kat <65649991+00-kat@users.noreply.github.com>
2025-03-14 11:50:56 +01:00
Leah Amelia Chen
07b653bd71 contributing: link to po/README_CONTRIBUTORS.md 2025-03-14 08:46:06 +01:00
Leah Amelia Chen
e8101c1136 contributing: fix link to Translator's Guide
For some reason it pointed to the Contributor's Guide instead...
2025-03-14 08:44:04 +01:00
Mitchell Hashimoto
8eacde92e6 Replace mach-glfw with pkg/glfw (#6708)
Closes #6702

This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.

This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.

The reasoning is stated in the issue but to summarize for the commit:

  - mach-glfw is no longer maintained, so we have to take ownership
- mach-glfw depended on some large blobs of header files to enable
cross-compilation but this isn't something we actually care about, so we
can (and do) drop the blobs
- mach-glfw blobs were hosted on mach hosts. given mach-glfw is
unmaintained, we can't rely on this hosting
- mach-glfw relied on a "glfw" package which was owned by another person
to be Zig 0.14 compatible, but we no longer need to rely on this
  - mach-glfw builds were outdated based on latest Zig practices
2025-03-13 21:18:41 -07:00
Mitchell Hashimoto
221f905a1c pkg/glfw
Closes #6702

This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.

This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.

The reasoning is stated in the issue but to summarize for the commit:

  - mach-glfw is no longer maintained, so we have to take ownership
  - mach-glfw depended on some large blobs of header files to enable
    cross-compilation but this isn't something we actually care about,
    so we can (and do) drop the blobs
  - mach-glfw blobs were hosted on mach hosts. given mach-glfw is
    unmaintained, we can't rely on this hosting
  - mach-glfw relied on a "glfw" package which was owned by another
    person to be Zig 0.14 compatible, but we no longer need to rely on
    this
  - mach-glfw builds were outdated based on latest Zig practices
2025-03-13 20:52:33 -07:00
Mitchell Hashimoto
73c7943fff CODEOWNERS: add localization teams (#6704) 2025-03-13 10:19:29 -07:00
Mitchell Hashimoto
d8497d9b16 CODEOWNERS: add localization teams 2025-03-13 10:07:27 -07:00
Mitchell Hashimoto
ef0ff94c75 translations: add German translation (#6601)
This PR adds a translation for German `de_DE`.

Additionally it excludes all `*.po` files from the typos CI action.

Some comments on the decisions I made (open to discuss them):

- I choosed to use `du` instead of `Sie` as this seems appropriate
  to me.
- I added `Window` (`Fenster` in German) to all split commands
  as it appears more naturally to me.
2025-03-13 10:05:27 -07:00
Jeffrey C. Ollie
572fc8b5d7 gtk: convert Surface to zig-gobject 2025-03-13 12:04:10 -05:00
Mitchell Hashimoto
1f6b1d75eb Fix aspect ratio when rendering images with kitty protocol (#6675)
Fixes https://github.com/ghostty-org/ghostty/issues/6673

<img width="914" alt="image"
src="https://github.com/user-attachments/assets/010a1304-0d46-46ec-9a82-87a8d8fbea1b"
/>
2025-03-13 09:50:19 -07:00
Mitchell Hashimoto
aeada3f1a8 Zig 0.14 (#6699)
Closes #5744 

This gets Ghostty onto Zig 0.14. The goal of this PR is to focus only on
Zig 0.14 _compatibility_. I plan to open a number of subsequent issues
for future improvements I'd like to tackle, as noted in #5744.

I did run some basic benchmarks on a Zig 0.13 vs 0.14 build and didn't
notice any statistically significant changes. All our scrolling
benchmarks on vtebench got consistently 1 or 2ms faster but that may
just be noise. The good news is nothing got consistently slower (nothing
got slower at all on any runs!).

The Git history here is kind of nasty, I'm going to squash it.
2025-03-13 09:36:09 -07:00
Mitchell Hashimoto
f1f9db8b96 Update PACKAGING to note Zig 0.14 requirement 2025-03-13 09:18:34 -07:00
Mitchell Hashimoto
a542e63582 pkg/gtk4-layer-shell: disable ubsan 2025-03-13 09:14:37 -07:00
Mitchell Hashimoto
bdb66984b6 for iOS simulator builds for apple M1 as base CPU model 2025-03-13 07:13:13 -07:00
Mitchell Hashimoto
6613a695f0 nix 2025-03-12 16:30:22 -07:00
Mitchell Hashimoto
b96a5d702b fix mach-glfw on windows 2025-03-12 16:29:17 -07:00
Mitchell Hashimoto
66c83648c8 ci: debian 12 build should use zig 0.14 2025-03-12 16:29:17 -07:00
Mitchell Hashimoto
1dbeba7065 ci: update snap to Zig 0.14 2025-03-12 16:29:17 -07:00
Mitchell Hashimoto
fc21444f2d fix windows 2025-03-12 16:29:17 -07:00
Mitchell Hashimoto
907ed239a1 update typos 2025-03-12 16:29:17 -07:00
Mitchell Hashimoto
b123b14686 update zig2nix 2025-03-12 15:56:24 -07:00
Mitchell Hashimoto
99bde549af fix /usr/lib issues 2025-03-12 15:46:15 -07:00
Mitchell Hashimoto
1f6aa0e90d apprt/glfw: move darwin enabled const out to top-level 2025-03-12 12:53:15 -07:00
Mitchell Hashimoto
18084a3e61 update gobject, fix compiler errors 2025-03-12 12:32:50 -07:00
Mitchell Hashimoto
816ff8cef0 fix tests building on Linux 2025-03-12 11:29:13 -07:00
Mitchell Hashimoto
2e45a4c7a3 fix typos 2025-03-12 11:23:11 -07:00
Mitchell Hashimoto
3116a1b92c bundle ubsan rt 2025-03-12 11:07:15 -07:00
Mitchell Hashimoto
601acf4059 pkg/highway: upgrade to fix compilation issues on LLVM18 2025-03-12 11:03:54 -07:00
Mitchell Hashimoto
7e9be00924 working on macos 2025-03-12 10:15:14 -07:00
Mitchell Hashimoto
43467690f3 test 2025-03-12 10:04:17 -07:00
Mitchell Hashimoto
2408d4c6a9 more fixes 2025-03-12 09:59:24 -07:00
Mitchell Hashimoto
0f4d2bb237 Lots of 0.14 changes 2025-03-12 09:55:52 -07:00
Mitchell Hashimoto
86d3f18707 pkg/oniguruma: fix build 2025-03-12 09:10:17 -07:00
Bryan Lee
2f814b37e8 Add unit tests for kitty image aspect ratio calculation 2025-03-12 22:44:17 +08:00
Mitchell Hashimoto
bd848a27d2 update all packages to new hash for caching 2025-03-12 07:30:01 -07:00
Bryan Lee
f091a69790 Fix aspect ratio when rendering images with kitty protocol 2025-03-12 15:06:06 +08:00
Mitchell Hashimoto
2466de4556 pkg: update to new build.zig.zon format and hash values 2025-03-11 15:00:47 -07:00
Mitchell Hashimoto
251caeb22a Zig 0.14 fixes 2025-03-11 14:53:30 -07:00
Mitchell Hashimoto
3abbe6d3ba nix: must not inject xcrun into PATH on macOS 2025-03-11 14:49:01 -07:00
Mitchell Hashimoto
7e2286eb8c Zig 0.14 2025-03-11 14:39:04 -07:00
Leah Amelia Chen
95daca616d core: refactor RepeatablePath into separate files and add Path (#6622)
Slims down `Config.zig` and makes some of the code reusable in Path.
2025-03-10 09:14:25 +01:00
Leah Amelia Chen
14b66e93d1 pkg(gtk4-layer-shell): Enable using system-installed headers for dynamic linking (#6624)
I noticed we weren't doing system-integration against the pkgconfig for
gtk4-layer-shell. This behaviour differed from how we handled system
integration for existing deps in `pkg/` (oniguruma, fontconfig).

Refactored `pkg/gtk4-layer-shell/build.zig` referencing
`pkg/oniguruma/build.zig` to use pkgconfig names in system integration.
Previously we used to libname `libgtk4-layer-shell.so`
(`gtk4-layer-shell`) instead of pkgconfig name `gtk4-layer-shell-0.pc`
which meant system integration still relied on fetching the C-headers
via `zig fetch` instead of system C-headers.

I've tested this with a `--system` build where the relevant
`.zig-cache/p/<hash of gtk4-layer-shell>` is stubbed to an empty
directory and `pkgconfig(gtk4-layer-shell-0)` is installed instead on
fedora linux.
2025-03-10 09:11:42 +01:00
Mitchell Hashimoto
0ecee3ee92 Fix passing EnvMap for Flatpak builds (#6647)
When using -Dflatpak=true the EnvMap was passed as the incorrect type.
2025-03-09 18:39:54 -07:00
Mitchell Hashimoto
57d0a4d2e7 font(freetype): constrain emoji to cell width (#6602)
When scaling emoji (with freetype), we would unilaterally scale the
bitmap to fit within the `cell_height`. For narrow fonts, this would
result in a horizontal overflow:


![image](https://github.com/user-attachments/assets/87b9f952-6f12-40b2-bbed-5bfe948f45b4)

Modify the glyph rendering such that we scale to fit within the cell
width. After doing so, the above image looks like:


![image](https://github.com/user-attachments/assets/c75bfa51-4730-4179-b032-c3afa7840d65)

The emoji glyph is noticeably smaller because we have constrained the
height further than before, but fits perfectly within two cells. I am
using Victor Mono as my font, which is pretty narrow. The effect would
be even more pronounced on something like Iosevka.
2025-03-09 18:34:34 -07:00
Mitchell Hashimoto
480b1a9805 macOS: only set LANGUAGE for app bundle, do not inherit in termio env (#6648)
Fixes #6633

For macOS, we set LANGUAGE to the priority list of preferred languages
for the app bundle, using the GNU gettext priority list format (colon
separated list of language codes).

This previously was inherited by the termio env. At first, this was by
design, but this has inherent flaws. Namely, the priority list format is
a GNU gettext specific format, and programs that use alternate gettext
implementations (like musl or Python) do not understand it and actually
do the wrong thing (not their fault!).

This change removes the inheritance of LANGUAGE in the termio env. To
make it extra safe, we only do set and unset LANGUAGE when we know we
launch from an app bundle. That was always the desired behavior but this
makes it more explicit.
2025-03-09 17:08:55 -07:00
Mitchell Hashimoto
ebffe299ce macOS: only set LANGUAGE for app bundle, do not inherit in termio env
Fixes #6633

For macOS, we set LANGUAGE to the priority list of preferred languages
for the app bundle, using the GNU gettext priority list format (colon
separated list of language codes).

This previously was inherited by the termio env. At first, this was by
design, but this has inherent flaws. Namely, the priority list format is
a GNU gettext specific format, and programs that use alternate gettext
implementations (like musl or Python) do not understand it and actually
do the wrong thing (not their fault!).

This change removes the inheritance of LANGUAGE in the termio env. To
make it extra safe, we only do set and unset LANGUAGE when we know we
launch from an app bundle. That was always the desired behavior but this
makes it more explicit.
2025-03-09 18:17:38 -05:00
Yorick Peterse
300f4544ef Fix passing EnvMap for Flatpak builds
When using -Dflatpak=true the EnvMap was passed as the incorrect type.
2025-03-09 23:46:24 +01:00
Jeffrey C. Ollie
843cc83f42 gtk: implement quick-terminal-size (#6629)
Fixes #2384 on GTK

I'm not exactly sure how to deal with centered quick terminals so I
opted to make them similar to either top/bottom or left/right quick
terminals based on the monitor's orientation (portrait/landscape). This
may not be the right approach, so I'd like to hear more thoughts about
this.
2025-03-09 09:51:04 -05:00
Mitchell Hashimoto
78f16d040d macOS: disable setting LANGUAGE for now until bug is fixed
See: https://github.com/ghostty-org/ghostty/discussions/6633

This is temporary while we figure this out.
2025-03-09 07:25:55 -07:00
Jeffrey C. Ollie
6767493428 core: move RepeatablePath to separate file and enable Path as config type
Slim down Config.zig by moving RepeatablePath to a separate file and
enable the use of Path as it's own config type.
2025-03-09 09:18:01 -05:00
Jeffrey C. Ollie
bb3dad1309 core: all paths referenced from the CLI must be expanded 2025-03-09 09:17:57 -05:00
Mitchell Hashimoto
d3fd2b02e7 terminal: remove redundant assertIntegrity from clearPrompt (#6630)
clearCells() always asserts its page's integrity after finishing its
work (via a `defer`). We don't need to re-assert the page's integrity
immediately thereafter.
2025-03-08 14:37:23 -08:00
Mitchell Hashimoto
5efa2a6ca1 macOS: Set LANGUAGE env var based on macOS preferred language list (#6628)
Sets the LANGUAGE environment variable based on the preferred languages
as reported by NSLocale.

macOS has a concept of preferred languages separate from the system
locale. The set of preferred languages is a list in priority order of
what translations the user prefers. A user can have, for example,
"fr_FR" as their locale but "en" as their preferred language. This would
mean that they want to use French units, date formats, etc. but they
prefer English translations.

gettext uses the LANGUAGE environment variable to override only
translations and a priority order can be specified by separating the
languages with colons. For example, "en:fr" would mean that English
translations are preferred but if they are not available then French
translations should be used.

To further complicate things, Apple reports the languages in BCP-47
format which is not compatible with gettext's POSIX locale format so we
have to canonicalize them. To canonicalize the languages we use an
internal function from libintl. This isn't normally available but since
we compile from source on macOS we can use it. This isn't necessary for
other platforms.

This logic is only run if the user didn't explicitly request a specific
locale, so it should really only affect macOS app launches. From the CLI
the environment will have a locale unless the user really explicitly
clears it out.
2025-03-08 14:36:58 -08:00
Leah Amelia Chen
a0080ddad7 gtk: implement quick-terminal-size
Fixes #2384 on GTK

I'm not exactly sure how to deal with centered quick terminals so I opted
to make them similar to either top/bottom or left/right quick terminals
based on the monitor's orientation (portrait/landscape). This may not be
the right approach, so I'd like to hear more thoughts about this.
2025-03-08 22:36:24 +01:00
Jon Parise
b0b2de01f5 terminal: remove redundant assertIntegrity from clearPrompt
clearCells() always asserts its page's integrity after finishing its
work (via a `defer`). We don't need to re-assert the page's integrity
immediately thereafter.
2025-03-08 16:36:14 -05:00
Mitchell Hashimoto
b48fcf33f7 macOS: Set LANGUAGE env var based on macOS preferred language list
Sets the LANGUAGE environment variable based on the preferred languages
as reported by NSLocale.

macOS has a concept of preferred languages separate from the system
locale. The set of preferred languages is a list in priority order
of what translations the user prefers. A user can have, for example,
"fr_FR" as their locale but "en" as their preferred language. This would
mean that they want to use French units, date formats, etc. but they
prefer English translations.

gettext uses the LANGUAGE environment variable to override only
translations and a priority order can be specified by separating
the languages with colons. For example, "en:fr" would mean that
English translations are preferred but if they are not available
then French translations should be used.

To further complicate things, Apple reports the languages in BCP-47
format which is not compatible with gettext's POSIX locale format so
we have to canonicalize them. To canonicalize the languages we use
an internal function from libintl. This isn't normally available but
since we compile from source on macOS we can use it. This isn't
necessary for other platforms.
2025-03-08 12:54:39 -08:00
azhn
35aab1a302 build: use pkgconfig name for gtk4-layer-shell system integration
By linking using the pkg-config name we gain the compiler flags in pkgconf
for linking, specifically the -I <headers> to include system-installed
headers. This allows the gtk4-layer-shell pkg to not require the source
files specified in the `pkg/gtk4-layer-shell/build.zig.zon`.

pkg(gtk4-layer-shell): Refactor to allow dynamic linking

Refactored `pkg/gtk4-layer-shell/build.zig` to have similar structure
to `pkg/oniguruma/build.zig`.

Now dynamic link using pkgconfig, this adds pkgconfig compiler flags.
So we are now using system-installed headers to resolve @cInclude().
2025-03-09 02:46:33 +11:00
Robin Pfäffle
c67c7da582 translations(german): fix fuzzy translations 2025-03-08 09:24:54 +01:00
Robin Pfäffle
9d86bdfe72 translations: add de_DE to locales 2025-03-08 08:23:33 +01:00
Robin Pfäffle
abb97fa574 translations(german): update de_DE strings 2025-03-08 08:11:43 +01:00
Robin Pfäffle
7e268b9a43 ci: typos ignore *.po files 2025-03-08 08:11:43 +01:00
Robin Pfäffle
d2931b5d8f translations(german): address review comments 2025-03-08 08:11:41 +01:00
Robin Pfäffle
2ef11fb65f translations(german): add missing warning indicator 2025-03-08 08:08:51 +01:00
Robin Pfäffle
d511b3601d translations: add German translation 2025-03-08 08:08:51 +01:00
Mitchell Hashimoto
e03e98e106 Groundwork for cross-platform i18n with libintl for libghostty/macOS (#6619)
This builds on @pluiedev's excellent #6004.

## Background: The macOS (and libghostty consumer) Plan

Broadly, the decision I've come to is that for cross-platform
translations (i.e. strings shared across libghostty), we will be using
gettext and libghostty will export helper methods to call those (e.g.
`ghostty_translate` in this PR for singular forms). To be clear, **this
only applies to strings owned by libghostty**. For application-level
strings such as macOS-specific menu items and so on, we still have
choice but will likely using native features.

The reason for this is because converting gettext translations (`po`) to
native formats (Xcode String Catalog, `.strings`/`.stringsdict`) is
nightmare level, in particular for plural forms. I don't see a robust
path to doing it. And if we don't convert and don't use gettext, then
translators would have to maintain an identical translation in multiple
locations. To make matters worse, the macOS translation formats require
Apple-tooling for now unless you want to edit raw JSON.

Leveraging gettext lets us share translations across platforms and take
advantage of proven tech.

## PR Contents

**`pkg/libintl` builds and statically links libintl for macOS.** macOS
doesn't ship libintl with the system while Linux generally does with
libc, so we need to build this ourselves. This makes gettext available
to macOS. libintl is LGPL and we remain in compliance with that despite
static linking because our build process is fully open source, so
downstream consumers can modify our build scripts to replace it if they
wanted to.

~~**`src/os/locale.zig` now sets the `LANGUAGE` environment variable on
macOS based on the app's preferred languages.** macOS lets you configure
the system locale separate from preferred language. We previously relied
solely on `NSLocale.currentLocale`, but this only represents the system
locale. We now also look at `NSLocale.preferredLanguages` (a list in
priority order) and if we support a given language we set `LANGUAGE` so
gettext translates properly. Notably, the above lets us debug
translations in Xcode by setting alternate languages for debug builds
only.~~ Removed this for a future PR since it was problematic.

**`build.zig` unconditionally builds binary `mo` files** since they're
required for all apprts now.

**The macOS app bundles the translation strings.** This includes our
GTK-specific translation strings but the size of these is so small it
isn't worth the complexity of splitting up into multiple `pot`s at this
time, I think.

**i18n APIs moved to `src/os` from `src/apprt/gtk`.** Since these are
now cross-platform/cross-apprt, they're a core API. The only notable
change here is that `_` now maps to `dgettext` and explicitly specifies
our domain so that it's library-friendly. The GTK apprt calls
`initGlobalDomain` so that blueprint translations still work.

## Next Steps

This PR is all groundwork. The macOS app doesn't leverage any of this
yet, although I've verified it all works (e.g. calling the
`ghostty_translate` API from Swift).

For next steps, we need to have a use case for cross-platform
translations and the first one I was looking at was configuration error
messages and other core strings.
2025-03-07 14:51:12 -08:00
Mitchell Hashimoto
dcb8440b52 os: remove the preferredLanguages lookup 2025-03-07 13:43:00 -08:00
Mitchell Hashimoto
7eddf98269 Fix typos 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
da731e6caa typo i81n -> i18n 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
79a9ddf66f build: pure libghostty builds need to build translations 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
e8a988f6d3 os: i18n unsupported on windows 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
c7681e8fd7 apprt/gtk: use the new global i18n API 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
cff092f4c6 nix: update hashes 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
3ebd5b839f update translating readme 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
be839cb681 update our gitattributes with new generated files 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
3c49bc5086 os: locale automatically sets LANGUAGE based on macOS preferred 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
edf619205c add ghostty_translate C API 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
238573d42e i18n: export proper _ function 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
4cf127a064 build: i18n should emit mo on every platform 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
e8c20b5501 pkg/libintl: fix missing symbols 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
cb8085ab72 global state initializes i18n 2025-03-07 13:42:00 -08:00
Mitchell Hashimoto
dd95f727ec build: add libintl for macOS builds 2025-03-07 13:41:59 -08:00
Mitchell Hashimoto
67488754d5 pkg/libintl 2025-03-07 13:41:59 -08:00
Leah Amelia Chen
4a215a9518 gtk: use AdwAlertDialog for close dialogs, fix incorrect close dialogs (#5741)
AdwAlertDialog is the recommended way to do alert/message dialogs
starting from libadwaita 1.5, and is much easier to manage than
GtkMessageDialog. (The latter is also deprecated since GTK 4.10, but
this PR does not migrate it to use GtkAlertDialog, mostly because of its
obtuse interface and that we'll remove the GtkMessageDialog code anyway
in 1.2 when we remove non-Adwaita builds.)

We also had two bugs where tabs with only one split would display the
"close surface" confirmation dialog, and windows would do the same when
closed via the "Close Window" menu item or by the `close_window` keybind
action. (The "close window" dialog only appears when the user clicks on
the close button on the titlebar.) Initially I was very confused by
this, but it turns out that we don't have any apprt action related to
closing a window, and it was simply closing surfaces...
2025-03-07 20:46:23 +01:00
Leah Amelia Chen
b25da6b9c4 core: update zig2nix to use explicit Zig version from Nix (#6617)
This will avoid build breakage in the near future as Zig 0.14 is coming
to Nix.
2025-03-07 20:42:13 +01:00
Jeffrey C. Ollie
3ba4864f6c core: update zig2nix to use explicit Zig version from Nix
This will avoid build breakage in the near future as Zig 0.14 is coming
to Nix.
2025-03-07 11:46:30 -06:00
Leah Amelia Chen
77e16770cc gtk: build gtk4-layer-shell ourselves (#6614)
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed,
etc.) and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41,
etc.) This is inconvenient for our users and severely limits where the
quick terminal can be used. As a result we then build gtk4-layer-shell
ourselves by default unless `--system` or `-fsys=gtk4-layer-shell` are
specified. This also allows me to add an idiomatic Zig API on top of the
library and avoiding adding even more raw C code in the GTK apprt.

Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as I
don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
2025-03-07 18:19:49 +01:00
Leah Amelia Chen
cd442eb9e2 gtk: build gtk4-layer-shell ourselves
As of now `gtk4-layer-shell` is unavailable on recent, stable releases
of many distros (Debian 12, Ubuntu 24.04, openSUSE Leap & Tumbleweed, etc.)
and outdated on many others (Nixpkgs 24.11/unstable, Fedora 41, etc.)
This is inconvenient for our users and severely limits where the quick
terminal can be used. As a result we then build gtk4-layer-shell ourselves
by default unless `--system` or `-fsys=gtk4-layer-shell` are specified.
This also allows me to add an idiomatic Zig API on top of the library
and avoiding adding even more raw C code in the GTK apprt.

Since we now build gtk4-layer-shell it should be theoretically available
on all Linux systems we target. As such, the `-Dgtk-layer-shell` build
option has been removed. This is somewhat of an experimental change as
I don't know if gtk4-layer-shell works perfectly across all distros, and
we can always add the option back if need be.
2025-03-07 17:52:06 +01:00
Leah Amelia Chen
6c00c36d62 config: make quick-terminal-autohide=false on Linux (#6613)
See diff for explanation
2025-03-07 17:36:05 +01:00
Leah Amelia Chen
9ed76729ab gtk: add separate close_window apprt action
For *some* reason we have a binding for close_window but it merely closes
the surface and not the entire window. That is not only misleading but
also just wrong. Now we make a separate apprt action for close_window
that would make it show a close confirmation prompt identical to as if
the user had clicked the (X) button on the window titlebar.
2025-03-06 20:32:38 +01:00
Leah Amelia Chen
b4bfdb2c44 translation: update template & zh_CN strings 2025-03-06 20:32:30 +01:00
Leah Amelia Chen
23d2d4ec70 gtk: use AdwAlertDialog for close dialogs 2025-03-06 20:32:30 +01:00
Tim Culverhouse
40c1140f7d font(freetype): constrain emoji to 2 cells wide
When scaling emoji, scale so that they entirely fit within 2 cells. The
previous behavior was to scale to fill vertically, however with fonts
which are narrow this would result in horizontal overflow.
2025-03-06 10:46:58 -06:00
Tim Culverhouse
27c4fd76f3 renderer(OpenGL): pass cell_width to glyph renderer
When adding a glyph, we didn't pass the expected width to the glyph
renderer. This can be helpful when scaling emoji, as will happen in the
next commit.
2025-03-06 09:59:24 -06:00
Tim Culverhouse
22ed08cfd8 chore: zig fmt 2025-03-06 09:59:24 -06:00
Leah Amelia Chen
260a90cbf0 config: make quick-terminal-autohide=false on Linux
See diff for explanation
2025-03-06 12:38:23 +01:00
Leah Amelia Chen
e07b6fdf6b gtk: implement quick terminal slide & autohide (#6090) 2025-03-05 23:20:03 +01:00
Leah Amelia Chen
44d4990eb2 gtk: implement quick-terminal-autohide 2025-03-05 21:51:35 +01:00
Leah Amelia Chen
58b0434092 docs: update information about quick terminal support on Linux 2025-03-05 21:37:49 +01:00
Jeffrey C. Ollie
d6bd7b56b3 gtk: implement sensitive content reveal on paste confirmation (#6054)
Fixes https://github.com/ghostty-org/ghostty/issues/4947 for gtk
This PR implements the senstive content hiding when displaying the paste
confirmation dialog in secure input mode.

Following changes are implemented:
- in the blueprint for each dialog add a show/hide button that is not
visible by default, and a Revealer that is revealed by default
- save the `secure_input` action value for each surface in the GTK apprt
- pass the value when initializing the paste confirmation dialog
- in the dialog code, alter the visibility of the content and
reveal/hide buttons based on secure input flag value

Demo:


https://github.com/user-attachments/assets/c91cbd3d-ed3b-464d-b4cf-e51fe7aa23b7

I feel like this is already a nearly full implementation, but I'm
leaving this as a draft for now, since i need to look into blueprints
for Adwaita 1.2, and verify if it behaves properly when the dialog is in
not-sensitive input mode and in OSC52 mode.
2025-03-05 14:27:13 -06:00
Leah Amelia Chen
8f7425f78c gtk: implement quick terminal slide animation
Yet another protocol that as far as I know only KWin implements.
Oh well, might as well let KDE users such as myself enjoy it OOTB
2025-03-05 21:13:13 +01:00
Maciej Bartczak
bd617c52e9 code review:
- implement blueprints for Adwaita 1.2
- use postifx notation for casting gtk widgets
- fix formatting
2025-03-05 21:03:02 +01:00
Maciej Bartczak
f71b294697 gtk: new approach to reveal/hide buttons 2025-03-05 21:03:02 +01:00
Maciej Bartczak
7123d4e055 gtk: blur the content view instead of using a Revealer widget 2025-03-05 21:03:02 +01:00
Maciej Bartczak
1f695c2646 gtk: implement sensitive content reveal mechanism when showing paste confirmation in secure input mode 2025-03-05 21:02:58 +01:00
Jeffrey C. Ollie
58adaffcb9 gtk: don't modify horizontal alignment on menus that have arrows (#6087)
Setting the horizontal alignment to start on popover menus that have
arrows results in visual anomalies:


![image](https://github.com/user-attachments/assets/fef279a3-73cf-4717-9b32-605ccd48c934)

From Discord:

https://discord.com/channels/1005603569187160125/1346819853612482571
2025-03-05 13:42:50 -06:00
Jeffrey C. Ollie
8f62901218 gtk: don't modify horizontal alignment on menus that have arrows 2025-03-05 13:28:57 -06:00
Leah Amelia Chen
2f65f01fc8 gtk: add localization support, take 3 (#6004)
This is my third (!) attempt at implementing localization support. By
leveraging GTK builder to do most of the `gettext` calls, I can avoid
the whole mess about missing symbols on non-glibc platforms.

Added some documentation too for contributors and translators, just for
good measure.

Supersedes #5214, resolves the GTK half of #2357
2025-03-05 20:12:52 +01:00
Mitchell Hashimoto
66e8d91957 Make equalize_splits action only affect current window (#6080)
Fixes #6064 


https://github.com/user-attachments/assets/bbf393be-de98-41eb-aaad-3a185705ed4c
2025-03-04 07:36:00 -08:00
Ken VanDine
fd6e4fd615 fix: Generate pixbuf loader cache on start if needed, fixes #6066 (#6079)
This fix ensures the correct pixbuf loaders are used, not mixing in
libraries from the host.
2025-03-04 09:38:23 -05:00
Bryan Lee
423bc1971b Make equalize_splits action only affect current window 2025-03-04 22:37:32 +08:00
Ken VanDine
2c6e6ad680 fix: Generate pixbuf loader cache on start if needed, fixes #6066 2025-03-04 08:50:11 -05:00
Leah Amelia Chen
6373399e59 os: deprioritize GHOSTTY_RESOURCES_DIR for debug builds
When one develops Ghostty while using Ghostty it could lead to an
interesting conundrum: the freshly built Ghostty would use the parent
Ghostty's resources, which would be stale and not reflect any new
changes to resources. This is especially bad for translators, since
their translations would not be reflected in the newly built Ghostty
if they happen to run it under older Ghostty, which is not only
counterintuitive and also painful in terms of workflow.

Now, on debug builds we always try to use the terminfo detection method
first in order to locate the zig-out/share/ghostty folder, and only fall
back to GHOSTTY_RESOURCES_DIR if the executable is for some reason no
longer in zig-out. You can test this behavior by manually moving the
Ghostty executable out of zig-out, and then launching it with and without
Ghostty.
2025-03-03 10:19:58 +01:00
Mitchell Hashimoto
6b1a017a86 build: some style changes, namely we should create steps only in root 2025-03-03 10:19:58 +01:00
Leah Amelia Chen
e252932bde translations: add basic Chinese translation 2025-03-03 10:19:58 +01:00
Leah Amelia Chen
9c97084ad0 gtk: extract translations from Zig source code 2025-03-03 10:19:58 +01:00
Leah Amelia Chen
5851bad4a0 ci: add check that ensures POT files are up to date 2025-03-03 10:19:58 +01:00
Leah Amelia Chen
9360afd29f gtk: add localization support, take 3
This is my third (!) attempt at implementing localization support.
By leveraging GTK builder to do most of the `gettext` calls, I
can avoid the whole mess about missing symbols on non-glibc platforms.

Added some documentation too for contributors and translators,
just for good measure.
2025-03-03 10:19:58 +01:00
Tim Culverhouse
ee8ae196ee input: legacy encoding never encodes text for command mods on macOS (#6057)
Fixes #5929
Replaces #5984

On macOS, native applications typically never encode any text for key
events that use the command key. This is because the command key is used
for key equivalents and "commands" and should not be used for text
input.

This can be verified with apps like TextEdit but also terminals like
Terminal.app officially but also iTerm2 unofficially. Anything such as
`Cmd+b` or `Cmd+Shift+b` will not produce any text input.

Cross-platform terminals generally don't follow this, for example Kitty
performs CSI-u encoding and Alacritty and WezTerm encode the text as-is
(i.e. `Cmd+b` will produce `b`).

On Linux, the super key (command-equivalent) does produce text input.
For example, `Super+b` will produce `b` in Gnome Console, Foot, and all
the cross-platform terminals mentioned above.

In the interest of matching the behavior of native macOS applications,
we should not encode text for command key events on macOS. We continue
to encode text for the super key on non-macOS platforms. This matches
the behaviors appropriately on each platform.
2025-03-02 16:21:02 -06:00
Mitchell Hashimoto
a646aee6bd CODEOWNERS: terminal team should own input encoding (#6058) 2025-03-02 13:56:37 -08:00
Mitchell Hashimoto
28e20f3015 CODEOWNERS: terminal team should own input encoding 2025-03-02 13:54:35 -08:00
Mitchell Hashimoto
f93eb0b27f input: legacy encoding never encodes text for command mods on macOS
Fixes #5929
Replaces #5984

On macOS, native applications typically never encode any text for
key events that use the command key. This is because the command key
is used for key equivalents and "commands" and should not be used
for text input.

This can be verified with apps like TextEdit but also terminals like
Terminal.app officially but also iTerm2 unofficially. Anything such as
`Cmd+b` or `Cmd+Shift+b` will not produce any text input.

Cross-platform terminals generally don't follow this, for example Kitty
performs CSI-u encoding and Alacritty and WezTerm encode the text as-is
(i.e. `Cmd+b` will produce `b`).

On Linux, the super key (command-equivalent) does produce text input.
For example, `Super+b` will produce `b` in Gnome Console, Foot, and
all the cross-platform terminals mentioned above.

In the interest of matching the behavior of native macOS applications,
we should not encode text for command key events on macOS. We continue
to encode text for the super key on non-macOS platforms.
2025-03-02 13:53:20 -08:00
Mitchell Hashimoto
82326508b1 macos: set title of terminal window immediately if configured (#6056)
Fixes #5934 for macOS

If a `title` config is set, this change sets the title immediately on
windowDidLoad to ensure that the window appears with the correct title
right away.

If there is any reason to set another title, the `set_title` apprt
action will come on another event loop tick (due to our usage of
notifications) but that's okay since that's already how it works. This
is just to say that setting this here won't break any shell integration
or anything.
2025-03-02 13:48:33 -08:00
Mitchell Hashimoto
8d395c094b macos: set title of terminal window immediately if configured
Fixes #5934 for macOS

If a `title` config is set, this change sets the title immediately on
windowDidLoad to ensure that the window appears with the correct title
right away.

If there is any reason to set another title, the `set_title` apprt
action will come on another event loop tick (due to our usage of
notifications) but that's okay since that's already how it works. This
is just to say that setting this here won't break any shell integration
or anything.
2025-03-02 13:27:40 -08:00
Mitchell Hashimoto
fc893ae7e3 core!: modify scroll behavior (#6052)
Modify the scroll behavior to better match other terminals, as well as
provide a
better overall experience. Before this PR, ghostty would scale
non-precision
scroll events dependent on the screen size. This is in line with kitty,
but no
other terminal. Ghostty also was the only terminal to send *more than
one* wheel
event.

```
 # 50% Screen height
| terminal   | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty  |        3        |          1        |
| foot       |        3        |          1        |
| xterm      |        5        |          1        |
| kitty      |        3        |          1        |
| ghostty    |        2        |          2        |

 # 100% Screen height
| terminal   | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty  |        3        |          1        |
| foot       |        3        |          1        |
| xterm      |        5        |          1        |
| kitty      |        5        |          1        |
| ghostty    |        3        |          3        |
```

This PR modifies Ghostty to behave like foot and alacritty.

For an improved user experience, we only use the configured multiplier
for
non-precision scrolls. This multiplier now *only applies* to viewport
scrolling
and alternate scroll mode. The default value has been updated to 3.0.

GTK also now supports precision scrolling.
2025-03-02 13:15:45 -08:00
Tim Culverhouse
30a49d0458 fixup! config: default mouse-scroll-multiplier to 3.0 2025-03-02 08:46:11 -06:00
Tim Culverhouse
68a2478317 gtk: enable non-discrete scrolling
Remove the flag which reports all scrolls as discrete scrolls. This
enables precision scrolling in GTK. We have to track a flag between
continuous scroll events.
2025-03-02 08:36:47 -06:00
Tim Culverhouse
c1e87e7122 scroll: only use multiplier for non-precision scrolls
Precision scrolls don't require a multiplier to behave nicely. However,
wheel scrolls feel extremely slow without one. We apply the multiplier
to wheel scrolls only
2025-03-02 08:35:25 -06:00
Tim Culverhouse
6e751d2d7e config: default mouse-scroll-multiplier to 3.0
Make Ghostty behave like other terminals by multiplying scrolls by 3.0.
This only affects when we are reporting arrow keys (alternate scroll
mode) or when we are scrolling the scrollback.
2025-03-02 08:03:09 -06:00
Tim Culverhouse
dbba3f1a60 scroll: don't use multiplier for wheel events
When we report mouse scroll wheel events, they should not be multiplied.
Refactor the scrollCallback to only use a multiplier for viewport or
alternate scroll reports.
2025-03-02 08:02:47 -06:00
Tim Culverhouse
34388ab5df surface: calculate scroll amount directly from yoff/xoff for non-precision scrolls
Calculate the scroll amount for non-precision scrolls as a direct
multiple of yoff. This fixes an issue where Ghostty sends scroll wheel
events (or arrow keys if in alternate scroll mode) that are variable,
dependent on the screen size. I checked multiple terminals, and each
responds to a single wheel click by sending only a single wheel / arrow
key - independent of screen size.

```sh
printf "\x1b[?1049h"
printf "\x1b[?1007h"

cat -v

```

Using the above procedure, with varying screen sizes:

```
 # 50% Screen height
| terminal   | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty  |        3        |          1        |
| foot       |        3        |          1        |
| xterm      |        5        |          1        |
| kitty      |        3        |          1        |
| ghostty    |        2        |          2        |

 # 100% Screen height
| terminal   | arrows keys sent| wheels events sent|
|------------|-----------------|-------------------|
| alacritty  |        3        |          1        |
| foot       |        3        |          1        |
| xterm      |        5        |          1        |
| kitty      |        5        |          1        |
| ghostty    |        3        |          3        |
```

Both ghostty and kitty scale the number of arrow keys sent in proportion
to the screen size. However, when mouse reporting is on, only ghostty
does this.

This commit makes Ghostty behave like foot, and more generally removes
the dependence on screen size.
2025-03-02 08:02:47 -06:00
Mitchell Hashimoto
8721f2ae51 Update iTerm2 colorschemes (#6047)
Upstream revision:
e21d5ffd19
2025-03-01 20:42:22 -08:00
mitchellh
29447b60b3 deps: Update iTerm2 color schemes 2025-03-02 00:13:13 +00:00
Mitchell Hashimoto
e2b5584a8d Add note to configuration that some settings might require a restart (#6033)
This adds a note in the default config to note that may require a
restart. Also adds a note with the window-padding-x and window-padding-y
to indicate a restart will be required to update existing windows.

I ran into this while updating one of the existing values in the default
config file. `window-padding-x` The value defaulted to 2 so figured it
was safe to assume and just uncomment it and try reloading the config.

Doing that doesn't work only restarting will make it take effect for the
main window ( or of course more tricky opening new windows and killing
off the old one )

https://github.com/ghostty-org/ghostty/discussions/6022
2025-03-01 13:57:25 -08:00
Aaron Ogle
e7a9d6a81d Update config template with a note about a restart possibly being required 2025-03-01 13:55:10 -08:00
Jeffrey C. Ollie
b342909e10 GTK: do not check for terminal area focus when setting window title (#6032)
Fixes https://github.com/ghostty-org/ghostty/issues/5940
The mentioned problem occurs because when creating a new tab through the
tab overview we do not have focus on the terminal area widget, it does
not matter if we have a custom title set or not.
I think it is just safe to remove this check from the code. I've tested
the change and I don't really see a valid use case in which we would not
want to set the window title even if we don't have focus on the terminal
area.
2025-03-01 12:10:13 -06:00
Leah Amelia Chen
df62d45b36 gtk: update URLWidget to use zig-gobject (#6042) 2025-03-01 18:40:27 +01:00
Jeffrey C. Ollie
5d5a632a89 gtk: update URLWidget to use zig-gobject
Also move URLWidget to a separate file to cut down on the size of
Surface.zig.
2025-03-01 10:02:16 -06:00
Jeffrey C. Ollie
ed647caa2e apprt/gtk: remove non-ascii characters from resize overlay (#6040)
It is possible that fonts people are using don't contain these
characters as evidenced by users in the Discord.
2025-03-01 08:54:31 -06:00
Maciej Bartczak
a3cd7c6f02 gtk: update the window title when grabbing tab focus 2025-03-01 13:25:25 +01:00
Tristan Partin
0e6c26bbfe apprt/gtk: remove non-ascii characters from resize overlay
It is possible that fonts people are using don't contain these
characters as evidenced by users in the Discord.

Signed-off-by: Tristan Partin <tristan@partin.io>
2025-03-01 00:37:01 -06:00
Mitchell Hashimoto
efc1b10bfd Introduce reset_window_size keybinding and apprt action (#6038)
Related to #6035

This implements the keybind/action portion of #5974 so that this can
have a binding and so that other apprts can respond to this and
implement it this way.
2025-02-28 19:02:10 -08:00
Mitchell Hashimoto
17cae57f51 Introduce reset_window_size keybinding and apprt action
Related to #6035

This implements the keybind/action portion of #5974 so that this can
have a binding and so that other apprts can respond to this and
implement it this way.
2025-02-28 15:31:17 -08:00
Mitchell Hashimoto
c6485b9fd5 "Return to Default Size" implementation (#5974)
## Added Support for "Return To Default Size"

This update introduces support for the **"Return To Default Size"**
feature.

### Fixes  
- Resolves [#1328](https://github.com/ghostty-org/ghostty/issues/1328)

### Screenshots  

| Description | Screenshot |
|------------|------------|
| **Ghostty** | <img width="1084" alt="Screenshot 2025-02-24 at 21 15
38"
src="https://github.com/user-attachments/assets/4657ccdb-9c7a-4884-873c-bbe0f30f9400"
/> |
| **After changing window size** | <img width="1155" alt="Screenshot
2025-02-24 at 21 16 00"
src="https://github.com/user-attachments/assets/9b3931f2-1c4b-4f86-8d56-8892bd5675cc"
/> |
| **Native Terminal App (for reference)** | <img width="630"
alt="Screenshot 2025-02-24 at 21 16 20"
src="https://github.com/user-attachments/assets/ae049931-b74d-4246-a9e7-d9be079b1a24"
/> |
2025-02-28 15:06:24 -08:00
Mitchell Hashimoto
afb154ee5d macos: store default size as computed property 2025-02-28 14:51:56 -08:00
Leah Amelia Chen
5accc069fb gtk: implement quick terminal (#6027) 2025-02-28 23:31:39 +01:00
Mikhail Borisov
8838ebf02a Refactor to use height/width from ghostty configuration 2025-02-28 14:17:46 -08:00
Mikhail Borisov
f73c1a2c59 "Return to Default Size" implementation
Added support for "Return To Default Size"
2025-02-28 14:17:46 -08:00
Mitchell Hashimoto
9681009650 apprt initial_size is sent whenever the grid size changes (#6034)
As noted in the comments, this is so that apprt's can always know what
the default size of a window would be so they can utilize this for
"return to default size" actions.

The initial size shouldn't be treated as a "resize" event and was
already documented as such. Prior to this commit the docs already noted
that the initial size may be sent multiple times but only the first time
during initialization should be used as a resize.

Therefore, this shouldn't impact prior behavior. I've verified this with
the apprts.
2025-02-28 10:43:55 -08:00
Mitchell Hashimoto
b0f1f19da0 apprt initial_size is sent whenever the grid size changes
As noted in the comments, this is so that apprt's can always know what
the default size of a window would be so they can utilize this for
"return to default size" actions.

The initial size shouldn't be treated as a "resize" event and was
already documented as such. Prior to this commit the docs already noted
that the initial size may be sent multiple times but only the first time
during initialization should be used as a resize.

Therefore, this shouldn't impact prior behavior. I've verified this with
the apprts.
2025-02-28 10:07:36 -08:00
Leah Amelia Chen
a85651fe4f gtk: implement quick terminal
Using `gtk4-layer-shell` still seems like the path of least resistance,
and to my delight it pretty much Just Works. Hurrah!

This implementation could do with some further polish (e.g. animations,
which can be implemented via libadwaita's animations API, and global
shortcuts), but as a MVP it works well enough.

It even supports tabs!

Fixes #4624.
2025-02-28 18:04:42 +01:00
Mitchell Hashimoto
1cfe7027e5 Fix Terminal Inspector option turns inactive if toggled in the Quick Terminal (#6024)
Fixed: [2475](https://github.com/ghostty-org/ghostty/issues/2475)

The problem actually existed because of the responder chain, as
previously pointed out in the report (thanks to @mitchellh).

When we first click on Toggle Terminal Inspector:
* the responder chain goes to _toggleTerminalInspector_
(_SurfaceView_AppKit_ implementation).
When we click the second toggleTerminalInspector:
* it tries to find the next responder, but the one available is
_TerminalController_. (if we remove this method from there, the bug will
reproduce even without quick mode)

**Problem**:
TerminalController not available during quick terminal, so there's no
way to toggle inspector
**Solution**:
We add toggleTerminalInspector to the _QuickTerminalController_:
selector, as we did with other similar methods.
2025-02-28 07:09:00 -08:00
Mikhail Borisov
744240c009 Fix Terminal Inspector option turns inactive if toggled in the Quick Terminal 2025-02-28 00:38:29 +01:00
Mitchell Hashimoto
ef88d1cba9 feat: respect maximize config on macOS (#5962)
Resolve #5928
2025-02-27 15:25:17 -08:00
Aaron Ruan
5a5478abe1 feat: respect maximize config on macOS
Signed-off-by: Aaron Ruan <i@ar212.com>
2025-02-27 15:10:39 -08:00
Qwerasd
a1437e5579 fix(Metal): force a full rebuild in setFontGrid (#6008)
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.

Fixes #2731
2025-02-27 09:43:47 -05:00
Jeffrey C. Ollie
31df9d5576 gtk: work around oversized drag handle for GtkPaned (#6000)
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-02-27 08:42:17 -06:00
Jeffrey C. Ollie
ef7f8cb3da gtk: use language-neutral arrows for resize overlays (#6013)
The meaning of "c" and "r" can be somewhat cryptic to non-native English
speakers as it may not be immediately obvious that "c" stands for
"columns", and "r" stands for "rows". I propose replacing them with
left-right and up-down double-headed arrows that convey the same
meaning, but in a truly language-neutral manner.

Related to #2357
2025-02-27 08:25:12 -06:00
David Wales
63ea1ab32e 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-02-27 19:17:37 +11:00
Leah Amelia Chen
cdfa028521 gtk: use language-neutral arrows for resize overlays
The meaning of "c" and "r" can be somewhat cryptic to non-native English
speakers as it may not be immediately obvious that "c" stands for "columns",
and "r" stands for "rows". I propose replacing them with left-right and
up-down double-headed arrows that convey the same meaning, but in a
truly language-neutral manner.

Related to #2357
2025-02-27 08:38:06 +01:00
Jeffrey C. Ollie
c7938af7be gtk: convert Split.zig to gobject (#6012) 2025-02-26 22:38:51 -06:00
Jeffrey C. Ollie
78a98e01fc gtk: convert Split.zig to gobject 2025-02-26 19:22:02 -06:00
Jeffrey C. Ollie
d85ed8275e gtk: convert cgroup operations to gobject (#6009) 2025-02-26 18:28:53 -06:00
Jeffrey C. Ollie
6581b9cf41 gtk: convert cgroup operations to gobject 2025-02-26 16:26:44 -06:00
Leah Amelia Chen
12ba5d89f0 apprt/gtk: subscribe to AdwStyleManager::dark for ColorScheme (#6007) 2025-02-26 20:38:50 +01:00
Qwerasd
16a61c43dd 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-02-26 14:11:05 -05:00
Tristan Partin
acbb1d3bd4 apprt/gtk: subscribe to AdwStyleManager::dark for ColorScheme
Signed-off-by: Tristan Partin <tristan@partin.io>
2025-02-26 13:03:12 -06:00
Jeffrey C. Ollie
dec14f3096 gtk: switch dbus operations in src/apprt/gtk/App.zig to zig-gobject (#5996) 2025-02-26 11:17:47 -06:00
Jeffrey C. Ollie
5fdb732798 gtk: switch dbus operations in src/apprt/gtk/App.zig to zig-gobject 2025-02-25 22:52:57 -06:00
Leah Amelia Chen
4e5e4a7c2f gtk: switch clipboard confirmation to zig-gobject and blueprints (#5968) 2025-02-25 23:28:56 +01:00
Mitchell Hashimoto
c068390634 Fix elvish sudo integration and update documentation (#5992)
Elvish integration is broken when running `sudo`, because the function
`sudo-with-terminfo` uses `command` command, which is not implemented in
Elvish. Changing it to `external` command should fix possible error when
bypassing aliases, functions and builtins, like `command` does in Bash.
Discussion about this issue: #5979 

Also I updated documentation about Elvish integration to provide fix
when using `use ghostty-integration` outside of Ghostty with `rc.elv`.
2025-02-25 13:57:31 -08:00
Yappaholic
3cac06a70a Fix elvish sudo integration and update documentation 2025-02-25 23:17:00 +03:00
Jeffrey C. Ollie
62dcddb315 gtk: fix missing defer 2025-02-25 13:46:50 -06:00
Jeffrey C. Ollie
0638eca633 gtk: use versioned directories to store blueprint files 2025-02-25 13:30:44 -06:00
Jeffrey C. Ollie
c6b049b12b gtk: fix properties on close confirmation text view 2025-02-25 13:30:44 -06:00
Jeffrey C. Ollie
3f847de964 gtk: switch clipboard confirmation to zig-gobject and blueprints
Note that for Debian 12, the blueprints must be compiled on a distro
with a newer version of `blueprint-compiler` and the raw UI XML
committed to git. Debian 12 includes `blueprint-compiler` 0.6.0 which
doesn't support compiling `adw.MessageDialog` even though the version of
`libadwaita` supports it.
2025-02-25 13:30:44 -06:00
Mitchell Hashimoto
9972eeb673 Create a snap packaging subsystem (#5987)
This team is responsible for snap packaging.
2025-02-25 10:54:29 -08:00
Mitchell Hashimoto
166362d349 Create a snap packaging subsystem
This team is responsible for snap packaging.
2025-02-25 10:21:55 -08:00
Mitchell Hashimoto
0b7df7511a Fix empty keybind setting (#5977)
Fixes https://github.com/ghostty-org/ghostty/issues/5936.

Extracts the default keybind setting to an `init` function. Add logic to
call `init` in `parseIntoField` if it is defined for the type.
2025-02-25 09:06:11 -08:00
David Mo
df9de1523c fix test 2025-02-25 11:53:01 -05:00
Leah Amelia Chen
22f3e60dcf gtk: convert window actions to use zig-gobject (#5978) 2025-02-25 16:44:09 +01:00
David Mo
af2d710000 rename setToDefault to init 2025-02-25 10:15:58 -05:00
Jeffrey C. Ollie
3d08b1c4aa gtk: cast to application window 2025-02-25 08:19:52 -06:00
David Mo
22d99f2533 add test for setToDefault 2025-02-24 23:39:01 -05:00
David Mo
8fadb54e65 set default keybinds when parsing empty keybind config 2025-02-24 23:31:49 -05:00
David Mo
0321aec68f create default keybinds function 2025-02-24 23:27:49 -05:00
Jeffrey C. Ollie
d284146621 gtk: convert window actions to use zig-gobject 2025-02-24 22:07:16 -06:00
Mitchell Hashimoto
92340f8fb0 fix(macos): prevent performing newTab shortcut on QuickTerminalWindow… (#5955)
Fixes #5939
2025-02-24 15:32:39 -08:00
McNight
b4349d3226 fix(macos): make showNoNewTabAlert method private #5939 2025-02-25 00:18:02 +01:00
McNight
1254c6b981 fix(macos): address MR feedback #5939 2025-02-25 00:17:01 +01:00
Mitchell Hashimoto
ce8bfe45ed Docs change: Note instead of simple text and Removing two times showing info. (#5920)
In docs option reference, for macos-icon info. It is showing two times
similar thing. And Note looks good instead of just text because it wont
work without `custom-style`. So a Big Note is a good highlight.
2025-02-24 09:41:26 -08:00
Jeffrey C. Ollie
e1e1539e4f kitty: delete stray log line (#5969) 2025-02-24 11:30:22 -06:00
Jeffrey C. Ollie
9bcf554139 gtk: instruct users to install blueprint-compiler (#5970)
There's been *far* too many people who aren't aware of the new
dependency, and that is partly our fault: a "FileNotFound" error is
quite obtuse, unless you religiously follow every PR and every commit
made to the repository. Instead of shepherding everyone who runs into
this manually, we should offer better signposting.
2025-02-24 11:25:56 -06:00
Leah Amelia Chen
1dc375dd0e gtk: instruct users to install blueprint-compiler
There's been *far* too many people who aren't aware of the new dependency,
and that is partly our fault: a "FileNotFound" error is quite obtuse,
unless you religiously follow every PR and every commit made to the
repository. Instead of shepherding everyone who runs into this manually,
we should offer better signposting.
2025-02-24 18:10:18 +01:00
Jeffrey C. Ollie
f4f36a9a98 kitty: delete stray log line 2025-02-24 11:05:37 -06:00
Mitchell Hashimoto
71ae51b4b3 kitty images: add delete by range operations (#5957)
Fixes #5937

Implement [deleting Kitty image
ranges](https://sw.kovidgoyal.net/kitty/graphics-protocol/#deleting-images).
2025-02-24 07:14:11 -08:00
Jeffrey C. Ollie
61f41e5c01 nix: include libxml2 in nativeBuildInputs for xmllint (#5959) 2025-02-23 15:56:08 -06:00
Jeffrey C. Ollie
2ef042978d nix: include libxml2 in nativeBuildInputs for xmllint 2025-02-23 15:17:18 -06:00
Leah Amelia Chen
a52c603f16 gtk: ensure that the content scale is always positive (#5954) 2025-02-23 21:27:09 +01:00
Jeffrey C. Ollie
da10269d3f gtk: handle other nonsensical values returned by gtk_widget_get_scale_factor 2025-02-23 13:18:00 -06:00
Jeffrey C. Ollie
ac7029256a gtk: better document what to do if gtk-xft-dpi <= 0 2025-02-23 13:04:47 -06:00
Jeffrey C. Ollie
995959dce4 kitty images: add delete by range operations
Fixes #5937

Implement [deleting Kitty image ranges](https://sw.kovidgoyal.net/kitty/graphics-protocol/#deleting-images).
2025-02-23 12:57:24 -06:00
McNight
aa4aaa200f fix(macos): prevent performing newTab shortcut on QuickTerminalWindow #5939 2025-02-23 19:34:27 +01:00
Jeffrey C. Ollie
bc2acdd060 gtk: ensure that the content scale is always positive
Fixes #5927

This doesn't fix the underlying reason that GTK sometimes reports
content scales as negative. If GTK reports a negative scale, we ignore
that and use 1.0 for the scale.
2025-02-23 11:02:59 -06:00
Mitchell Hashimoto
2f63f840de terminal: increase CSI max params to 24 to accept Kakoune sequence (#5949)
See #5930

Kakoune sends a real SGR sequence with 17 parameters. Our previous max
was 16 so we threw 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-02-23 07:12:38 -08:00
Tristan Partin
eaeb6a620f gtk: switch menus to use blueprints instead of raw builder ui (#5944) 2025-02-22 23:30:56 -06:00
Tristan Partin
65c65b9c97 gtk: fix menu separator colors (again) (#5945) 2025-02-22 23:30:30 -06:00
Mitchell Hashimoto
dad2cf887b add macos default config path to manpages (#5942)
This should resolve #5938.

I tested this locally using `zig build -D=emit-docs=true` and confirmed
that the mdgen process provided the new manpages in
`zig-out/share/man/man*`, respectively.

Let me know if this needs any changes, I tried to keep this as similar
as possible to the existing manpages (both format and content).
2025-02-22 20:53:34 -08:00
Mitchell Hashimoto
22c506b03e 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-02-22 20:43:44 -08:00
Jeffrey C. Ollie
427da79a02 gtk: fix menu separator colors (again) 2025-02-22 20:23:16 -06:00
Jeffrey C. Ollie
a8b6b96fbd gtk: switch menus to use blueprints instead of raw builder ui 2025-02-22 20:21:41 -06:00
Tristan Partin
e7cbb7fd16 gtk: update ResizeOverlay for zig-gobject (#5941)
Also switch to a "DerivedConfig" model so that we aren't referring to a
global copy of the config.
2025-02-22 20:07:20 -06:00
Jeffrey C. Ollie
bdf0f27d1a gtk: fix the alignment of the context menu (#5943)
This aligns the top left of the context menu with the right-click
location, rather than the top center.
2025-02-22 18:37:41 -06:00
Jeffrey C. Ollie
0af256b57a gtk: fix the alignment of the context menu
This aligns the top left of the context menu with the right-click
location, rather than the top center.
2025-02-22 18:23:30 -06:00
taylrfnt
398add17f1 actually change it in both places 2025-02-22 18:00:37 -06:00
taylrfnt
eec150d4cd mention default loc precendence over XDG 2025-02-22 17:58:05 -06:00
taylrfnt
573fe7348b add macos default config path to manpages 2025-02-22 17:46:30 -06:00
Jeffrey C. Ollie
f1134640c5 gtk: update ResizeOverlay for zig-gobject
Also switch to a "DerivedConfig" model so that we aren't referring to a
global copy of the config.
2025-02-22 17:42:09 -06:00
Jeffrey C. Ollie
5131f8a71c GTK: propagate config updates to the GTK apprt surfaces (#5866)
This will be needed to the convert GTK apprt surfaces to a DerivedConfig
model rather than accessing the global config stored in the app. It's a
no-op for now, but I have a PR in the works to update the resize overlay
that will take advantage of this.
2025-02-22 17:19:13 -06:00
Jeffrey C. Ollie
867982a2ff Gtk: change title prompt (#5905)
This PR implements the title change functionality for the GTK app and
closes https://github.com/ghostty-org/ghostty/issues/5769

Demo:


https://github.com/user-attachments/assets/cad611b3-b7bf-40ac-8d0f-11d2095fe525

For now I came up with a basic UI that i believe is sufficient, but I'm
open to feedback and happy to iterate on it further.

https://github.com/ghostty-org/ghostty/issues/5769#issuecomment-2660341107
- Regarding Mitchell's comment I checked Gnome Console and I could not
find a similar feature.
2025-02-22 17:00:40 -06:00
Jeffrey C. Ollie
bde5b963d0 gtk: fix Builder api changes 2025-02-22 16:39:12 -06:00
Maciej Bartczak
5d80db2ef8 code review: fix log format 2025-02-22 16:39:12 -06:00
Jeffrey C. Ollie
32a62ff862 snap: add libxml2-utils (for xmllint) to snap 2025-02-22 16:33:22 -06:00
Jeffrey C. Ollie
d4bcac0150 snap: add blueprint-compiler to snap 2025-02-22 16:33:22 -06:00
Jeffrey C. Ollie
51dc1e2e8c gtk: fix typos 2025-02-22 16:33:22 -06:00
Jeffrey C. Ollie
4f3c4037aa gtk: get 'Change Title' working with older distros 2025-02-22 16:33:20 -06:00
Maciej Bartczak
7c19dd5a33 format the blueprint file using blueprint-compiler format 2025-02-22 16:32:52 -06:00
Maciej Bartczak
8758295647 code review:
- move responses definition to the blueprint, use translatable strings
- minor changes in the response callback
2025-02-22 16:32:52 -06:00
Maciej Bartczak
1ee8dfc99c code review:
- remove unnecessary @ptrCast
- set the default focus to the text entry field
2025-02-22 16:32:52 -06:00
Maciej Bartczak
cd287b4161 - remove the unused dialog context struct
- set the current title in the input buffer
- fix formatting
2025-02-22 16:32:50 -06:00
Maciej Bartczak
454a53b3f1 code review:
- remove the menu entry defined in code
2025-02-22 16:32:23 -06:00
Maciej Bartczak
6189f5d09e code review:
- use blueprint for the dialog content
- use zig-gobject bindings
- make the enum values lowercase
2025-02-22 16:31:32 -06:00
Maciej Bartczak
dcd17c6ac4 set the ok widget to be activated by default 2025-02-22 16:30:57 -06:00
Maciej Bartczak
3542778d84 free the terminal title when destroy is run 2025-02-22 16:30:57 -06:00
Maciej Bartczak
95fc5ad1e9 remove outdated comment 2025-02-22 16:30:57 -06:00
Maciej Bartczak
cc9c45de2a fix the edge case when user tries to revert the title to default and hasn't set a title before 2025-02-22 16:30:57 -06:00
Maciej Bartczak
5e9908af27 make the change of the title persistent & allow the user to restore to a default one 2025-02-22 16:30:56 -06:00
Maciej Bartczak
6b75ca40ca Implement a prompt that allows the user to set the title 2025-02-22 16:30:55 -06:00
Jeffrey C. Ollie
3f715c296a gtk: update menus to use popovers and builder ui files (#5781)
Menus (context menus and the window hamburger menu) now use popovers and
are defined using GTK builder UI files. This is a bit more "modern" and
reduces the amount of code to define menus.
2025-02-22 14:54:06 -06:00
Mitchell Hashimoto
e307f1a373 Allow whitespace in ColorList values (#5925)
The following formats are now all valid:

```zig
macos-icon-screen-color = #00FF00,#FF1000,#00FFFF
macos-icon-screen-color = #00FF00, #FF1000, #00FFFF
macos-icon-screen-color = #00FF00 ,#FF1000 ,#00FFFF
macos-icon-screen-color =  #00FF00 , #FF1000  , #00FFFF
```

Fixes #5918
2025-02-22 12:18:54 -08:00
Mitchell Hashimoto
726ac36612 Add support for whitespace in color and palette parsing (#5926)
Resolves #5921
2025-02-22 07:25:18 -08:00
Mitchell Hashimoto
ce62b5cc5e Clarify configuration of macos-icon-screen-color's gradient (#5923)
From #5863, cc @vollink.

I'm not sure what the hard-wrap limit for comments is (70?), so I've
tried to match the paragraph I've edited.
2025-02-22 07:13:08 -08:00
Bryan Lee
1b6b029e0d Add test cases for whitespace handling for ColorList 2025-02-22 22:11:35 +08:00
Bryan Lee
2383e4d90d Add support for whitespace in color and palette parsing 2025-02-22 22:05:05 +08:00
Bryan Lee
7c6375f744 Allow whitespace after commas in ColorList values 2025-02-22 20:53:27 +08:00
Kat
6770ad3736 Clarify configuration of macos-icon-screen-color's gradient. 2025-02-22 17:14:08 +11:00
Ronit Gandhi
a262da92bf using only note instead of both for docs. 2025-02-22 10:49:27 +05:30
Mitchell Hashimoto
870b74f4da macOS: Fix new window focus when created from quick terminal (#5834)
## Root Cause

The issue has two aspects:

1. The window creation process didn't explicitly force focus on the new
window after showing it.
2. More fundamentally, we were relying on `NSApp.isActive` to determine
whether to activate the application, which is problematic because:
- When creating a window from quick terminal, the application is already
"active" but this active state is owned by the quick terminal
- The [`NSApp.isActive`
check](4cfe5522db/macos/Sources/Features/Terminal/TerminalManager.swift (L100))
doesn't accurately reflect our intent - creating a new window is an
explicit user action that should always result in that window gaining
focus

## Solution

Removing the `NSApp.isActive` check.

```swift
// Before
if !NSApp.isActive {
    NSApp.activate(ignoringOtherApps: true)
}

// After
NSApp.activate(ignoringOtherApps: true)
```

Fixes #5688
2025-02-21 15:50:00 -08:00
Mitchell Hashimoto
c1fb9a33f7 Fix barely visible new tab button on macOS (#5897)
Resolve #5894 and #4288
2025-02-21 15:47:33 -08:00
Aaron Ruan
4291e1c5d7 fix: use surfaceConfig.backgroundColor to determine if the theme is light or dark
Signed-off-by: Aaron Ruan <i@ar212.com>
2025-02-21 15:32:24 -08:00
Mitchell Hashimoto
b5ecd7b6be Update libxev to use dynamic backend, support Linux configurability (#5916)
Related to #3224

Previously, Ghostty used a static API for async event handling: io_uring
on Linux, kqueue on macOS. This commit changes the backend to be dynamic
on Linux so that epoll will be used if io_uring isn't available, or if
the user explicitly chooses it.

This introduces a new config `async-backend` (default "auto") which can
be set by the user to change the async backend in use. This is a
best-effort setting: if the user requests io_uring but it isn't
available, Ghostty will fall back to something that is and that choice
is up to us.

Basic benchmarking both in libxev and Ghostty (vtebench) show no
noticeable performance differences introducing the dynamic API, nor
choosing epoll over io_uring.

For platforms that don't have multiple choices (currently macOS), the
dynamic API is literally the static API so there should be no
performance or behavioral changes whatsoever.
2025-02-21 15:18:59 -08:00
Mitchell Hashimoto
d532a6e260 Update libxev to use dynamic backend, support Linux configurability
Related to #3224

Previously, Ghostty used a static API for async event handling: io_uring
on Linux, kqueue on macOS. This commit changes the backend to be dynamic
on Linux so that epoll will be used if io_uring isn't available, or if
the user explicitly chooses it.

This introduces a new config `async-backend` (default "auto") which can
be set by the user to change the async backend in use. This is a
best-effort setting: if the user requests io_uring but it isn't
available, Ghostty will fall back to something that is and that choice
is up to us.

Basic benchmarking both in libxev and Ghostty (vtebench) show no
noticeable performance differences introducing the dynamic API, nor
choosing epoll over io_uring.
2025-02-21 15:04:37 -08:00
Jeffrey C. Ollie
2697061e5b gtk: fix comment in Window.updateConfig 2025-02-18 23:28:10 -06:00
Jeffrey C. Ollie
d1fa933006 gtk: forward config updates to GTK apprt surfaces 2025-02-18 23:18:47 -06:00
Jeffrey C. Ollie
2d5a07c795 gtk: fix build on debian 12 2025-02-18 17:10:54 -06:00
Jeffrey C. Ollie
b3f994a9d2 gtk: use builder ui files and popovers for menus 2025-02-18 17:10:51 -06:00
Jeffrey C. Ollie
38908e0126 gtk: apply all window appearance changes in syncAppearance (#5404)
The GTK side of appearance code is kind of a mess with several different
functions all having the responsibility of interacting with each other
and setting the appropriate window appearance. It should solely be the
responsibility of the `syncAppearance` function to apply appearance
changes, with other callbacks/functions calling it instead: much like
what we already do for the macOS apprt.

~~I also took the time to refactor the libadwaita version checks since
calling `versionAtLeast(0, 0, 0)` does get old after a while. Now almost
all checks are given human-readable names and contributors need not
memorize what the relevant version checks all are.~~ Moved to another PR
2025-02-18 16:36:07 -06:00
Leah Amelia Chen
8eaa901aec config: update reload ability information for several keys
Several keys are now able to affect existing windows (especially
window-decoration, whose config documentation got a greater overhaul)
2025-02-18 10:09:41 +01:00
Leah Amelia Chen
aa2dbe2919 gtk: apply all window appearance changes in syncAppearance
The GTK side of appearance code is kind of a mess with several different
functions all having the responsibility of interacting with each other
and setting the appropriate window appearance. It should solely be the
responsibility of the `syncAppearance` function to apply appearance
changes, with other callbacks/functions calling it instead: much like
what we already do for the macOS apprt.
2025-02-18 10:09:41 +01:00
Bryan Lee
c9f8732e5c Fix new window focus when quick terminal is open 2025-02-18 10:50:01 +08:00
Jeffrey C. Ollie
da32534e8a gtk: fix closing window when last tab is closed (#5837)
Causes windows to not close and leave behind a "corpse" if they are not
the last window or we are running in single instance mode.

![image](https://github.com/user-attachments/assets/5f6860c2-7807-4ff1-9cad-d32dd4bc348b)
2025-02-17 11:37:51 -06:00
Jeffrey C. Ollie
3dbbbdee0b gtk: fix closing window when last tab is closed 2025-02-17 11:15:16 -06:00
Mitchell Hashimoto
16c6903706 Vertically center title when macos-titlebar-style = tabs (#5777)
Fix #3868 
<img width="812" alt="Frame 14"
src="https://github.com/user-attachments/assets/1fe78a13-ed8f-46e5-b4d4-69ecefdbdc22"
/>
2025-02-17 07:44:22 -08:00
Mitchell Hashimoto
3253df3d54 Equalize Splits invalid keybind (#5646)
refer to #4007

there are also quite a few posts about this in the discord help section

Basically for some reason for the equal key shift is not a typical
modifier where it would be the original key + shift modifier but it
actually just changes equal to respond as the plus key this is the only
key on the keyboard I can see exhibit this behavior

it seems all 3 major operating systems report the key this way (mac,
linux, and windows)
2025-02-17 07:41:42 -08:00
Aaron Ruan
830a117555 fix: vertically center toolbar title more accurately
Signed-off-by: Aaron Ruan <i@ar212.com>
2025-02-17 07:30:14 -08:00
rhodes-b
768b0a79cb dont use shift+equal combo 2025-02-17 07:26:32 -08:00
Mitchell Hashimoto
429c8ab277 fix: [snap] Don't set GDK_PIXBUF_MODULE_FILE, it causes the icon loader (#5820)
This fixes loading the tab-new-symbolic icon in the snap package
2025-02-16 20:45:22 -08:00
Ken VanDine
76bd002aa4 fix: [snap] Don't set GDK_PIXBUF_MODULE_FILE, it causes the icon loader 2025-02-16 23:18:16 -05:00
Jeffrey C. Ollie
246f4baf7c gtk: rename notebook to TabView and switch to gobject (#5795) 2025-02-16 17:43:46 -06:00
Mitchell Hashimoto
1013ba63ee [macOS] feat: Add "Split Left" and "Split Up" actions to menubar (#5807)
Fixes #5779
2025-02-16 12:37:11 -08:00
mbrown379
b1df97b33f Add Split Left and Split Up to menu 2025-02-16 15:14:02 -05:00
Jeffrey C. Ollie
25b93ceb55 gtk: disable shortcuts in tab view 2025-02-16 13:56:15 -06:00
Jeffrey C. Ollie
497a1b6f8f gtk: prevent double free when closing window 2025-02-16 13:56:15 -06:00
Jeffrey C. Ollie
bf7e9603d2 gtk: rename notebook to TabView and switch to gobject 2025-02-16 13:56:15 -06:00
Mitchell Hashimoto
2e7ed98dfd build: fix colorscheme update (#5797) 2025-02-16 08:39:39 -08:00
Jeffrey C. Ollie
36e6ed3339 build: fix colorscheme update 2025-02-15 23:39:40 -06:00
Jeffrey C. Ollie
9a5bc65034 gtk: fix building on Debian 12 (#5791)
`std.debug.assert(x)` _is not_ the same as `if (!x) unreachable` because
the function call is not `inline`. Since it's not inline the Zig
compiler will try to compile any code that might otherwise be
unreachable.

Also, added a CI test that compiles Ghostty in a Debian 12 container to
ensure that regressions do not happen.
2025-02-15 18:31:39 -06:00
Mitchell Hashimoto
8ad2ae6ab4 Update iTerm2 colorschemes (#5793)
Upstream revision:
efb1bb1843
2025-02-15 16:26:58 -08:00
mitchellh
fe11efff63 deps: Update iTerm2 color schemes 2025-02-16 00:13:20 +00:00
Jeffrey C. Ollie
b0d68324a6 gtk: fix multiple build args in docker build 2025-02-15 18:11:42 -06:00
Jeffrey C. Ollie
fb35d10981 gtk: add Zig version as arg to Debian 12 build 2025-02-15 18:01:07 -06:00
Jeffrey C. Ollie
191b19f9a5 gtk: add debian build to list of required checks 2025-02-15 17:58:25 -06:00
Jeffrey C. Ollie
c7b3cbd397 gtk: only test Debian 12 builds on amd64 2025-02-15 17:57:19 -06:00
Jeffrey C. Ollie
0ce1342263 gtk: fix building on Debian 12
`std.debug.assert(x)` _is not_ the same as `if (!x) unreachable`
because the function call is not `inline`. Since it's not inline the
Zig compiler will try to compile any code that might otherwise be
unreachable.

Also, added a CI test that compiles Ghostty in a Debian 12 container to
ensure that regressions do not happen.
2025-02-15 16:53:53 -06:00
Mitchell Hashimoto
6d8db4b380 [macOS] feat: Add setting to hide icon from dock/cmd-tab (#5122)
Resolves #4538 

Adds boolean configuration option `macos-hidden` which toggles the
NSApp's activation policy appropriately on config change.
2025-02-15 11:00:32 -08:00
Albert Dong
d7a82f212a Add setting to hide icon from dock/cmd-tab 2025-02-15 10:45:27 -08:00
Mitchell Hashimoto
aeccbd266a Added snap packaging (#3931)
Added snap packaging, fixes #3153

If whoever registered the name in the snap store could add me as a
collaborator, I can handle getting it released in the store, setup
automated builds, and request the necessary classic permissions in the
store.
2025-02-15 09:24:36 -08:00
Thom Dickson
ac9f8ba9b1 wip: allow directional split movement 2025-02-15 10:14:21 -06:00
Mitchell Hashimoto
baa47ff24e ci: test requires build-snap 2025-02-15 07:24:25 -08:00
Mitchell Hashimoto
818bc779b3 apprt/gtk: unset snap env vars 2025-02-15 07:22:21 -08:00
Mitchell Hashimoto
88a6b542b3 ci: move snap testing into our big test workflow 2025-02-15 07:20:55 -08:00
Mitchell Hashimoto
494273cf08 ci: snap workflow requires git history 2025-02-15 07:10:31 -08:00
Mitchell Hashimoto
03d1240999 nix: use snapcraft only on Linux 2025-02-15 07:07:23 -08:00
Ken VanDine
b551e106a8 Comment out refresh-mode, the store rejects this. Needs fixing in
review-tools
2025-02-15 07:06:40 -08:00
Ken VanDine
0c3b873dde Merge remote-tracking branch 'origin/add_snap_package' into add_snap_package 2025-02-15 07:06:40 -08:00
Ken VanDine
d3623393a6 More environment handling to ensure reliability across distros 2025-02-15 07:06:40 -08:00
Ken VanDine
2adee4290a Improved rpath handling for ghostty 2025-02-15 07:06:40 -08:00
Ken VanDine
94e2982d4b Allow snap to refresh while running 2025-02-15 07:06:40 -08:00
Ken VanDine
238b0faf5c Simplified setting snap version 2025-02-15 07:06:40 -08:00
Ken VanDine
927f626d9a Merge remote-tracking branch 'upstream/main' into add_snap_package 2025-02-15 07:06:40 -08:00
Ken VanDine
bd6a133e95 Updated stage packages 2025-02-15 07:06:40 -08:00
Ken VanDine
1c41cf236f Merge branch 'ghostty-org:main' into add_snap_package 2025-02-15 07:06:40 -08:00
Ken VanDine
a831df903d Merge branch 'ghostty-org:main' into add_snap_package 2025-02-15 07:06:40 -08:00
Ken VanDine
ff5c1001c6 Per PR review feedback, this is the more "ziggy" way of doing the check for environment variable. 2025-02-15 07:06:40 -08:00
Ken VanDine
e4cf81c2ba Clean up environment variable while launching the shell 2025-02-15 07:06:40 -08:00
Ken VanDine
cb5379ab1d Unset environment varies set by the snap 2025-02-15 07:06:40 -08:00
Ken VanDine
7e5c57a848 Only export XDG_CONFIG_HOME and XDG_DATA_HOME if they aren't already set 2025-02-15 07:06:40 -08:00
Ken VanDine
5841a4f958 Stage libglib2.0-0t64 to insure we don't mix in the host's lib 2025-02-15 07:06:40 -08:00
Ken VanDine
725488e1a2 Improved environment handling to ensure the snap will work across
distros and unset all SNAP environment variables that could leak at
runtime
2025-02-15 07:06:40 -08:00
Ken VanDine
0acf82bb9c Use patch-rpath which improves our cross distro support 2025-02-15 07:06:40 -08:00
Mitchell Hashimoto
9944fd5958 ci: temporary apt installs required for namespace 2025-02-15 07:06:40 -08:00
Ken VanDine
e7d4daa7c1 Removed duplicated stage-packages 2025-02-15 07:06:40 -08:00
Ken VanDine
5de0e775cb Don't stage shells 2025-02-15 07:06:40 -08:00
Ken VanDine
301fdff58f enable-patchelf is more repliable for classic snaps 2025-02-15 07:06:40 -08:00
Ken VanDine
99c7abb43a Set GHOSTTY_RESOURCES_DIR 2025-02-15 07:06:40 -08:00
Ken VanDine
a85de40710 Exit with error if building for unsupported arch 2025-02-15 07:06:40 -08:00
Ken VanDine
e174fb2748 no-patchelf for DRI and tidy up the mesa bits 2025-02-15 07:06:40 -08:00
Ken VanDine
403eab2cf0 Stage gnome-text-editor to open configuration, this makes it more
reliable across more distros as a classic snap.
2025-02-15 07:06:40 -08:00
Ken VanDine
bdafc2227c Drop patchelf 2025-02-15 07:06:40 -08:00
Ken VanDine
c9cafd3051 Enable patch-elf for libs part 2025-02-15 07:06:40 -08:00
Ken VanDine
48f94e6fcc Stage more depends to ensure we aren't getting leaks from the host 2025-02-15 07:06:40 -08:00
Ken VanDine
43b2e43a11 EGL fixes, ensure necessary env variables are set to isolate
dependencies from the host
2025-02-15 07:06:40 -08:00
Ken VanDine
8dffe3450c CRAFT_TARGET_ARCH is deprecated, use CRAFT_ARCH_BUILD_FOR 2025-02-15 07:06:40 -08:00
Ken VanDine
ae953b5f10 Ensure LD_LIBRARY_PATH is set appropriately 2025-02-15 07:06:40 -08:00
Mitchell Hashimoto
c7635201ab Add snap to nix, add arm64 builders 2025-02-15 07:06:40 -08:00
Ken VanDine
c35ca1e87f Set a more meaningful version for the snap 2025-02-15 07:06:40 -08:00
Ken VanDine
d06d6796c5 Changed shebang in launcher script 2025-02-15 07:06:40 -08:00
Ken VanDine
e6c9dc7040 Only run snap workflow on push and PR 2025-02-15 07:06:40 -08:00
Ken VanDine
5d0dde57f9 Don't stage shells 2025-02-15 07:06:40 -08:00
Ken VanDine
d0108416d0 enable-patchelf is more repliable for classic snaps 2025-02-15 07:06:40 -08:00
Ken VanDine
71297870cf Set GHOSTTY_RESOURCES_DIR 2025-02-15 07:06:40 -08:00
Ken VanDine
fcde494440 Install bash-completion 2025-02-15 07:06:40 -08:00
Ken VanDine
b7bd8444c7 Exit with error if building for unsupported arch 2025-02-15 07:06:40 -08:00
Ken VanDine
2b2b3c5b3b Set source-type for launcher dir 2025-02-15 07:06:40 -08:00
Ken VanDine
818c81282b Added snap build workflow 2025-02-15 07:06:40 -08:00
Ken VanDine
f0842c5599 Added snap packaging 2025-02-15 07:06:40 -08:00
Jeffrey C. Ollie
2d0940f6ae gtk: require libadwaita (#5749)
This commit removes support for building without libadwaita. (Y'all knew
that I just had this sitting in my back pocket). This will need some
serious review to ensure that we haven't lost any functionality.
2025-02-15 09:04:11 -06:00
Tim Culverhouse
f1f1120749 termio: use modified backend (#5776)
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-02-14 22:57:50 -06:00
Mitchell Hashimoto
c1ff382e97 core: add env config option (#5309)
Fixes #5257

Specify environment variables to pass to commands launched in a terminal
surface. The format is `env=KEY=VALUE`.

`env = foo=bar`
`env = bar=baz`

Setting `env` to an empty string will reset the entire map to default
(empty).

`env =`

Setting a key to an empty string will remove that particular key and
corresponding value from the map.

`env = foo=bar`
`env = foo=`

will result in `foo` not being passed to the launched commands. Setting
a key multiple times will overwrite previous entries.

`env = foo=bar`
`env = foo=baz`

will result in `foo=baz` being passed to the launched commands.

These environment variables _will not_ be passed to commands run by
Ghostty for other purposes, like `open` or `xdg-open` used to open URLs
in your browser.
2025-02-14 20:55:51 -08:00
Tim Culverhouse
29f25ae474 termio: prevent responses to non-query OSC 21 sequences (#5770)
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-02-14 22:50:03 -06:00
Jeffrey C. Ollie
c7971b562e core: add env config option
Fixes #5257

Specify environment variables to pass to commands launched in a terminal
surface. The format is `env=KEY=VALUE`.

`env = foo=bar`
`env = bar=baz`

Setting `env` to an empty string will reset the entire map to default
(empty).

`env =`

Setting a key to an empty string will remove that particular key and
corresponding value from the map.

`env = foo=bar`
`env = foo=`

will result in `foo` not being passed to the launched commands.
Setting a key multiple times will overwrite previous entries.

`env = foo=bar`
`env = foo=baz`

will result in `foo=baz` being passed to the launched commands.

These environment variables _will not_ be passed to commands run by Ghostty
for other purposes, like `open` or `xdg-open` used to open URLs in your
browser.
2025-02-14 20:50:01 -08:00
Tim Culverhouse
b7009202ce 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-02-14 22:44:27 -06:00
Jeffrey C. Ollie
25c5ecf553 gtk: require libadwaita
This commit removes support for building without libadwaita.
2025-02-14 21:49:51 -06:00
Tim Culverhouse
09fbf096d3 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-02-14 18:55:26 -06:00
Mitchell Hashimoto
b975f1e860 cli: disable +boo on non-desktop platforms due to lack of tty 2025-02-14 15:14:05 -08:00
Mitchell Hashimoto
8c4b0f815d prettier 2025-02-14 14:54:59 -08:00
Mitchell Hashimoto
4e8e2d9796 nix: snapcraft should only be installed on Linux 2025-02-14 14:52:36 -08:00
Ken VanDine
90ce5b75f1 Simplified setting snap version 2025-02-14 14:52:08 -08:00
Ken VanDine
fd9cbde3d8 Merge remote-tracking branch 'upstream/main' into add_snap_package 2025-02-14 14:52:08 -08:00
Ken VanDine
b0edda4b69 Updated stage packages 2025-02-14 14:52:08 -08:00
Ken VanDine
bbd279bb4f Merge branch 'ghostty-org:main' into add_snap_package 2025-02-14 14:52:08 -08:00
Ken VanDine
bdbf30dc96 Merge branch 'ghostty-org:main' into add_snap_package 2025-02-14 14:52:08 -08:00
Ken VanDine
a111b3f96f Per PR review feedback, this is the more "ziggy" way of doing the check for environment variable. 2025-02-14 14:52:08 -08:00
Ken VanDine
f239df59ca Clean up environment variable while launching the shell 2025-02-14 14:52:08 -08:00
Ken VanDine
c036eb2444 Unset environment varies set by the snap 2025-02-14 14:52:08 -08:00
Ken VanDine
cee189de11 Only export XDG_CONFIG_HOME and XDG_DATA_HOME if they aren't already set 2025-02-14 14:52:08 -08:00
Ken VanDine
d2f82b2e40 Stage libglib2.0-0t64 to insure we don't mix in the host's lib 2025-02-14 14:52:08 -08:00
Ken VanDine
3e669fc4bb Improved environment handling to ensure the snap will work across
distros and unset all SNAP environment variables that could leak at
runtime
2025-02-14 14:52:08 -08:00
Ken VanDine
1a5b69181f Use patch-rpath which improves our cross distro support 2025-02-14 14:52:08 -08:00
Mitchell Hashimoto
55c5b8b72f ci: temporary apt installs required for namespace 2025-02-14 14:52:08 -08:00
Ken VanDine
5e77a973b2 Removed duplicated stage-packages 2025-02-14 14:52:08 -08:00
Ken VanDine
9c81cd323d Don't stage shells 2025-02-14 14:52:08 -08:00
Ken VanDine
78446008c4 enable-patchelf is more repliable for classic snaps 2025-02-14 14:52:08 -08:00
Ken VanDine
e09d8455a1 Set GHOSTTY_RESOURCES_DIR 2025-02-14 14:52:08 -08:00
Ken VanDine
1dcea3b11f Exit with error if building for unsupported arch 2025-02-14 14:52:08 -08:00
Ken VanDine
9d62c31f44 no-patchelf for DRI and tidy up the mesa bits 2025-02-14 14:52:08 -08:00
Ken VanDine
0272ad9edb Stage gnome-text-editor to open configuration, this makes it more
reliable across more distros as a classic snap.
2025-02-14 14:52:08 -08:00
Ken VanDine
f3829072f3 Drop patchelf 2025-02-14 14:52:08 -08:00
Ken VanDine
2b6b7c19d2 Enable patch-elf for libs part 2025-02-14 14:52:08 -08:00
Ken VanDine
3a9d61d6e4 Stage more depends to ensure we aren't getting leaks from the host 2025-02-14 14:52:08 -08:00
Ken VanDine
6d8b3973e4 EGL fixes, ensure necessary env variables are set to isolate
dependencies from the host
2025-02-14 14:52:08 -08:00
Ken VanDine
aa4d9809c3 CRAFT_TARGET_ARCH is deprecated, use CRAFT_ARCH_BUILD_FOR 2025-02-14 14:52:08 -08:00
Ken VanDine
ec8e7d9d86 Ensure LD_LIBRARY_PATH is set appropriately 2025-02-14 14:52:08 -08:00
Mitchell Hashimoto
f1f23e1c7d Add snap to nix, add arm64 builders 2025-02-14 14:52:08 -08:00
Ken VanDine
2e0e8af1ad Set a more meaningful version for the snap 2025-02-14 14:52:08 -08:00
Ken VanDine
53f1b4bc15 Changed shebang in launcher script 2025-02-14 14:52:08 -08:00
Ken VanDine
eae420a241 Only run snap workflow on push and PR 2025-02-14 14:52:08 -08:00
Ken VanDine
bf49784b7d Don't stage shells 2025-02-14 14:52:08 -08:00
Ken VanDine
b6a3b98828 enable-patchelf is more repliable for classic snaps 2025-02-14 14:52:08 -08:00
Ken VanDine
eb0816c2c4 Set GHOSTTY_RESOURCES_DIR 2025-02-14 14:52:08 -08:00
Ken VanDine
30fa18390f Install bash-completion 2025-02-14 14:52:08 -08:00
Ken VanDine
f51789b17a Exit with error if building for unsupported arch 2025-02-14 14:52:08 -08:00
Ken VanDine
97b104cf9d Set source-type for launcher dir 2025-02-14 14:52:08 -08:00
Ken VanDine
a7d1029e5c Added snap build workflow 2025-02-14 14:52:08 -08:00
Ken VanDine
aed30502bd Added snap packaging 2025-02-14 14:52:08 -08:00
Mitchell Hashimoto
56efaf0c82 [macOS] feat: add bring_all_to_front keybinding (#5006)
Can't consider the feature complete until the Linux (GTK) implementation
:/ .

Fixes #4704
2025-02-14 14:49:28 -08:00
Mitchell Hashimoto
686b6a2971 cli: add +boo command (#4876)
Add a `+boo` command to show the animation from the website. The data
for the frames is compressed during the build process. This build step
was added to the SharedDeps object because it is used in both
libghostty and in binaries.

The compression is done as follows:

- All files are concatenated together using \x01 as a combining byte
- The files are compressed to a cached build file
- A zig file is written to stdout which `@embedFile`s the compressed
  file and exposes it to the importer
- A new anonymous module "framedata" is added in the SharedDeps object

Any file can import framedata and access the compressed bytes via
`framedata.compressed`. In the `boo` command, we decompress the slice
and
split it into frames for use in the animation.

The overall addition to the binary size is 348k.
2025-02-14 14:47:51 -08:00
Tim Culverhouse
9cb297202b cli: add +boo command
Add a `+boo` command to show the animation from the website. The data
for the frames is compressed during the build process. This build step
was added to the SharedDeps object because it is used in both
libghostty and in binaries.

The compression is done as follows:
- All files are concatenated together using \x01 as a combining byte
- The files are compressed to a cached build file
- A zig file is written to stdout which `@embedFile`s the compressed
  file and exposes it to the importer
- A new anonymous module "framedata" is added in the SharedDeps object

Any file can import framedata and access the compressed bytes via
`framedata.compressed`. In the `boo` command, we decompress the file and
split it into frames for use in the animation.

The overall addition to the binary size is 348k.
2025-02-14 14:46:18 -08:00
Damien Mehala
a0f243691a feat: add bring_all_to_front keybinding
Resolves #4704.
2025-02-14 14:41:49 -08:00
Mitchell Hashimoto
cdfea4bc7e feat: add save instruction to +list-themes (#4902)
Close #4731 

<img width="1164" alt="image"
src="https://github.com/user-attachments/assets/50cb96cc-5192-424f-9ec6-1a150ae7f98b"
/>
2025-02-14 13:47:46 -08:00
Aaron Ruan
5c1f85e861 Merge branch 'ghostty-org:main' into list-theme-save 2025-02-14 13:44:33 -08:00
Aaron Ruan
2da7d77feb add save instruction to +list-themes
Signed-off-by: Aaron Ruan <aaron212cn@outlook.com>
2025-02-14 13:44:33 -08:00
Mitchell Hashimoto
1121ea9d6c Add tab title renaming feature to macos (#4217)
Tries to resolve #2509 partially by enabling tab title renaming feature
for MacOS.

* Used the existing set_title action
* Added the set_title action to Bindings so that a keybind can be set
for this feature (defaulted this to cmd+i -- same as iTerm2)
* 3 ways to trigger this feature: "Set Title" option in the right-click
menu for the window, "Set Title" option in the "View" menu in the title
bar, and the keybind (cmd+i default)
* Prevented esc sequences from the different programs from resetting the
tab title once it is set manually using a new state var called
`titleSetManually` in the SurfaceView
* The input box is a basic native macos dialog with "OK" and "Cancel".
Leaving the title text box empty resets the title to automatic / from
the `title` property in the config

**Demo**:


https://github.com/user-attachments/assets/5cacc389-4486-46c4-8cd5-dda347e9c663


Need some feedback. Thanks!
2025-02-14 13:37:29 -08:00
Aswin M Prabhu
a581955b9b Add tab title rename feature to macos 2025-02-14 13:29:36 -08:00
Mitchell Hashimoto
228b4dbd60 build: mirror most of our direct dependencies (#5765)
This adds a new script `update-mirror.sh` which generates the proper
blob format for R2 (or any blob storage) to mirror all of our
dependencies.

It doesn't automate updating build.zig.zon but on an ongoing basis this
should be easy to do manually, and we can strive to automate it in the
future.

I omitted iTerm2 color themes because we auto-update that via CI and
updating all of the machinery to send it to our mirror and so on is a
pain. Additionally, this doesn't mirror transitive dependencies because
Zig doesn't have a way to fetch those from a mirror instead (unless you
pre-generate a full cache like packagers but that's not practical for
day to day development).

It's hugely beneficial just to get most of our dependencies mirrored.
2025-02-14 11:34:08 -08:00
Mitchell Hashimoto
8231ebb770 build: mirror most of our direct dependencies
This adds a new script `update-mirror.sh` which generates the proper
blob format for R2 (or any blob storage) to mirror all of our
dependencies.

It doesn't automate updating build.zig.zon but on an ongoing basis this
should be easy to do manually, and we can strive to automate it in the
future.

I omitted iTerm2 color themes because we auto-update that via CI and
updating all of the machinery to send it to our mirror and so on is a
pain. Additionally, this doesn't mirror transitive dependencies because
Zig doesn't have a way to fetch those from a mirror instead (unless you
pre-generate a full cache like packagers but that's not practical for
day to day development).

It's hugely beneficial just to get most of our dependencies mirrored.
2025-02-14 10:06:15 -08:00
Mitchell Hashimoto
95e6a27393 build: generate a build.zig.zon.txt file for easy zig fetch scripting (#5764)
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-02-14 09:24:07 -08:00
Jeffrey C. Ollie
f32ad5216b 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-02-14 09:23:51 -08:00
Mitchell Hashimoto
56b244973f Add back fetch-zig-cache.sh for packaging (#5762)
See the comment on more details, which also covers when and how we can
remove this.
2025-02-14 08:07:28 -08:00
Mitchell Hashimoto
cdd287c88b 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-02-14 07:23:26 -08:00
Mitchell Hashimoto
315df0ab3f macos: add padded-notch option for macos-non-native-fullscreen (#5750)
Finishes #378
Supercedes #4159

This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the menu.

This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.

The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.

EDIT: Hah, the video below is pretty useless since the screen recording
software omits the notch. 😄


https://github.com/user-attachments/assets/4b1e84dd-3b3a-44a8-a83b-0f51e44f6cf8
2025-02-13 21:09:29 -08:00
Mitchell Hashimoto
ac7aa757bd macos: add padded-notch option for macos-non-native-fullscreen
Finishes #378
Supercedes #4159

This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the
menu.

This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.

The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.
2025-02-13 20:27:42 -08:00
Mitchell Hashimoto
52a5069dec up versions for development 2025-02-13 15:31:14 -08:00
Mitchell Hashimoto
ee963f6296 macOS: fix invalid kitty keyboard encoding of control characters (#5747)
Fixes #5743 

This fixes a terrible regression where by fixing one issue we introduced
another, and the other is that ctrl keys didn't work with programs with
Kitty keyboard protocol.

The problem is that our fix blindly assumed control was always consumed
for translation, which is obviously wrong but I didn't think there'd be
downstream effects. The reality is that we need to be more accurate.

This PR makes it so that:

* If macOS provides us with UTF-8 text, we assume all mods were involved
except super
* If macOS does not provide us with UTF-8 text, we use whatever
UCKeyTranslate consumed
* We never allow UCKeyTranslate to consume control because it turns it
into the masked ASCII value and we don't want that.

The only _new_ behavior which fixes the bug is point 2 above. 

Tested:

  1. Dvorak Ctrl characters
  2. Ergo-L Ctrl characters
  3. US standard Ctrl characters
  4. Japanese IME input Ctrl input to modify IME state
5. Ctrl keybindings with Kitty keyboard protocol in both Kitty and
Neovim
2025-02-13 15:03:54 -08:00
Mitchell Hashimoto
b44b1086d3 apprt/embedded: proper consumed modifier state for ctrl keys 2025-02-13 14:58:41 -08:00
Mitchell Hashimoto
9978ea3b9c Revert "macos: don't remove ctrl modifier for text input"
This reverts commit 3104b21758.
2025-02-13 14:20:13 -08:00
Mitchell Hashimoto
710ea1c8d9 up all the metadata to 1.1.2 2025-02-13 13:02:06 -08:00
Mitchell Hashimoto
ed60e07257 apprt: require envmap for exec-based termio (#5742)
Supercedes #5726
2025-02-13 12:27:13 -08:00
Mitchell Hashimoto
1fea8028a3 apprt: require envmap for exec-based termio
Supercedes #5726
2025-02-13 12:26:11 -08:00
Mitchell Hashimoto
c481bdf704 macos: make goto_split, goto_tab, and move_tab performable (#5740)
Fixes #5552

This makes the mentioned actions performable. This isn't perfect, but it
does so in a way that resolves the user issue in #5552. This commit
returns an action is NOT performed if it doesn't have splits or tabs
(respectiely for the actions), but also reports its ALWAYS performed if
it does.

This latter logic isn't accurate: we should only return performable if
it was actually done. So for example, goto_split:top should do nothing
if we're already at the top. But, we report it as performed today.

This is good enough to resolve the issue and fix the core problem faced
for 1.1.0.
2025-02-13 10:59:04 -08:00
Mitchell Hashimoto
5105c52ef7 macos: make goto_split, goto_tab, and move_tab performable
Fixes #5552

This makes the mentioned actions performable. This isn't perfect, but it
does so in a way that resolves the user issue in #5552. This commit
returns an action is NOT performed if it doesn't have splits or tabs
(respectiely for the actions), but also reports its ALWAYS performed if
it does.

This latter logic isn't accurate: we should only return performable if
it was actually done. So for example, goto_split:top should do nothing
if we're already at the top. But, we report it as performed today.

This is good enough to resolve the issue and fix the core problem faced
for 1.1.0.
2025-02-13 10:40:07 -08:00
Mitchell Hashimoto
20dcae876d macos: don't remove ctrl modifier for text input (#5738)
Fixes #5448

We previously removed the ctrl modifier for text commit (IME-style) to
workaround a libghostty quirk (as noted in the comment in the diff). But
this broke other keyboard layouts.

This commit attempts to clean this up slightly -- but not completely --
by removing that hack, and only modifying the ctrl behavior for the
UCKeyTranslate call.

Long term, I plan to remove UCKeyTranslate completely, as noted in the
todo comment already written just below this diff.

This fixes the aforementioned issue and hopefully doesn't regress any
other behavior. I tested the following:

  1. Dvorak Ctrl characters
  2. Ergo-L Ctrl characters
  3. US standard Ctrl characters
  4. Japanese IME input Ctrl input to modify IME state
2025-02-13 09:51:26 -08:00
Mitchell Hashimoto
3104b21758 macos: don't remove ctrl modifier for text input
Fixes #5448

We previously removed the ctrl modifier for text commit (IME-style)
to workaround a libghostty quirk (as noted in the comment in the diff).
But this broke other keyboard layouts.

This commit attempts to clean this up slightly -- but not completely --
by removing that hack, and only modifying the ctrl behavior for the
UCKeyTranslate call.

Long term, I plan to remove UCKeyTranslate completely, as noted in the
todo comment already written just below this diff.

This fixes the aforementioned issue and hopefully doesn't regress any
other behavior. I tested the following:

  1. Dvorak Ctrl characters
  2. Ergo-L Ctrl characters
  3. US standard Ctrl characters
  4. Japanese IME input Ctrl input to modify IME state
2025-02-13 09:43:07 -08:00
Mitchell Hashimoto
15e41959eb Eliminate tab content flickering during tab movement on macOS (#5729)
Fixes https://github.com/ghostty-org/ghostty/issues/5689

The flickering was caused by uncontrolled visual updates during tab
reordering. The fix uses `NSAnimationContext` to batch all visual
changes together and simplifies window focus handling to prevent
unnecessary redraws.


https://github.com/user-attachments/assets/2a6525cd-8a97-418b-8442-18dab6b4636d
2025-02-13 09:24:41 -08:00
Mitchell Hashimoto
783f4014f8 build: replace codeberg dep with self-hosted mirror (#5736)
The content hashes match so if users don't trust us they can grab from
upstream.
2025-02-13 09:24:30 -08:00
Mitchell Hashimoto
2a29f71b2c draw octants directly rather than relying on font (#5433)
* implement `yQuads()` and `draw_octant()`, pretty obvious extensions of
existing code. to allocate up to 3 potential remainder lines, consider
that octants will often appear in a rectangular subset of the terminal.
we want the distributed excess uniformly distributed across such a
region. so:
* one excess row: break symmetry in any direction (pick an arbitrary
tetrad and use it everywhere)
  * two excess rows: go to alternating tetrads
* three excess rows: break symmetry, do not use three contiguous tetrads
* our `Octant`s are octary arrays of `bool`, provided as a somewhat
opaque constant table
* the 8-line copy-and-paste draw based on the `Octant` is not the
prettiest thing in the known universe
* we could generalize `draw_sextant()` and `draw_octant()` like
notcurses did, almost certainly
  * oh bird thou never wert
 

with that said, i don't think `draw_octant()` is actually being called
lol, so let's not merge this yet. happy to hear early feedback, though.
2025-02-13 09:21:11 -08:00
Mitchell Hashimoto
5919c57527 build: replace codeberg dep with self-hosted mirror
The content hashes match so if users don't trust us they can grab from
upstream.
2025-02-13 09:18:50 -08:00
nick black
8b2f9acfb4 Implement draw_octant() and yQuads() for U16.0 2025-02-13 09:08:33 -08:00
Mitchell Hashimoto
6f4716fd60 macOS: update additional references to Sparkle 2.6.3->2.6.4 (#5734) 2025-02-13 08:49:28 -08:00
Jeffrey C. Ollie
74f222abec macOS: update additional references to Sparkle 2.6.3->2.6.4 2025-02-13 10:35:38 -06:00
Jeffrey C. Ollie
9f6067d899 gtk: fix ui/blueprint builder (#5727)
fixes issues found while making use of the new builder api
2025-02-13 08:03:49 -06:00
Bryan Lee
f72fd32bf0 Eliminate tab content flickering during tab movement on macOS 2025-02-13 14:59:14 +08:00
Jeffrey C. Ollie
e396c8538a gtk: fix ui/blueprint builder 2025-02-12 22:16:33 -06:00
Jeffrey C. Ollie
432beac315 gtk: add support for using GTK Builder UI files and Blueprints (#5714)
Adds buildtime and comptime checks to make sure that Blueprints/UI files
are availble and correctly formed. Will also compile Blueprints to UI
files so that they are available to GTK code.
2025-02-12 19:10:50 -06:00
Mitchell Hashimoto
256281c5de terminal: reflow the saved cursor if we have one (#5720)
Fixes #5718

When a terminal is resized with text reflow (i.e. soft-wrapped text),
the cursor is generally reflowed with it.

For example, imagine a terminal window 5-columns wide and you type the
following without pressing enter. The cursor is on the X.

```
OOOOO
OOX
```

If you resize the window now to 8 or more columns, this happens, as
expected:

```
OOOOOOOX
```

As expected, the cursor remains on the "X". This behaves like any other
text input...

Terminals also provide an escape sequence to
[save the cursor (ESC 7 aka
DECSC)](https://ghostty.org/docs/vt/esc/decsc). This includes, amongst
other things, the cursor position. The cursor can be restored with
[DECRC](https://ghostty.org/docs/vt/esc/decrc).

The behavior of the position of the _saved cursor_ in the context of
text reflow is unspecified and varies wildly between terminals Ghostty
does this right now (as do many other terminals):

```
OOOOOOOO
   X
```

This commit changes the behavior so that we reflow the saved cursor.
2025-02-12 13:16:11 -08:00
Jeffrey C. Ollie
f63242f7fb gtk: add support for using GTK Builder UI files and Blueprints
Adds buildtime and comptime checks to make sure that Blueprints/UI files
are availble and correctly formed. Will also compile Blueprints to UI
files so that they are available to GTK code.
2025-02-12 15:05:42 -06:00
Mitchell Hashimoto
7dac9e02b3 terminal: reflow the saved cursor if we have one
Fixes #5718

When a terminal is resized with text reflow (i.e. soft-wrapped text), the cursor
is generally reflowed with it.

For example, imagine a terminal window 5-columns wide and you type the
following without pressing enter. The cursor is on the X.

```
OOOOO
OOX
```

If you resize the window now to 8 or more columns, this happens, as expected:

```
OOOOOOOX
```

As expected, the cursor remains on the "X". This behaves like any other text
input...

Terminals also provide an escape sequence to
[save the cursor (ESC 7 aka DECSC)](https://ghostty.org/docs/vt/esc/decsc).
This includes, amongst other things, the cursor position. The cursor can be
restored with [DECRC](https://ghostty.org/docs/vt/esc/decrc).

The behavior of the position of the _saved cursor_ in the context of text
reflow is unspecified and varies wildly between terminals Ghostty does this
right now (as do many other terminals):

```
OOOOOOOO
   X
```

This commit changes the behavior so that we reflow the saved cursor.
2025-02-12 10:34:31 -08:00
Jeffrey C. Ollie
99cbc06292 gtk: use the standard path for including Adwaita header file (#5716)
As pointed out by @tristan957 the standard path for including the
Adwaita header file is simply "adwaita.h". While it may have been
necessary in the past to use a non-standard include path, that no longer
appears to be the case.
2025-02-12 11:29:38 -06:00
Jeffrey C. Ollie
749bac3d72 gtk: use the standard path for including Adwaita header file
As pointed out by @tristan957 the standard path for including the
Adwaita header file is simply "adwaita.h". While it may have been
necessary in the past to use a non-standard include path, that no longer
appears to be the case.
2025-02-12 11:14:38 -06:00
Mitchell Hashimoto
1bb87e14a5 termio: free envmap when able, fix memory leak (#5715)
Caused by #5650

I actually don't understand how this didn't happen before or why we
didn't notice it but it seems like the envmap was never freed. In the
latest debug builds prior to this build GPA reports the leak.

We should free the envmap when the subprocess is deinitialized. But also
we can free the env map as soon as we start the subprocess which saves
some small amount of memory at runtime.

Additionally, we should only be freeing the envmap on error if we
created it.
2025-02-12 09:00:56 -08:00
Mitchell Hashimoto
9ea29dc508 termio: free envmap when able, fix memory leak
Caused by #5650

I actually don't understand how this didn't happen before or why we
didn't notice it but it seems like the envmap was never freed. In the
latest debug builds prior to this build GPA reports the leak.

We should free the envmap when the subprocess is deinitialized. But also
we can free the env map as soon as we start the subprocess which saves
some small amount of memory at runtime.

Additionally, we should only be freeing the envmap on error if we
created it.
2025-02-12 08:48:26 -08:00
Mitchell Hashimoto
f2f0c6b4e9 macOS: use Nix to get Zig deps (#5699)
This should help with API rate limits being hit by macOS builders since
they can't use the Namespace cache that Linux builders can use to cache
Zig dependencies. It will need to be run by Mitchell once so that the
Cachix action can push everything up to the cache and then the full
benefits should be seen. Not sure how using `--system` on all the macOS
builds will affect things overall but it doesn't seem to have affected
the CI.
2025-02-12 07:17:13 -08:00
Jeffrey C. Ollie
1e3cd89516 macOS: use Nix to get Zig deps 2025-02-12 08:21:16 -06:00
Mitchell Hashimoto
019f789819 macOS: fix iOS build breakage from #5644 (#5713) 2025-02-12 06:18:06 -08:00
Jeffrey C. Ollie
9bac6ecbff macOS: fix iOS build breakage from #5644 2025-02-12 07:54:50 -06:00
Leah Amelia Chen
76ab6717c9 gtk: fix context menu computed location (#5710)
When a tab bar is displayed, the context menu opened with right click is
offset from the cursor.

This was due to using the incorrect coordinate space for describing
where to draw the context menu.


[gtk_popover_set_pointing_to](https://docs.gtk.org/gtk4/method.Popover.set_pointing_to.html)
uses the coordinate space of the popover's parent, however we used the
active window's coordinate space which was noticeably different when the
tab bar is visible.

Before:
![Screenshot_12-Feb_22-39-07_com mitchellh
ghostty](https://github.com/user-attachments/assets/5263dd9d-f3ab-44f6-baf2-7208d3d3bd01)
After:
![Screenshot_12-Feb_22-41-41_com mitchellh
ghostty-debug](https://github.com/user-attachments/assets/8d3e8bbd-af4e-43a6-a52d-6601a0b66ece)
2025-02-12 13:08:33 +01:00
azhn
b1ae7eea2f gtk: fix context menu computed location 2025-02-12 22:46:00 +11:00
azhn
c061d06aa1 Revert "gtk: Point right-click context menu exactly at cursor to improve behaviour at edges and closeness to cursor"
This reverts commit bf5351ed41.
2025-02-12 19:50:32 +11:00
azhn
bf5351ed41 gtk: Point right-click context menu exactly at cursor to improve behaviour at edges and closeness to cursor 2025-02-12 19:02:56 +11:00
Mitchell Hashimoto
338a07ecad Performable tab and split navigation (#5644)
Partial fixes #5552 (for GTK).

This PR adds the core infrastructure for keybind actions that are
implemented as runtime app actions to be performable. This is done by
having `rt_app.performAction` return a boolean. By default all runtime
app actions return `true` (the action was performed) unless they are
modified to return `true`/`false` as appropriate.

The GTK apprt is modified so that `goto_split`, `previous_tab`,
`next_tab`, `last_tab`, and `goto_tab` are performable. macOS support
will need to be added in a subsequent commit.

This doesn't completely solve the issue for the OP because if the
`goto_split` isn't performable there is no fallback to`previous_tab` or
`next_tab`.

I don't think that the approach taken in #5579 is the right one as it
conflates split and tab navigation unconditionally which I don't think
is what everyone would want. Either a separate action that explicitly
combines the actions or a solution to #3175 will be the ultimate
solution I believe.
2025-02-11 17:03:39 -08:00
Mitchell Hashimoto
f26c96d51b apprt/glfw: return false for unimplemented actions 2025-02-11 16:53:04 -08:00
Mitchell Hashimoto
927cb4e622 apprt/gtk: some stylistic changes 2025-02-11 16:52:31 -08:00
Mitchell Hashimoto
cbf562ecb3 apprt/embedded: make performAction return the performable state 2025-02-11 16:43:50 -08:00
Jeffrey C. Ollie
69fd438370 gtk: make previous_tab, next_tab, last_tab, and goto_tab performable 2025-02-11 16:43:50 -08:00
Jeffrey C. Ollie
57e7565b7f gtk: make goto_split a performable action 2025-02-11 16:43:50 -08:00
Jeffrey C. Ollie
4ad749607a core: performAction now returns a bool
This is to facilitate the `performable:` prefix on keybinds that are
implemented using app runtime actions.
2025-02-11 16:43:50 -08:00
Mitchell Hashimoto
66442cd469 Refactor keybinding actions reference generation (#5695)
https://github.com/ghostty-org/ghostty/pull/4974#issuecomment-2652048815
2025-02-11 15:05:22 -08:00
Mitchell Hashimoto
afc42fe891 macos: add a variety of artist-drawn alternate icons (#5696)
This is just a fun change to add a bunch of alternate icons. We don't
want to add too many since this increases the final bundle size but we
also want to have some fun. :)


![Ghostty-More-Alts](https://github.com/user-attachments/assets/2bf228fb-7a1c-4341-a4a1-e9daca93d1c5)
2025-02-11 15:03:55 -08:00
Mitchell Hashimoto
58ab66f094 macos: add a variety of artist-drawn alternate icons
This is just a fun change to add a bunch of alternate icons. We don't
want to add too many since this increases the final bundle size but we
also want to have some fun. :)
2025-02-11 14:51:43 -08:00
Bryan Lee
1674224c1a Refactor keybinding actions reference generation 2025-02-12 06:29:40 +08:00
Mitchell Hashimoto
4aa452a2c7 gtk(x11): set WINDOWID env var for subprocesses (#5650)
`WINDOWID` is the conventional environment variable for scripts that
want to know the X11 window ID of the terminal, so that it may call
tools like `xprop` or `xdotool`. We already know the window ID for
window protocol handling, so we might as well throw this in for
convenience.

Originally suggested by #4299
2025-02-11 13:55:30 -08:00
Leah Amelia Chen
56ea6c406c gtk(x11): set WINDOWID env var for subprocesses
`WINDOWID` is the conventional environment variable for scripts that
want to know the X11 window ID of the terminal, so that it may call
tools like `xprop` or `xdotool`. We already know the window ID for
window protocol handling, so we might as well throw this in for
convenience.
2025-02-11 13:42:12 -08:00
Mitchell Hashimoto
f8b547f92e Revert "Fixed documentation generation in list-actions --docs command (#4974)"
This reverts commit f3d0c7c2ad, reversing
changes made to 4b77a1c71e.
2025-02-11 12:55:40 -08:00
Mitchell Hashimoto
adb187685b Fix confirm-close-surface not working for hidden quick terminal (#5647)
Fixes #5450.


https://github.com/user-attachments/assets/7090fe55-dd1f-4517-9f8a-ffc3807283db
2025-02-11 12:55:20 -08:00
Mitchell Hashimoto
f3d0c7c2ad Fixed documentation generation in list-actions --docs command (#4974)
Fixes https://github.com/ghostty-org/ghostty/issues/4958

## Changes

1. Fixed documentation generation in `actions.mdx`:

- Fixed an issue where the last action's documentation was [not properly
generated](fe6c69263c/docs/config/keybind/reference.mdx (crash))
   
- Ensured all actions' documentation is correctly included in the output

2. Improved `ghostty +list-actions --docs` command output formatting:

   - Grouped related actions together with shared documentation
   
   - Added proper spacing between action groups
  
<details> 
    <summary>ghostty-dev +list-actions --docs</summary>

```
ignore:
  Ignore this key combination, don't send it to the child process, just
  black hole it.

unbind:
  This action is used to flag that the binding should be removed from
  the set. This should never exist in an active set and `set.put` has an
  assertion to verify this.

csi:
  Send a CSI sequence. The value should be the CSI sequence without the
  CSI header (`ESC [` or `\x1b[`).

esc:
  Send an `ESC` sequence.

text:
  Send the given text. Uses Zig string literal syntax. This is currently
  not validated. If the text is invalid (i.e. contains an invalid escape
  sequence), the error will currently only show up in logs.

cursor_key:
  Send data to the pty depending on whether cursor key mode is enabled
  (`application`) or disabled (`normal`).

reset:
  Reset the terminal. This can fix a lot of issues when a running
  program puts the terminal into a broken state. This is equivalent to
  when you type "reset" and press enter.

  If you do this while in a TUI program such as vim, this may break
  the program. If you do this while in a shell, you may have to press
  enter after to get a new prompt.

copy_to_clipboard:
paste_from_clipboard:
paste_from_selection:
  Copy and paste.

copy_url_to_clipboard:
  Copy the URL under the cursor to the clipboard. If there is no
  URL under the cursor, this does nothing.

increase_font_size:
decrease_font_size:
  Increase/decrease the font size by a certain amount.

reset_font_size:
  Reset the font size to the original configured size.

clear_screen:
  Clear the screen. This also clears all scrollback.

select_all:
  Select all text on the screen.

scroll_to_top:
scroll_to_bottom:
scroll_page_up:
scroll_page_down:
scroll_page_fractional:
scroll_page_lines:
  Scroll the screen varying amounts.

adjust_selection:
  Adjust an existing selection in a given direction. This action
  does nothing if there is no active selection.

jump_to_prompt:
  Jump the viewport forward or back by prompt. Positive number is the
  number of prompts to jump forward, negative is backwards.

write_scrollback_file:
  Write the entire scrollback into a temporary file. The action
  determines what to do with the filepath. Valid values are:

    - "paste": Paste the file path into the terminal.
    - "open": Open the file in the default OS editor for text files.
      The default OS editor is determined by using `open` on macOS
      and `xdg-open` on Linux.

write_screen_file:
  Same as write_scrollback_file but writes the full screen contents.
  See write_scrollback_file for available values.

write_selection_file:
  Same as write_scrollback_file but writes the selected text.
  If there is no selected text this does nothing (it doesn't
  even create an empty file). See write_scrollback_file for
  available values.

new_window:
  Open a new window. If the application isn't currently focused,
  this will bring it to the front.

new_tab:
  Open a new tab.

previous_tab:
  Go to the previous tab.

next_tab:
  Go to the next tab.

last_tab:
  Go to the last tab (the one with the highest index)

goto_tab:
  Go to the tab with the specific number, 1-indexed. If the tab number
  is higher than the number of tabs, this will go to the last tab.

move_tab:
  Moves a tab by a relative offset.
  Adjusts the tab position based on `offset`. For example `move_tab:-1` for left, `move_tab:1` for right.
  If the new position is out of bounds, it wraps around cyclically within the tab range.

toggle_tab_overview:
  Toggle the tab overview.
  This only works with libadwaita enabled currently.

new_split:
  Create a new split in the given direction. The new split will appear in
  the direction given. For example `new_split:up`. Valid values are left, right, up, down and auto.

goto_split:
  Focus on a split in a given direction. For example `goto_split:up`.
  Valid values are left, right, up, down, previous and next.

toggle_split_zoom:
  zoom/unzoom the current split.

resize_split:
  Resize the current split by moving the split divider in the given
  direction. For example `resize_split:left,10`. The valid directions are up, down, left and right.

equalize_splits:
  Equalize all splits in the current window

inspector:
  Show, hide, or toggle the terminal inspector for the currently focused
  terminal.

open_config:
  Open the configuration file in the default OS editor. If your default OS
  editor isn't configured then this will fail. Currently, any failures to
  open the configuration will show up only in the logs.

reload_config:
  Reload the configuration. The exact meaning depends on the app runtime
  in use but this usually involves re-reading the configuration file
  and applying any changes. Note that not all changes can be applied at
  runtime.

close_surface:
  Close the current "surface", whether that is a window, tab, split, etc.
  This only closes ONE surface. This will trigger close confirmation as
  configured.

close_tab:
  Close the current tab, regardless of how many splits there may be.
  This will trigger close confirmation as configured.

close_window:
  Close the window, regardless of how many tabs or splits there may be.
  This will trigger close confirmation as configured.

close_all_windows:
  Close all windows. This will trigger close confirmation as configured.
  This only works for macOS currently.

toggle_fullscreen:
  Toggle fullscreen mode of window.

toggle_window_decorations:
  Toggle window decorations on and off. This only works on Linux.

toggle_secure_input:
  Toggle secure input mode on or off. This is used to prevent apps
  that monitor input from seeing what you type. This is useful for
  entering passwords or other sensitive information.

  This applies to the entire application, not just the focused
  terminal. You must toggle it off to disable it, or quit Ghostty.

  This only works on macOS, since this is a system API on macOS.

toggle_quick_terminal:
  Toggle the "quick" terminal. The quick terminal is a terminal that
  appears on demand from a keybinding, often sliding in from a screen
  edge such as the top. This is useful for quick access to a terminal
  without having to open a new window or tab.

  When the quick terminal loses focus, it disappears. The terminal state
  is preserved between appearances, so you can always press the keybinding
  to bring it back up.

  To enable the quick terminally globally so that Ghostty doesn't
  have to be focused, prefix your keybind with `global`. Example:

  \```ini
  keybind = global:cmd+grave_accent=toggle_quick_terminal
  \```

  The quick terminal has some limitations:

    - It is a singleton; only one instance can exist at a time.
    - It does not support tabs, but it does support splits.
    - It will not be restored when the application is restarted
      (for systems that support window restoration).
    - It supports fullscreen, but fullscreen will always be a non-native
      fullscreen (macos-non-native-fullscreen = true). This only applies
      to the quick terminal window. This is a requirement due to how
      the quick terminal is rendered.

  See the various configurations for the quick terminal in the
  configuration file to customize its behavior.

  This currently only works on macOS.

toggle_visibility:
  Show/hide all windows. If all windows become shown, we also ensure
  Ghostty becomes focused. When hiding all windows, focus is yielded
  to the next application as determined by the OS.

  This currently only works on macOS.

quit:
  Quit ghostty.

crash:
  Crash ghostty in the desired thread for the focused surface.

  WARNING: This is a hard crash (panic) and data can be lost.

  The purpose of this action is to test crash handling. For some
  users, it may be useful to test crash reporting functionality in
  order to determine if it all works as expected.

  The value determines the crash location:

    - "main" - crash on the main (GUI) thread.
    - "io" - crash on the IO thread for the focused surface.
    - "render" - crash on the render thread for the focused surface.

```
</details>

## Testing

- Run `ghostty-dev +list-actions --docs` to verify the new output format

- Check generated _zig-out/share/ghostty/webdata/actions.mdx_ to ensure
all actions are properly documented
2025-02-11 12:47:47 -08:00
Mitchell Hashimoto
4b77a1c71e Remember last focused window position for next startup (#5529)
Implements window position persistence on macOS, similar to Terminal.app
and iTerm2. The window position is saved when it becomes main or moves,
and restored on next startup. Window position is kept within visible
screen bounds.

cc @jsumners - Would you mind giving it a try? Any feedback would be
appreciated!

Resolves #4233
2025-02-11 12:47:12 -08:00
Mitchell Hashimoto
5ad348b23e Add keyboard navigation for Terminal IO window (#3909)
## Changes

- Add keyboard navigation support in Terminal IO window
  - Use J/K keys for vim-style navigation
  - Support arrow keys for traditional navigation


https://github.com/user-attachments/assets/e5d3bba1-1a47-49c9-ac59-c6195515605c

Resolves https://github.com/ghostty-org/ghostty/issues/1096
2025-02-11 12:46:18 -08:00
Bryan Lee
c627231b0f Fix confirm-close-surface not working for hidden quick terminal 2025-02-11 12:42:34 -08:00
Bryan Lee
31273aaabc Remember last focused window position for next startup 2025-02-11 12:33:29 -08:00
Mitchell Hashimoto
d1614f6bc4 Fix segfault in reset with Kitty graphics images on screen (#5693)
Fixes #5649
2025-02-11 11:26:06 -08:00
Mitchell Hashimoto
95ae3642f9 macos: ensure previously key window regains key on toggle_visibility (#5692)
Fixes #5690

When we hide the app and then show it again, the previously key window
is lost. This is because we are not using unhide and are manually doing
it (and we're not using unhide for good reasons commented in the source
already).

Modify our hidden state to include what the key window was (as a weak
ref) and restore it when we show the app again.
2025-02-11 11:23:48 -08:00
Qwerasd
b624cfe262 fix(terminal): avoid Screen.reset causing use of undefined
Fully reset the kitty image storage instead of using the delete handler,
previously this caused a memory corruption / likely segfault due to use
of undefined, since the delete handler tries to clear the tracked pins
for placements, which it gets from the terminal, for which `undefined`
was passed in before.
2025-02-11 14:20:47 -05:00
Mitchell Hashimoto
c6da845f33 macos: ensure previously key window regains key on toggle_visibility
Fixes #5690

When we hide the app and then show it again, the previously key window
is lost. This is because we are not using unhide and are manually
doing it (and we're not using unhide for good reasons commented in the
source already).

Modify our hidden state to include what the key window was (as a weak
ref) and restore it when we show the app again.
2025-02-11 11:13:19 -08:00
Mitchell Hashimoto
869819eb89 macos: toggle_visibility fullscreen check requires active app (#5691)
This fixes a regression from #5472. The fullscreen check must check if
the app is active otherwise the guard statement fails and we can't bring
the macOS app back from the background.

This regression never hit a versioned release, only tip.
2025-02-11 11:11:36 -08:00
Mitchell Hashimoto
f986a32185 macos: toggle_visibility fullscreen check requires active app
This fixes a regression from #5472. The fullscreen check must check if
the app is active otherwise the guard statement fails and we can't bring
the macOS app back from the background.
2025-02-11 11:01:20 -08:00
Mitchell Hashimoto
0532f67282 nix: use zig2nix to manage nix cache/deps (#5439)
This brings the internal package more in line with how the nixpkgs
package is built. It also handles recursive dependencies better than the
current system.
2025-02-11 08:41:54 -08:00
Jeffrey C. Ollie
3a8c934b19 nix: use zig2nix to manage nix cache/deps
This brings the internal package more in line with how the nixpkgs
package is built. It also handles recursive dependencies better than the
current system.
2025-02-11 08:25:34 -08:00
Mitchell Hashimoto
ab71dac282 Fix large OSC 8 links causing memory corruption (#5666)
Fixes #5635

### Changes:
- Added handling to `Screen.adjustCapacity` which properly returns an
error if there's no room for the cursor hyperlink. Note the TODO, in the
future we should probably make it handle this by increasing the capacity
further.

- Added handling to `Screen.cursorSetHyperlink` which ensures there will
be sufficient capacity to add the redundant cursor hyperlink to the
string alloc after adjusting the capacity.

***Future work:** Aside from separating the cursor's managed memory from
the page memory, which is a big thing we desperately need, we should
probably also make the page string alloc intern the strings it gets to
deduplicate.*
  
- Fixed a slight bug in the OSC parser which was causing the hyperlink
command to be issued with an empty (0-length) URI when the buffer
capacity was exceeded during an OSC 8 sequence.

***Future work:** We should consider increasing the `MAX_BUF` since some
specs call for a max size of 4096 (such as various Kitty protocols) --
otherwise we should switch all the OSCs that can take arbitrary data
like this to use the allocator instead of the fixed buffer.*
  
- Fixed a problem where when cloning content across pages (as happened
every time we had to adjust page capacities) hyperlinks would not be
properly looked up, often leading to many many redundant copies of a
given URI being stored, exploding string memory.
2025-02-11 08:09:03 -08:00
Mitchell Hashimoto
fa5fe5d293 core: protect against crashes and hangs when themes are not files (#5632)
If a theme was not a file or a directory you could get a crash or a hang
(depending on platform) if the theme references a directory. This patch
also prevents attempts to load from other non-file sources.

Fixes: #5596
2025-02-11 07:24:10 -08:00
Mitchell Hashimoto
d87cf61d26 gtk: introduce Zig bindings for GTK/GObject (#5560)
`zig-gobject` is a set of GObject bindings that allow us to write
GTK-facing code in Zig instead of getting hands dirty with C. It's been
tested and refined in real-life applications and several GTK
contributors agree that it is a marked improvement over using the C API
directly, such as allowing method call syntax and avoiding many manual
`@ptrCast`s.

This PR doesn't actually contain any changes to our preexisting GTK code
— the migration process is intended to begin in chunks, firstly in
self-contained components (e.g. the header bar, overlays, etc.), and
then full-scale migration can begin when we remove non-Adwaita GTK
builds for 1.2. (After all, why port code that you'll remove later
either way?)
2025-02-11 07:19:59 -08:00
Qwerasd
dd8c795ec6 test(terminal/screen): cursorSetHyperlink OOM handling edge case
Tests handling introduced in 09c76d95 which ensures sufficient space for
the cursor hyperlink uri in the string alloc when adjusting capacity.
2025-02-10 15:52:40 -05:00
Qwerasd
a1b682d0da fix(terminal): properly lookup hyperlinks when cloning rows across pages
Before this we were doing bad things with the memory, looking at
`PageEntry`s for links not actually in the page we were reading the
strings from.
2025-02-10 15:52:40 -05:00
Qwerasd
e540a79032 test(terminal/screen): OOM handling in adjustCapacity
Adds tests for the adjustCapacity changes from 09c76d95

Fixes a small oversight in that change as well (resetting cursor style).
2025-02-10 14:21:42 -05:00
Qwerasd
1c524238c8 test(terminal/osc): fix command longer than buffer test
Ensure that the state is invalidated properly, this previously wasn't
the case but was fixed in 03fd9a97
2025-02-10 13:27:26 -05:00
Leah Amelia Chen
843c714088 gtk: introduce Zig bindings for GTK/GObject
`zig-gobject` is a set of GObject bindings that allow us to write
GTK-facing code in Zig instead of getting hands dirty with C.
It's been tested and refined in real-life applications and several
GTK contributors agree that it is a marked improvement over using
the C API directly, such as allowing method call syntax and avoiding
many manual `@ptrCast`s.

This commit doesn't actually contain any changes to our preexisting
GTK code — the migration process is intended to begin in chunks,
firstly in self-contained components (e.g. the header bar, overlays,
etc.), and then full-scale migration can begin when we remove non-Adwaita
GTK builds for 1.2. (After all, why port code that you'll remove later
either way?)
2025-02-10 18:34:08 +01:00
Qwerasd
03fd9a970b fix(terminal): properly invalidate over-sized OSCs
The `self.complete = false` here is important so we don't accidentally
dispatch, for example, a hyperlink command with an empty URI.
2025-02-10 11:49:05 -05:00
Qwerasd
09c76d95c7 fix(terminal): handle errors in Screen.adjustCapacity
Previously assumed adjusted capacities would always fit the cursor style
and hyperlink, this is not necessarily the case and led to memory
corruption in scenarios with large hyperlinks.
2025-02-10 00:17:08 -05:00
Qwerasd
a3e462bbba Hopefully fix weird bug for Intel Mac users (#5652)
I don't have a machine locally that can reproduce the issue in #5597 but
have been working with @paperlib over Discord to try to narrow down the
cause. My fix in #5625 didn't fix the problem, so I'm *really* hoping
this patch does.

The problem presents as any text that matches the default bg color not
being rendered, regardless of the cell's bg color. The best I can figure
is that either we have some sort of accidental UB or there's a driver
bug (perhaps related to the addition of the `[[flat]]` interpolation
qualifier to the vertex out structure?), but I don't have a system
locally to iterate on to narrow it down.
2025-02-09 18:24:58 -05:00
Qwerasd
28200fb9e5 fix(Metal): don't throw away glyphs when the fg and bg colors match
This optimization is extremely niche and seems to be related to a
strange bug experienced by Intel Mac users. Considering it costs some
amount to have this extra check here even though it's false in the vast
majority of cases, I feel it's pretty safe to just remove it entirely.
2025-02-09 18:11:36 -05:00
Jeffrey C. Ollie
2c06ce761b GTK: disable color management (#5593)
fixes: https://github.com/ghostty-org/ghostty/discussions/4976

Before: 

![image](https://github.com/user-attachments/assets/169c29b5-b52a-44cd-aea8-43c2cfd8e411)

After:

![image](https://github.com/user-attachments/assets/80468ae2-7d75-464f-a2b0-37da48fdf61d)
2025-02-08 20:48:36 -06:00
Jeffrey C. Ollie
1947ba9c68 core: protect against crashes and hangs when themes are not files
If a theme was not a file or a directory you could get a crash or a hang
(depending on platform) if the theme references a directory. This patch
also prevents attempts to load from other non-file sources.

Fixes: #5596
2025-02-08 15:17:25 -06:00
Qwerasd
f95f636f1f Metal: use "Managed" resource storage mode on discrete GPUs (#5625)
Discrete GPUs cannot use the "shared" storage mode. This causes
undefined behavior right now, and I believe it's what's causing a
problem on Intel systems with discrete GPUs with "inverted" cells.
(Observed in discussion #5597)

This commit also sets the CPU cache mode to "write combined" for our
resources since we don't read them back so Metal can optimize them
further with this hint.
2025-02-07 13:49:39 -05:00
Qwerasd
ea16890fd3 Metal: use "Managed" resource storage mode on discrete GPUs
Discrete GPUs cannot use the "shared" storage mode. This causes
undefined behavior right now, and I believe it's what's causing a
problem on Intel systems with discrete GPUs with "inverted" cells.

This commit also sets the CPU cache mode to "write combined" for our
resources since we don't read them back so Metal can optimize them
further with this hint.
2025-02-07 13:15:18 -05:00
eifr
c7044b198c Merge branch 'main' into disable-color-management 2025-02-07 18:00:18 +02:00
eifr
5d6c021e26 update gtk version 2025-02-07 18:00:08 +02:00
Mitchell Hashimoto
79d6d26784 Introduce subsystem maintainers via CODEOWNERS (#5599)
This commit introduces the proposed subsystem maintainer system for
Ghostty. This commit doesn't assign anyone yet to the subsystems, but
defines the CODEOWNERS file, creates the GitHub teams, and documents the
system.

We can discuss this in Discord, but any feedback is also welcome here.
2025-02-07 07:21:32 -08:00
eifr
76cf58915b add comments 2025-02-06 10:25:26 +02:00
eifr
cbe0478572 remove config 2025-02-06 10:19:23 +02:00
Mitchell Hashimoto
ae25ad4047 Introduce subsystem maintainers via CODEOWNERS
This commit introduces the proposed subsystem maintainer system for
Ghostty. This commit doesn't assign anyone yet to the subsystems, but
defines the CODEOWNERS file, creates the GitHub teams, and documents the
system.
2025-02-05 18:49:30 -08:00
eifr
1ce23c079e expand comment + rename 2025-02-05 23:35:12 +02:00
eifr
d87bfdff1a move color-mgmt to config 2025-02-05 22:48:28 +02:00
Mitchell Hashimoto
4eb5885017 macos: update Sparkle to 2.6.4 to workaround security issue (#5598)
https://github.com/ghostty-org/ghostty/security/dependabot/4
2025-02-05 10:57:32 -08:00
Mitchell Hashimoto
f2db80520a Fix cursor character not visible when cursor color matches background (#5570)
When cursor color matches the background color, the optimization that
skips rendering identical colors causes the character under cursor to
become invisible. This PR ensures characters at cursor position are
always rendered, maintaining cursor visibility regardless of color
settings.

<img width="1129" alt="image"
src="https://github.com/user-attachments/assets/b82387c1-2bfd-481d-b679-1a1f82d21bcb"
/>

cc @kpovel - Would you mind giving it a try? Any feedback would be
appreciated!

Fixes #5554
2025-02-05 10:47:31 -08:00
Mitchell Hashimoto
1d7f041f55 macos: update Sparkle to 2.6.4 to workaround security issue
https://github.com/ghostty-org/ghostty/security/dependabot/4
2025-02-05 10:45:10 -08:00
Mitchell Hashimoto
fbdee68667 Add a final newline to "No crash reports!" (#5583) 2025-02-05 10:35:28 -08:00
Mitchell Hashimoto
581b87b20c gtk: remove CSD styling when CSDs aren't used (#5581)
Fixes #2023
2025-02-05 10:33:48 -08:00
Ofir Levitan
f660ec8bd2 GTK: disable color management 2025-02-05 16:04:53 +02:00
Kat
47e50abe24 Add a final newline to "No crash reports!" 2025-02-05 08:22:58 +00:00
Leah Amelia Chen
0c1f3d724d gtk: remove CSD styling when CSDs aren't used
Fixes #2023
2025-02-05 09:01:46 +01:00
Bryan Lee
4a95e8e48c Fix cursor character not visible when cursor color matches background 2025-02-04 20:50:34 +08:00
Mitchell Hashimoto
f0d276062b Toggling visibility is now ignored when in fullscreen mode. (#5472)
This PR ensures visibility toggling is ignored when the currently
focused surface is fullscreen. This includes the following:

- Show/Hide All Terminals is ignored
- Using a binding to toggle is ignored

This ensures Ghostty doesn't unexpectedly lose focus or disappear and is
the expected behavior on macOS.
2025-02-03 13:57:48 -08:00
Mitchell Hashimoto
7b593b9d7c linux: ensure that group dir fd is closed (#5515)
The CLOEXEC flag on the fd will ensure that the directory is closed on
the child, but also need to close the fd in the parent.
2025-02-03 13:46:28 -08:00
Mitchell Hashimoto
0538d6db6a macOS: binding checks should never trigger preedit callbacks (#5559)
Fixes #5558

Binding checks would sometimes trigger preedit which would cause some
characters to leak through.

This is a bit of a band-aid. The real long term solution is noted in the
TODO comment in this commit, but I wanted to avoid regressions in a
patch release so I'm going to defer that to 1.2. This commit fixes the
main issue for 1.1.1.
2025-02-03 13:46:19 -08:00
Shaps Benkau
8d31f6ce2e Toggling visibility is now ignored when in fullscreen mode. 2025-02-03 13:44:23 -08:00
Mitchell Hashimoto
fc14c5b070 core: add explicit errors to src/os/env.zig (#5530) 2025-02-03 13:38:23 -08:00
Mitchell Hashimoto
fe6b46f4e7 core: add explicit errors to src/pty.zig (#5531) 2025-02-03 13:38:00 -08:00
Mitchell Hashimoto
c07b1ac2f0 gtk(x11): support server-side decorations (#5533)
Remind me to never touch Xlib code ever again.
2025-02-03 13:35:24 -08:00
Mitchell Hashimoto
730c6884f7 macOS: binding checks should never trigger preedit callbacks
Fixes #5558

Binding checks would sometimes trigger preedit which would cause some
characters to leak through.

This is a bit of a band-aid. The real long term solution is noted in the
TODO comment in this commit, but I wanted to avoid regressions in a
patch release so I'm going to defer that to 1.2. This commit fixes the
main issue for 1.1.1.
2025-02-03 13:32:47 -08:00
Jeffrey C. Ollie
b7fa8e5947 linux: ensure that group dir fd is closed
The CLOEXEC flag on the fd will ensure that the directory is closed on
the child, but also need to close the fd in the parent.
2025-02-03 15:10:06 -06:00
Jeffrey C. Ollie
3fdbd5f7ba core: fix up pty Zig error sets 2025-02-03 15:06:59 -06:00
Jeffrey C. Ollie
7f0d22a31e core: add explicit errors to src/pty.zig 2025-02-03 15:06:59 -06:00
Jeffrey C. Ollie
8607d463ff core: fix puralization of src/os/env.zig Errors->Error 2025-02-03 14:38:00 -06:00
Jeffrey C. Ollie
002cce4e81 core: handle src/os/env.zig errors on windows 2025-02-03 14:26:12 -06:00
Jeffrey C. Ollie
8660cdaad5 core: add explicit errors to src/os/env.zig 2025-02-03 14:26:12 -06:00
Leah Amelia Chen
d61ee46210 config: mention X11 support in window-decoration docs 2025-02-03 18:56:09 +01:00
Leah Amelia Chen
4cd49632b2 gtk(x11): support server-side decorations
Remind me to never touch Xlib code ever again.
2025-02-03 18:56:09 +01:00
Mitchell Hashimoto
cdd2099090 apprt/gtk: handle input methods that end preedit before commit (#5550)
Fixes #5494

When ibus/fcitx is not running (the GTK "simple" input method is
active), the preedit end event triggers _before_ the commit event. For
both ibus/fcitx, the opposite is true. We were relying on this ordering.

This commit changes the GTK input handling to not rely on this ordering.
Instead, we encode our composing state into the boolean state of whether
a key event is pressed. This happens before ANY input method events are
triggered.

Tested dead key handling on: X11/Wayland, ibus/fcitx/none.
2025-02-03 09:37:17 -08:00
Mitchell Hashimoto
f70ba14abd apprt/gtk: handle input methods that end preedit before commit
Fixes #5494

When ibus/fcitx is not running (the GTK "simple" input method is
active), the preedit end event triggers _before_ the commit event. For
both ibus/fcitx, the opposite is true. We were relying on this ordering.

This commit changes the GTK input handling to not rely on this ordering.
Instead, we encode our composing state into the boolean state of whether
a key event is pressed. This happens before ANY input method events are
triggered.

Tested dead key handling on: X11/Wayland, ibus/fcitx/none.
2025-02-03 09:24:29 -08:00
Mitchell Hashimoto
3b3e75c1dc gtk(wayland): respect window-decoration=none on GNOME (#5463)
This is, admittedly, a very silly bug. On GNOME the SSD protocol is not
available and past me just decided to always enable CSDs in that case,
*even when* `window-decoration = none`. I now question my own
intelligence.
2025-02-03 07:23:10 -08:00
Mitchell Hashimoto
61fd41116a linux: fix installation path of nautilus-python extension (#5469)
Fixes #5468
2025-02-03 07:19:01 -08:00
Mitchell Hashimoto
fe085001cb Update iTerm2 colorschemes (#5511)
Upstream revision:
db227d159a
2025-02-03 07:14:36 -08:00
Mitchell Hashimoto
1a1d3db207 Linux: Syscall errno handle (#5537)
When trying to run valgrind this incorrectly results in a correct
result, this is because `posix.errno` will use libc errno when linking
libc which ghostty does.


cf90dfd309/lib/std/posix.zig (L219-L221)

Since we are making the syscall directly we should not use this function
but rather use the return code directly on the enum, name from this
function seems odd to me (no zig experience) but it is the suggested
answer from zig (refer to issue below)
https://github.com/ziglang/zig/issues/22718

Note this definitely isnt much better than what we were doing before in
the case of running in valgrind

```text
error(linux-cgroup): unable to clone: os.linux.E__enum_81093.NOSYS
debug(io_thread): IO thread exited
warning(io_thread): error in io thread err=error.CloneError
warning(io_thread): abrupt io thread exit detected, starting xev to drain mailbox
debug(io_thread): io thread fully exiting after abnormal failure
```

opening a new tab shows
```
error starting IO thread: error.CloneError

The underlying shell or command was unable to be started.
This error is usually due to exhausting a system resource.
If this looks like a bug, please report it.

This terminal is non-functional. Please close it and try again.
```

this did not show on the original surface only on the new tab
2025-02-03 07:12:18 -08:00
Mitchell Hashimoto
79dd3776e2 Fix a typo in the docs for adjust-strikethrough-position and adjust-overline-position (#5543)
Presumably an error from copy-pasting documentation text.
2025-02-03 07:11:33 -08:00
Kat
1bd5ea6f73 Fix a typo in the docs for adjust-strikethrough-position and adjust-overline-position 2025-02-03 05:58:48 +00:00
rhodes-b
fb2516fbf6 comment on why to not use posix.errno 2025-02-02 16:35:20 -06:00
rhodes-b
bc156266c6 fix errno handle 2025-02-02 16:27:10 -06:00
mitchellh
5100f4ff7d deps: Update iTerm2 color schemes 2025-02-02 00:10:42 +00:00
Jeffrey C. Ollie
660d9dc671 linux: fix installation path of nautilus-python extension
Fixes #5468
2025-01-31 09:14:50 -06:00
Leah Amelia Chen
ac582ccf7c gtk(wayland): respect window-decoration=none on GNOME
This is, admittedly, a very silly bug. On GNOME the SSD protocol is not
available and past me just decided to always enable CSDs in that case,
*even when* `window-decoration = none`. I now question my own intelligence.
2025-01-31 12:17:05 +01:00
Mitchell Hashimoto
c5508e7d19 update version for development 2025-01-30 14:23:24 -08:00
Mitchell Hashimoto
dda242c96e ci: update publish job 2025-01-30 13:58:17 -08:00
Bryan Lee
0016199ec3 Extract keybind actions help generation into a dedicated module 2025-01-18 05:14:50 +08:00
Bryan Lee
8e2c55a5da Improve list-actions command documentation formatting
This commit fixes two issues with the `list-actions` command:

1. Ensures all actions are listed, including those without individual
   documentation but sharing docs with related actions
2. Improves documentation formatting with proper indentation and grouping
2025-01-18 05:14:49 +08:00
Bryan Lee
05fe3e7ec3 Ensure last action's documentation is properly generated
The issue was caused by the documentation generation logic not writing the final buffered content.
2025-01-18 05:14:49 +08:00
Bryan Lee
1b52365541 Add default documentation for undocumented keybind actions
Previously, `ghostty +list-actions` would only show actions that had doc
comments, making it difficult for users to discover all available actions.
This change ensures all actions are listed with appropriate documentation.

For actions without doc comments, we now generate a default message
encouraging contribution.
2025-01-18 05:14:49 +08:00
Bryan Lee
8da600a62e Add keyboard navigation for Terminal IO window
- Add J/K and arrow keys navigation in Terminal IO window
2024-12-30 01:00:09 +08:00
618 changed files with 43606 additions and 7280 deletions

2
.envrc
View File

@@ -4,3 +4,5 @@ if has nix; then
watch_file nix/{devShell,package,wraptest}.nix
use flake
fi
source_env_if_exists .envrc.local

6
.gitattributes vendored
View File

@@ -1,6 +1,12 @@
build.zig.zon.nix linguist-generated=true
build.zig.zon.txt linguist-generated=true
build.zig.zon.json linguist-generated=true
vendor/** linguist-vendored
website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored
pkg/cimgui/vendor/** linguist-vendored
pkg/glfw/wayland-headers/** linguist-vendored
pkg/libintl/config.h linguist-generated=true
pkg/libintl/libintl.h linguist-generated=true
pkg/simdutf/vendor/** linguist-vendored
src/terminal/res/** linguist-vendored

View File

@@ -50,5 +50,5 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
useDaemon: false # sometimes fails on short jobs
- name: Check Zig cache hash
run: nix develop -c ./nix/build-support/check-zig-cache-hash.sh
- name: Check Zig cache
run: nix develop -c ./nix/build-support/check-zig-cache.sh

View File

@@ -55,7 +55,7 @@ jobs:
- name: Download Staged Appcast
run: |
curl -L https://release.files.ghostty.org/${GHOSTTY_VERSION}/appcast-staged.xml
curl -L https://release.files.ghostty.org/${GHOSTTY_VERSION}/appcast-staged.xml > appcast-staged.xml
mv appcast-staged.xml appcast.xml
- name: Upload Appcast

View File

@@ -68,7 +68,7 @@ jobs:
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle

View File

@@ -136,7 +136,7 @@ jobs:
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle
@@ -298,7 +298,7 @@ jobs:
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle

View File

@@ -164,7 +164,7 @@ jobs:
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle

View File

@@ -10,13 +10,16 @@ jobs:
name: "Required Checks: Test"
runs-on: namespace-profile-ghostty-sm
needs:
- build
- build-bench
- build-flatpak
- build-linux
- build-linux-libghostty
- build-nix
- build-snap
- build-macos
- build-macos-matrix
- build-windows
- build-windows-cross
- test
- test-gtk
- test-sentry-linux
@@ -24,7 +27,9 @@ jobs:
- prettier
- alejandra
- typos
- translations
- test-pkg-linux
- test-debian-12
steps:
- id: status
name: Determine status
@@ -45,52 +50,6 @@ jobs:
echo "One or more required build workflows failed: ${{ steps.status.outputs.results }}"
exit 1
build:
strategy:
fail-fast: false
matrix:
os: ["namespace-profile-ghostty-md"]
target: [
aarch64-linux,
x86_64-linux,
x86-windows-gnu,
x86_64-windows-gnu,
# We don't support cross-compiling to macOS because the macOS build
# requires xcode due to the swift harness.
#aarch64-macos,
#x86_64-macos,
]
runs-on: ${{ matrix.os }}
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# Cross-compile the binary. We always use static building for this
# because its the only way to access the headers.
- name: Test Build
run: nix develop -c zig build -Dapp-runtime=glfw -Dtarget=${{ matrix.target }}
build-bench:
# We build benchmarks on large because it uses ReleaseFast
runs-on: namespace-profile-ghostty-lg
@@ -121,6 +80,73 @@ jobs:
- name: Build Benchmarks
run: nix develop -c zig build -Dapp-runtime=glfw -Demit-bench
build-flatpak:
strategy:
fail-fast: false
runs-on: namespace-profile-ghostty-sm
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build with Flatpak
run: |
nix develop -c \
zig build \
-Dflatpak=true
build-linux:
strategy:
fail-fast: false
matrix:
os: [namespace-profile-ghostty-md, namespace-profile-ghostty-md-arm64]
runs-on: ${{ matrix.os }}
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Test Build
run: nix develop -c zig build -Dapp-runtime=glfw
build-linux-libghostty:
runs-on: namespace-profile-ghostty-md
needs: test
@@ -202,10 +228,14 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access.
- name: Build GhosttyKit
run: nix develop -c zig build
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }}
# The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
@@ -238,35 +268,65 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
- name: Test All
run: |
# OpenGL
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
# Metal
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
- name: Build All
run: |
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
build-snap:
strategy:
fail-fast: false
matrix:
os:
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
runs-on: ${{ matrix.os }}
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
- run: sudo apt install -y udev
- run: sudo systemctl start systemd-udevd
- uses: snapcore/action-build@v1
build-windows:
runs-on: windows-2022
@@ -336,6 +396,52 @@ jobs:
shell: pwsh
run: Get-Content -Path ".\build.log"
build-windows-cross:
strategy:
fail-fast: false
matrix:
os: ["namespace-profile-ghostty-md"]
target: [
x86-windows-gnu,
x86_64-windows-gnu,
# We don't support cross-compiling to macOS or Linux because
# we require system libraries.
#aarch64-linux,
#x86_64-linux,
#aarch64-macos,
#x86_64-macos,
]
runs-on: ${{ matrix.os }}
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# Cross-compile the binary. We always use static building for this
# because its the only way to access the headers.
- name: Test Build
run: nix develop -c zig build -Dapp-runtime=glfw -Dtarget=${{ matrix.target }}
test:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-md
@@ -366,7 +472,7 @@ jobs:
run: nix develop -c zig build -Dapp-runtime=none test
- name: Test GTK Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs
- name: Test GLFW Build
run: nix develop -c zig build -Dapp-runtime=glfw
@@ -379,10 +485,9 @@ jobs:
strategy:
fail-fast: false
matrix:
adwaita: ["true", "false"]
x11: ["true", "false"]
wayland: ["true", "false"]
name: GTK adwaita=${{ matrix.adwaita }} x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
name: GTK x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
runs-on: namespace-profile-ghostty-sm
needs: test
env:
@@ -413,7 +518,6 @@ jobs:
nix develop -c \
zig build \
-Dapp-runtime=gtk \
-Dgtk-adwaita=${{ matrix.adwaita }} \
-Dgtk-x11=${{ matrix.x11 }} \
-Dgtk-wayland=${{ matrix.wayland }}
@@ -471,8 +575,12 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
- name: test
run: nix develop -c zig build test
run: nix develop -c zig build test --system ${{ steps.deps.outputs.deps }}
prettier:
if: github.repository == 'ghostty-org/ghostty'
@@ -555,6 +663,45 @@ jobs:
- name: typos check
run: nix develop -c typos
translations:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-sm
timeout-minutes: 60
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- uses: actions/checkout@v4 # Check out repo so we can lint it
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
skipPush: true
useDaemon: false # sometimes fails on short jobs
- name: check translations
run: |
old_pot=$(mktemp)
cp po/com.mitchellh.ghostty.pot "$old_pot"
nix develop -c zig build update-translations
# Compare previous POT to current POT
msgcmp "$old_pot" po/com.mitchellh.ghostty.pot --use-untranslated
# Compare all other POs to current POT
for f in po/*.po; do
# Ignore untranslated entries
msgcmp --use-untranslated "$f" po/com.mitchellh.ghostty.pot;
done
test-pkg-linux:
strategy:
fail-fast: false
@@ -589,3 +736,25 @@ jobs:
- name: Test ${{ matrix.pkg }} Build
run: |
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test"
test-debian-12:
name: Test build on Debian 12
runs-on: namespace-profile-ghostty-sm
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install and configure Namespace CLI
uses: namespacelabs/nscloud-setup@v0
- name: Configure Namespace powered Buildx
uses: namespacelabs/nscloud-setup-buildx-action@v0
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: src/build/docker/debian/Dockerfile
build-args: |
DISTRO_VERSION=12

View File

@@ -48,14 +48,14 @@ jobs:
run: |
# Only proceed if build.zig.zon has changed
if ! git diff --exit-code build.zig.zon; then
nix develop -c ./nix/build-support/check-zig-cache-hash.sh --update
nix develop -c ./nix/build-support/check-zig-cache-hash.sh
nix develop -c ./nix/build-support/check-zig-cache.sh --update
nix develop -c ./nix/build-support/check-zig-cache.sh
fi
# Verify the build still works. We choose an arbitrary build type
# as a canary instead of testing all build types.
- name: Test Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true
run: nix build .#ghostty
- name: Create pull request
uses: peter-evans/create-pull-request@v7
@@ -66,7 +66,9 @@ jobs:
commit-message: "deps: Update iTerm2 color schemes"
add-paths: |
build.zig.zon
nix/zigCacheHash.nix
build.zig.zon.nix
build.zig.zon.txt
build.zig.zon.json
body: |
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
labels: dependencies

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
.DS_Store
.vscode/
.direnv/
.envrc.local
.flatpak-builder/
zig-cache/
.zig-cache/

165
CODEOWNERS Normal file
View File

@@ -0,0 +1,165 @@
# This file documents the subsystem maintainers of the Ghostty project
# along with the responsibilities of a maintainer and how one can become
# a maintainer.
#
# Ghostty follows a subsystem maintainer model where distinguished
# contributors (with mutual agreement) are designated as maintainers of a
# specific subset of the project. A subsystem maintainer has more privileges
# and authority over a specific part of the project than a regular
# contributor and deference is given to them when making decisions about
# their subsystem.
#
# Ultimately Ghostty has a BDFL (Benevolent Dictator For Life) model
# currently with @mitchellh as the BDFL. The BDFL has the final say in all
# decisions and may override a maintainer's decision if necessary. I like to
# say its a BDFLFN (Benevolent Dictator For Life "For Now") model because
# long term I'd like to see the project be more community driven. But for
# now, early in its life, we're going with this model.
#
# ## Privileges
#
# - Authority to approve or reject pull requests in their subsystem.
# - Authority to moderate issues and discussions in their subsystem.
# - Authority to make roadmap and design decisions about their subsystem
# with input only from other subsystem maintainers.
#
# In all scenarios, the BDFL doesn't need to be consulted for decisions
# but may revert or override decisions if necessary. The expectation is
# that maintainers will be trusted to make the right decisions for their
# subsystem and this will be rare.
#
# ## Responsibilities
#
# Subsystem maintainership is a voluntary role and maintainers are not
# expected to dedicate any amount of time to the project. However, if a
# maintainer is inactive for a long period of time, they may be removed from
# the maintainers list to avoid bitrot or outdated information.
#
# Maintainers are expected to be exemplary members of the community and
# should be respectful, helpful, and professional in all interactions.
# This is both in regards to the community at large as well as other
# subsystem maintainers as well as @mitchellh.
#
# As technical leaders, maintainers are expected to be mindful about
# breaking changes, performance, user impact, and other technical
# considerations in their subsystem. They should be considerate of large
# changes and should be able to justify their decisions.
#
# Notably, maintainers have NO OBLIGATION to review pull requests or issues
# in their subsystem. They have full discretion to review or not review
# anything they want. This isn't a job! It is a role of trust and authority
# and the expectation is that maintainers will use their best judgement.
#
# ## Becoming a Maintainer
#
# Maintainer candidates are noticed and proposed by the community. Anyone
# may propose themselves or someone else as a maintainer. The BDFL along
# with existing maintainers will discuss and decide.
#
# Generally, we want to see consistent high quality contributions to a
# specific subsystem before considering someone as a maintainer. There isn't
# an exact number of contributions or time period required but generally
# we're looking for an order of a dozen or more contributions over a period of
# months, at least.
#
# # Subsystem List
#
# The subsystems don't fully cover the entirety of the Ghostty project but
# are created organically as experts in certain areas emerge. If you feel
# you are an expert in a certain area and would like to be a maintainer,
# please reach out to @mitchellh on Discord.
#
# (Alphabetical order)
#
# - @ghostty-org/font - All things font related including discovery,
# rasterization, shaping, coloring, etc.
#
# - @ghostty-org/gtk - Anything GTK-related in the project, primarily
# the GTK apprt. Also includes X11/Wayland integrations and general
# Linux support.
#
# - @ghostty-org/localization/* - Anything related to localization
# for a specific locale.
#
# - @ghostty-org/macos - The Ghostty macOS app and any macOS-specific
# features, configurations, etc.
#
# - @ghostty-org/packaging/snap - Ghostty snap packaging
# (https://snapcraft.io/ghostty)
#
# - @ghostty-org/renderer - Ghostty rendering subsystem, including the
# rendering abstractions as well as specific renderers like OpenGL
# and Metal.
#
# - @ghostty-org/shell - Ghostty shell integration, including shell
# completions, shell detection, and any other shell interactions.
#
# - @ghostty-org/terminal - The terminal emulator subsystem, including
# subprocess management and pty handling, escape sequence parsing,
# key encoding, etc.
#
# ## Outside of Ghostty
#
# Other "subsystems" exist outside of Ghostty and will not be represented
# in this CODEOWNERS file:
#
# - @ghostty-org/discord-bot - Maintainers of the Ghostty Discord bot.
#
# - @ghostty-org/website - Maintainers of the Ghostty website.
# Font
/src/font/ @ghostty-org/font
/pkg/fontconfig/ @ghostty-org/font
/pkg/freetype/ @ghostty-org/font
/pkg/harfbuzz/ @ghostty-org/font
# GTK
/src/apprt/gtk/ @ghostty-org/gtk
/src/os/cgroup.zig @ghostty-org/gtk
/src/os/flatpak.zig @ghostty-org/gtk
/dist/linux/ @ghostty-org/gtk
# macOS
#
# This includes libghostty because the macOS apprt is built on top of
# libghostty and often requires or is impacted by changes to libghostty.
# macOS subsystem maintainers are expected to only work on libghostty
# insofar as it impacts the macOS apprt.
/include/ghostty.h @ghostty-org/macos
/src/apprt/embedded.zig @ghostty-org/macos
/src/os/cf_release_thread.zig @ghostty-org/macos
/src/os/macos.zig @ghostty-org/macos
/macos/ @ghostty-org/macos
/dist/macos/ @ghostty-org/macos
/pkg/apple-sdk/ @ghostty-org/macos
/pkg/macos/ @ghostty-org/macos
# Renderer
/src/renderer.zig @ghostty-org/renderer
/src/renderer/ @ghostty-org/renderer
/pkg/glslang/ @ghostty-org/renderer
/pkg/opengl/ @ghostty-org/renderer
/pkg/spirv-cross/ @ghostty-org/renderer
/pkg/wuffs/ @ghostty-org/renderer
# Shell
/src/shell-integration/ @ghostty-org/shell
/src/termio/shell-integration.zig @ghostty-org/shell
# Terminal
/src/simd/ @ghostty-org/terminal
/src/input/KeyEncoder.zig @ghostty-org/terminal
/src/terminal/ @ghostty-org/terminal
/src/terminfo/ @ghostty-org/terminal
/src/unicode/ @ghostty-org/terminal
/src/Surface.zig @ghostty-org/terminal
/src/surface_mouse.zig @ghostty-org/terminal
# Localization
/po/README_TRANSLATORS.md @ghostty-org/localization
/po/com.mitchellh.ghostty.pot @ghostty-org/localization
/po/de_DE.UTF-8.po @ghostty-org/de_DE
/po/zh_CN.UTF-8.po @ghostty-org/zh_CN
# Packaging - Snap
/snap/ @ghostty-org/snap

View File

@@ -21,6 +21,15 @@ All issues are actionable. Pick one and start working on it. Thank you.
If you need help or guidance, comment on the issue. Issues that are extra
friendly to new contributors are tagged with "contributor friendly".
**I'd like to translate Ghostty to my language!**
We have written a [Translator's Guide](po/README_TRANSLATORS.md) for
everyone interested in contributing translations to Ghostty.
Translations usually do not need to go through the process of issue triage
and you can submit pull requests directly, although please make sure that
our [Style Guide](po/README_TRANSLATORS.md#style-guide) is followed before
submission.
**I have a bug!**
1. Search the issue tracker and discussions for similar issues.
@@ -78,6 +87,93 @@ pull request will be accepted with a high degree of certainty.
> not open a WIP pull request to discuss a feature. Instead, use a discussion
> and link to your branch.
# Developer Guide
> [!NOTE]
>
> **The remainder of this file is dedicated to developers actively
> working on Ghostty.** If you're a user reporting an issue, you can
> ignore the rest of this document.
## Including and Updating Translations
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
## Input Stack Testing
The input stack is the part of the codebase that starts with a
key event and ends with text encoding being sent to the pty (it
does not include _rendering_ the text, which is part of the
font or rendering stack).
If you modify any part of the input stack, you must manually verify
all the following input cases work properly. We unfortunately do
not automate this in any way, but if we can do that one day that'd
save a LOT of grief and time.
Note: this list may not be exhaustive, I'm still working on it.
### Linux IME
IME (Input Method Editors) are a common source of bugs in the input stack,
especially on Linux since there are multiple different IME systems
interacting with different windowing systems and application frameworks
all written by different organizations.
The following matrix should be tested to ensure that all IME input works
properly:
1. Wayland, X11
2. ibus, fcitx, none
3. Dead key input (e.g. Spanish), CJK (e.g. Japanese), Emoji, Unicode Hex
4. ibus versions: 1.5.29, 1.5.30, 1.5.31 (each exhibit slightly different behaviors)
> [!NOTE]
>
> This is a **work in progress**. I'm still working on this list and it
> is not complete. As I find more test cases, I will add them here.
#### Dead Key Input
Set your keyboard layout to "Spanish" (or another layout that uses dead keys).
1. Launch Ghostty
2. Press `'`
3. Press `a`
4. Verify that `á` is displayed
Note that the dead key may or may not show a preedit state visually.
For ibus and fcitx it does but for the "none" case it does not. Importantly,
the text should be correct when it is sent to the pty.
We should also test canceling dead key input:
1. Launch Ghostty
2. Press `'`
3. Press escape
4. Press `a`
5. Verify that `a` is displayed (no diacritic)
#### CJK Input
Configure fcitx or ibus with a keyboard layout like Japanese or Mozc. The
exact layout doesn't matter.
1. Launch Ghostty
2. Press `Ctrl+Shift` to switch to "Hiragana"
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
4. Press `Enter`
5. Verify that `こん` is displayed in the terminal.
We should also test switching input methods while preedit is active, which
should commit the text:
1. Launch Ghostty
2. Press `Ctrl+Shift` to switch to "Hiragana"
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
4. Press `Ctrl+Shift` to switch to another layout (any)
5. Verify that `こん` is displayed in the terminal as committed text.
## Nix Virtual Machines
Several Nix virtual machine definitions are provided by the project for testing

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:
@@ -55,7 +48,7 @@ To find the version of Zig required to build Ghostty, check the `required_zig`
constant in `build.zig`. You don't need to know Zig to extract this information.
This version will always be an official released version of Zig.
For example, at the time of writing this document, Ghostty requires Zig 0.13.0.
For example, at the time of writing this document, Ghostty requires Zig 0.14.0.
## Building Ghostty
@@ -88,6 +81,13 @@ for system packages which separate a build and install step, since the
install step can then be done with a `mv` or `cp` command (from `/tmp/ghostty`
to wherever the package manager expects it).
> [!NOTE]
>
> **Version 1.1.1 and 1.1.2 are missing `fetch-zig-cache.sh`.** This was
> an oversight on the release process. You can use the script from version
> 1.1.0 to fetch the Zig cache for these versions. Future versions will
> restore the script.
### Build Options
Ghostty uses the Zig build system. You can see all available build options by

View File

@@ -3,7 +3,7 @@ const builtin = @import("builtin");
const buildpkg = @import("src/build/main.zig");
comptime {
buildpkg.requireZig("0.13.0");
buildpkg.requireZig("0.14.0");
}
pub fn build(b: *std.Build) !void {
@@ -11,6 +11,7 @@ pub fn build(b: *std.Build) !void {
// Ghostty resources like terminfo, shell integration, themes, etc.
const resources = try buildpkg.GhosttyResources.init(b, &config);
const i18n = try buildpkg.GhosttyI18n.init(b, &config);
// Ghostty dependencies used by many artifacts.
const deps = try buildpkg.SharedDeps.init(b, &config);
@@ -39,6 +40,7 @@ pub fn build(b: *std.Build) !void {
if (config.app_runtime != .none) {
exe.install();
resources.install();
i18n.install();
}
// Libghostty
@@ -48,7 +50,7 @@ pub fn build(b: *std.Build) !void {
// As such, these build steps are lacking. For example, the Darwin
// build only produces an xcframework.
if (config.app_runtime == .none) {
if (config.target.result.isDarwin()) darwin: {
if (config.target.result.os.tag.isDarwin()) darwin: {
if (!config.emit_xcframework) break :darwin;
// Build the xcframework
@@ -58,6 +60,7 @@ pub fn build(b: *std.Build) !void {
// The xcframework build always installs resources because our
// macOS xcode project contains references to them.
resources.install();
i18n.install();
// If we aren't emitting docs we need to emit a placeholder so
// our macOS xcodeproject builds.
@@ -80,6 +83,16 @@ pub fn build(b: *std.Build) !void {
{
const run_cmd = b.addRunArtifact(exe.exe);
if (b.args) |args| run_cmd.addArgs(args);
// Set the proper resources dir so things like shell integration
// work correctly. If we're running `zig build run` in Ghostty,
// this also ensures it overwrites the release one with our debug
// build.
run_cmd.setEnvironmentVariable(
"GHOSTTY_RESOURCES_DIR",
b.getInstallPath(.prefix, "share/ghostty"),
);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
@@ -103,4 +116,11 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&test_run.step);
}
}
// update-translations does what it sounds like and updates the "pot"
// files. These should be committed to the repo.
{
const step = b.step("update-translations", "Update translation files");
step.dependOn(i18n.update_step);
}
}

View File

@@ -1,86 +1,111 @@
.{
.name = "ghostty",
.version = "1.1.0",
.name = .ghostty,
.version = "1.1.3",
.paths = .{""},
.fingerprint = 0x64407a2a0b4147e5,
.dependencies = .{
// Zig libs
.libxev = .{
.url = "https://github.com/mitchellh/libxev/archive/31eed4e337fed7b0149319e5cdbb62b848c24fbd.tar.gz",
.hash = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c",
},
.mach_glfw = .{
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
.hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62",
// mitchellh/libxev
.url = "https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz",
.hash = "libxev-0.0.0-86vtc-ziEgDbLP0vihUn1MhsxNKY4GJEga6BEr7oyHpz",
.lazy = true,
},
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b",
.hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f",
// rockorager/libvaxis
.url = "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447",
.hash = "vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti",
.lazy = true,
},
.z2d = .{
.url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
.hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
// vancluever/z2d
.url = "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz",
.hash = "z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C",
.lazy = true,
},
.zig_objc = .{
.url = "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz",
.hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634",
// mitchellh/zig-objc
.url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt",
.lazy = true,
},
.zig_js = .{
.url = "https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz",
.hash = "12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc",
// mitchellh/zig-js
.url = "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz",
.hash = "N-V-__8AAB9YCQBaZtQjJZVndk-g_GDIK-NTZcIa63bFp9yZ",
.lazy = true,
},
.ziglyph = .{
.url = "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz",
.hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
.hash = "ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf",
.lazy = true,
},
.zig_wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
.hash = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38",
// codeberg ifreund/zig-wayland
.url = "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz",
.hash = "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy",
},
.zf = .{
.url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd",
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
// natecraddock/zf
.url = "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz",
.hash = "zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5",
.lazy = true,
},
.gobject = .{
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
// Temporary until we generate them at build time automatically.
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
.hash = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR",
.lazy = true,
},
// C libs
.cimgui = .{ .path = "./pkg/cimgui" },
.fontconfig = .{ .path = "./pkg/fontconfig" },
.freetype = .{ .path = "./pkg/freetype" },
.harfbuzz = .{ .path = "./pkg/harfbuzz" },
.highway = .{ .path = "./pkg/highway" },
.libpng = .{ .path = "./pkg/libpng" },
.macos = .{ .path = "./pkg/macos" },
.oniguruma = .{ .path = "./pkg/oniguruma" },
.opengl = .{ .path = "./pkg/opengl" },
.sentry = .{ .path = "./pkg/sentry" },
.simdutf = .{ .path = "./pkg/simdutf" },
.utfcpp = .{ .path = "./pkg/utfcpp" },
.wuffs = .{ .path = "./pkg/wuffs" },
.zlib = .{ .path = "./pkg/zlib" },
.cimgui = .{ .path = "./pkg/cimgui", .lazy = true },
.fontconfig = .{ .path = "./pkg/fontconfig", .lazy = true },
.freetype = .{ .path = "./pkg/freetype", .lazy = true },
.glfw = .{ .path = "./pkg/glfw", .lazy = true },
.gtk4_layer_shell = .{ .path = "./pkg/gtk4-layer-shell", .lazy = true },
.harfbuzz = .{ .path = "./pkg/harfbuzz", .lazy = true },
.highway = .{ .path = "./pkg/highway", .lazy = true },
.libintl = .{ .path = "./pkg/libintl", .lazy = true },
.libpng = .{ .path = "./pkg/libpng", .lazy = true },
.macos = .{ .path = "./pkg/macos", .lazy = true },
.oniguruma = .{ .path = "./pkg/oniguruma", .lazy = true },
.opengl = .{ .path = "./pkg/opengl", .lazy = true },
.sentry = .{ .path = "./pkg/sentry", .lazy = true },
.simdutf = .{ .path = "./pkg/simdutf", .lazy = true },
.utfcpp = .{ .path = "./pkg/utfcpp", .lazy = true },
.wuffs = .{ .path = "./pkg/wuffs", .lazy = true },
.zlib = .{ .path = "./pkg/zlib", .lazy = true },
// Shader translation
.glslang = .{ .path = "./pkg/glslang" },
.spirv_cross = .{ .path = "./pkg/spirv-cross" },
.glslang = .{ .path = "./pkg/glslang", .lazy = true },
.spirv_cross = .{ .path = "./pkg/spirv-cross", .lazy = true },
// Wayland
.wayland = .{
.url = "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz",
.hash = "12202cdac858abc52413a6c6711d5026d2d3c8e13f95ca2c327eade0736298bb021f",
.hash = "N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t",
.lazy = true,
},
.wayland_protocols = .{
.url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
.hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef",
.hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
.lazy = true,
},
.plasma_wayland_protocols = .{
.url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86",
.hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
.url = "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz",
.hash = "N-V-__8AAKYZBAB-CFHBKs3u4JkeiT4BMvyHu3Y5aaWF3Bbs",
.lazy = true,
},
// Other
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
.iterm2_themes = .{
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/0e23daf59234fc892cba949562d7bf69204594bb.tar.gz",
.hash = "12204fc99743d8232e691ac22e058519bfc6ea92de4a11c6dba59b117531c847cd6a",
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz",
.hash = "N-V-__8AAOCKKgR3_ixUa8LPz0Zz8okA9C6A3kXVLbmdLsGP",
.lazy = true,
},
},
}

172
build.zig.zon.json generated Normal file
View File

@@ -0,0 +1,172 @@
{
"N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ": {
"name": "breakpad",
"url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
"hash": "sha256-bMqYlD0amQdmzvYQd8Ca/1k4Bj/heh7+EijlQSttatk="
},
"N-V-__8AAIrfdwARSa-zMmxWwFuwpXf1T3asIN7s5jqi9c1v": {
"name": "fontconfig",
"url": "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz",
"hash": "sha256-O6LdkhWHGKzsXKrxpxYEO1qgVcJ7CB2RSvPMtA3OilU="
},
"N-V-__8AAKLKpwC4H27Ps_0iL3bPkQb-z6ZVSrB-x_3EEkub": {
"name": "freetype",
"url": "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz",
"hash": "sha256-QnIB9dUVFnDQXB9bRb713aHy592XHvVPD+qqf/0quQw="
},
"N-V-__8AADcZkgn4cMhTUpIz6mShCKyqqB-NBtf_S2bHaTC-": {
"name": "gettext",
"url": "https://deps.files.ghostty.org/gettext-0.24.tar.gz",
"hash": "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc="
},
"N-V-__8AADTkRwBjUvVwTLOnV96QhN0J5Nyg7YzvnISe-Eax": {
"name": "glfw",
"url": "https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip",
"hash": "sha256-k7wBKiQpgxBhqHRwSEgZjmfncltlGG8BgY3FhyycM5E="
},
"N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy": {
"name": "glslang",
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
"gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR": {
"name": "gobject",
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
"hash": "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI="
},
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
"name": "gtk4_layer_shell",
"url": "https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz",
"hash": "sha256-mChCgSYKXu9bT2OlXxbEv2p4ihAgptsDfssPcfozaYg="
},
"N-V-__8AAKa0rgW4WI8QbJlq8QJJv6CSxvsvNfussVBe9Heg": {
"name": "harfbuzz",
"url": "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz",
"hash": "sha256-nxygiYE7BZRK0c6MfgGCEwJtNdybq0gKIeuHaDg5ZVY="
},
"N-V-__8AAGmZhABbsPJLfbqrh6JTHsXhY6qCaLAQyx25e0XE": {
"name": "highway",
"url": "https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz",
"hash": "sha256-h9T4iT704I8iSXNgj/6/lCaKgTgLp5wS6IQZaMgKohI="
},
"N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3": {
"name": "imgui",
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
},
"N-V-__8AAOCKKgR3_ixUa8LPz0Zz8okA9C6A3kXVLbmdLsGP": {
"name": "iterm2_themes",
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz",
"hash": "sha256-lt4fE77C41C/CpQweF+w/oy96xZJEzPEFTy4TSFEYbw="
},
"N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": {
"name": "libpng",
"url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz",
"hash": "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo="
},
"libxev-0.0.0-86vtc-ziEgDbLP0vihUn1MhsxNKY4GJEga6BEr7oyHpz": {
"name": "libxev",
"url": "https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz",
"hash": "sha256-oKZqA9d79jHnp/HsqJWQE33Ffn5Ee5G4VnlQepQuY4o="
},
"N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK": {
"name": "libxml2",
"url": "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz",
"hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU="
},
"N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c": {
"name": "oniguruma",
"url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz",
"hash": "sha256-ABqhIC54RI9MC/GkjHblVodrNvFtks4yB+zP1h2Z8qA="
},
"N-V-__8AADYiAAB_80AWnH1AxXC0tql9thT-R-DYO1gBqTLc": {
"name": "pixels",
"url": "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz",
"hash": "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro="
},
"N-V-__8AAKYZBAB-CFHBKs3u4JkeiT4BMvyHu3Y5aaWF3Bbs": {
"name": "plasma_wayland_protocols",
"url": "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz",
"hash": "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE="
},
"N-V-__8AAPlZGwBEa-gxrcypGBZ2R8Bse4JYSfo_ul8i2jlG": {
"name": "sentry",
"url": "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz",
"hash": "sha256-KsZJfMjWGo0xCT5HrduMmyxFsWsHBbszSoNbZCPDGN8="
},
"N-V-__8AANb6pwD7O1WG6L5nvD_rNMvnSc9Cpg1ijSlTYywv": {
"name": "spirv_cross",
"url": "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz",
"hash": "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M="
},
"N-V-__8AAHffAgDU0YQmynL8K35WzkcnMUmBVQHQ0jlcKpjH": {
"name": "utfcpp",
"url": "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz",
"hash": "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8="
},
"vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti": {
"name": "vaxis",
"url": "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447",
"hash": "sha256-iONEySjPeD0WYJ93fw5mxT+0pVfUO/m6008J/LXjQkA="
},
"N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t": {
"name": "wayland",
"url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz",
"hash": "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0="
},
"N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S": {
"name": "wayland_protocols",
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
},
"N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": {
"name": "wuffs",
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
"z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C": {
"name": "z2d",
"url": "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz",
"hash": "sha256-PEKVSUZ6teRbDyhFPWSiuBSe40pgr0kVRivIY8Cn8HQ="
},
"zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5": {
"name": "zf",
"url": "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz",
"hash": "sha256-xVva07TAYlVv4E4PKe2wUj86a6Ky2YC30YBgtbvNKvw="
},
"zg-0.13.4-AAAAAGiZ7QLz4pvECFa_wG4O4TP4FLABHHbemH2KakWM": {
"name": "zg",
"url": "git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc",
"hash": "sha256-fo3l6cjkrr/godElTGnQzalBsasN7J73IDIRmw7v1gA="
},
"N-V-__8AAB9YCQBaZtQjJZVndk-g_GDIK-NTZcIa63bFp9yZ": {
"name": "zig_js",
"url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz",
"hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0="
},
"zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt": {
"name": "zig_objc",
"url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz",
"hash": "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw="
},
"wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy": {
"name": "zig_wayland",
"url": "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz",
"hash": "sha256-E77GZ15APYbbO1WzmuJi8eG9/iQFbc2CgkNBxjCLUhk="
},
"zigimg-0.1.0-lly-O4heEADSRxoTwJwrD3TBfUob9052sIgb9SL8Iz-A": {
"name": "zigimg",
"url": "git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0",
"hash": "sha256-Rr+mAfbLOoaxHOwCug+0cWCmW9gDhjhnaO2J/Oik9HI="
},
"ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf": {
"name": "ziglyph",
"url": "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz",
"hash": "sha256-cse98+Ft8QUjX+P88yyYfaxJOJGQ9M7Ymw7jFxDz89k="
},
"N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o": {
"name": "zlib",
"url": "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz",
"hash": "sha256-F+iIY/NgBnKrSRgvIXKBtvxNPHYr3jYZNeQ2qVIU0Fw="
}
}

356
build.zig.zon.nix generated Normal file
View File

@@ -0,0 +1,356 @@
# generated by zon2nix (https://github.com/jcollie/zon2nix)
{
lib,
linkFarm,
fetchurl,
fetchgit,
runCommandLocal,
zig_0_14,
name ? "zig-packages",
}: let
unpackZigArtifact = {
name,
artifact,
}:
runCommandLocal name
{
nativeBuildInputs = [zig_0_14];
}
''
hash="$(zig fetch --global-cache-dir "$TMPDIR" ${artifact})"
mv "$TMPDIR/p/$hash" "$out"
chmod 755 "$out"
'';
fetchZig = {
name,
url,
hash,
}: let
artifact = fetchurl {inherit url hash;};
in
unpackZigArtifact {inherit name artifact;};
fetchGitZig = {
name,
url,
hash,
}: let
parts = lib.splitString "#" url;
url_base = builtins.elemAt parts 0;
url_without_query = builtins.elemAt (lib.splitString "?" url_base) 0;
rev_base = builtins.elemAt parts 1;
rev =
if builtins.match "^[a-fA-F0-9]{40}$" rev_base != null
then rev_base
else "refs/heads/${rev_base}";
in
fetchgit {
inherit name rev hash;
url = url_without_query;
deepClone = false;
};
fetchZigArtifact = {
name,
url,
hash,
}: let
parts = lib.splitString "://" url;
proto = builtins.elemAt parts 0;
path = builtins.elemAt parts 1;
fetcher = {
"git+http" = fetchGitZig {
inherit name hash;
url = "http://${path}";
};
"git+https" = fetchGitZig {
inherit name hash;
url = "https://${path}";
};
http = fetchZig {
inherit name hash;
url = "http://${path}";
};
https = fetchZig {
inherit name hash;
url = "https://${path}";
};
};
in
fetcher.${proto};
in
linkFarm name [
{
name = "N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ";
path = fetchZigArtifact {
name = "breakpad";
url = "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz";
hash = "sha256-bMqYlD0amQdmzvYQd8Ca/1k4Bj/heh7+EijlQSttatk=";
};
}
{
name = "N-V-__8AAIrfdwARSa-zMmxWwFuwpXf1T3asIN7s5jqi9c1v";
path = fetchZigArtifact {
name = "fontconfig";
url = "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz";
hash = "sha256-O6LdkhWHGKzsXKrxpxYEO1qgVcJ7CB2RSvPMtA3OilU=";
};
}
{
name = "N-V-__8AAKLKpwC4H27Ps_0iL3bPkQb-z6ZVSrB-x_3EEkub";
path = fetchZigArtifact {
name = "freetype";
url = "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz";
hash = "sha256-QnIB9dUVFnDQXB9bRb713aHy592XHvVPD+qqf/0quQw=";
};
}
{
name = "N-V-__8AADcZkgn4cMhTUpIz6mShCKyqqB-NBtf_S2bHaTC-";
path = fetchZigArtifact {
name = "gettext";
url = "https://deps.files.ghostty.org/gettext-0.24.tar.gz";
hash = "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc=";
};
}
{
name = "N-V-__8AADTkRwBjUvVwTLOnV96QhN0J5Nyg7YzvnISe-Eax";
path = fetchZigArtifact {
name = "glfw";
url = "https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip";
hash = "sha256-k7wBKiQpgxBhqHRwSEgZjmfncltlGG8BgY3FhyycM5E=";
};
}
{
name = "N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy";
path = fetchZigArtifact {
name = "glslang";
url = "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz";
hash = "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U=";
};
}
{
name = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR";
path = fetchZigArtifact {
name = "gobject";
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst";
hash = "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI=";
};
}
{
name = "N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr";
path = fetchZigArtifact {
name = "gtk4_layer_shell";
url = "https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz";
hash = "sha256-mChCgSYKXu9bT2OlXxbEv2p4ihAgptsDfssPcfozaYg=";
};
}
{
name = "N-V-__8AAKa0rgW4WI8QbJlq8QJJv6CSxvsvNfussVBe9Heg";
path = fetchZigArtifact {
name = "harfbuzz";
url = "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz";
hash = "sha256-nxygiYE7BZRK0c6MfgGCEwJtNdybq0gKIeuHaDg5ZVY=";
};
}
{
name = "N-V-__8AAGmZhABbsPJLfbqrh6JTHsXhY6qCaLAQyx25e0XE";
path = fetchZigArtifact {
name = "highway";
url = "https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz";
hash = "sha256-h9T4iT704I8iSXNgj/6/lCaKgTgLp5wS6IQZaMgKohI=";
};
}
{
name = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3";
path = fetchZigArtifact {
name = "imgui";
url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz";
hash = "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=";
};
}
{
name = "N-V-__8AAOCKKgR3_ixUa8LPz0Zz8okA9C6A3kXVLbmdLsGP";
path = fetchZigArtifact {
name = "iterm2_themes";
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz";
hash = "sha256-lt4fE77C41C/CpQweF+w/oy96xZJEzPEFTy4TSFEYbw=";
};
}
{
name = "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD";
path = fetchZigArtifact {
name = "libpng";
url = "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz";
hash = "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo=";
};
}
{
name = "libxev-0.0.0-86vtc-ziEgDbLP0vihUn1MhsxNKY4GJEga6BEr7oyHpz";
path = fetchZigArtifact {
name = "libxev";
url = "https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz";
hash = "sha256-oKZqA9d79jHnp/HsqJWQE33Ffn5Ee5G4VnlQepQuY4o=";
};
}
{
name = "N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK";
path = fetchZigArtifact {
name = "libxml2";
url = "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz";
hash = "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU=";
};
}
{
name = "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c";
path = fetchZigArtifact {
name = "oniguruma";
url = "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz";
hash = "sha256-ABqhIC54RI9MC/GkjHblVodrNvFtks4yB+zP1h2Z8qA=";
};
}
{
name = "N-V-__8AADYiAAB_80AWnH1AxXC0tql9thT-R-DYO1gBqTLc";
path = fetchZigArtifact {
name = "pixels";
url = "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz";
hash = "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro=";
};
}
{
name = "N-V-__8AAKYZBAB-CFHBKs3u4JkeiT4BMvyHu3Y5aaWF3Bbs";
path = fetchZigArtifact {
name = "plasma_wayland_protocols";
url = "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz";
hash = "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE=";
};
}
{
name = "N-V-__8AAPlZGwBEa-gxrcypGBZ2R8Bse4JYSfo_ul8i2jlG";
path = fetchZigArtifact {
name = "sentry";
url = "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz";
hash = "sha256-KsZJfMjWGo0xCT5HrduMmyxFsWsHBbszSoNbZCPDGN8=";
};
}
{
name = "N-V-__8AANb6pwD7O1WG6L5nvD_rNMvnSc9Cpg1ijSlTYywv";
path = fetchZigArtifact {
name = "spirv_cross";
url = "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz";
hash = "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M=";
};
}
{
name = "N-V-__8AAHffAgDU0YQmynL8K35WzkcnMUmBVQHQ0jlcKpjH";
path = fetchZigArtifact {
name = "utfcpp";
url = "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz";
hash = "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8=";
};
}
{
name = "vaxis-0.1.0-BWNV_MHyCAA0rNbPTr50Z44PyEdNP9zQSnHcXBXoo3Ti";
path = fetchZigArtifact {
name = "vaxis";
url = "git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447";
hash = "sha256-iONEySjPeD0WYJ93fw5mxT+0pVfUO/m6008J/LXjQkA=";
};
}
{
name = "N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t";
path = fetchZigArtifact {
name = "wayland";
url = "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz";
hash = "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0=";
};
}
{
name = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S";
path = fetchZigArtifact {
name = "wayland_protocols";
url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz";
hash = "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=";
};
}
{
name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs";
path = fetchZigArtifact {
name = "wuffs";
url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz";
hash = "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=";
};
}
{
name = "z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C";
path = fetchZigArtifact {
name = "z2d";
url = "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz";
hash = "sha256-PEKVSUZ6teRbDyhFPWSiuBSe40pgr0kVRivIY8Cn8HQ=";
};
}
{
name = "zf-0.10.3-OIRy8bKIAADhjqtdjVaDfONRuI7RVl5gMbhCoOwiBWV5";
path = fetchZigArtifact {
name = "zf";
url = "https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz";
hash = "sha256-xVva07TAYlVv4E4PKe2wUj86a6Ky2YC30YBgtbvNKvw=";
};
}
{
name = "zg-0.13.4-AAAAAGiZ7QLz4pvECFa_wG4O4TP4FLABHHbemH2KakWM";
path = fetchZigArtifact {
name = "zg";
url = "git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc";
hash = "sha256-fo3l6cjkrr/godElTGnQzalBsasN7J73IDIRmw7v1gA=";
};
}
{
name = "N-V-__8AAB9YCQBaZtQjJZVndk-g_GDIK-NTZcIa63bFp9yZ";
path = fetchZigArtifact {
name = "zig_js";
url = "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz";
hash = "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0=";
};
}
{
name = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt";
path = fetchZigArtifact {
name = "zig_objc";
url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz";
hash = "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw=";
};
}
{
name = "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy";
path = fetchZigArtifact {
name = "zig_wayland";
url = "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz";
hash = "sha256-E77GZ15APYbbO1WzmuJi8eG9/iQFbc2CgkNBxjCLUhk=";
};
}
{
name = "zigimg-0.1.0-lly-O4heEADSRxoTwJwrD3TBfUob9052sIgb9SL8Iz-A";
path = fetchZigArtifact {
name = "zigimg";
url = "git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0";
hash = "sha256-Rr+mAfbLOoaxHOwCug+0cWCmW9gDhjhnaO2J/Oik9HI=";
};
}
{
name = "ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf";
path = fetchZigArtifact {
name = "ziglyph";
url = "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz";
hash = "sha256-cse98+Ft8QUjX+P88yyYfaxJOJGQ9M7Ymw7jFxDz89k=";
};
}
{
name = "N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o";
path = fetchZigArtifact {
name = "zlib";
url = "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz";
hash = "sha256-F+iIY/NgBnKrSRgvIXKBtvxNPHYr3jYZNeQ2qVIU0Fw=";
};
}
]

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

@@ -0,0 +1,34 @@
git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc
git+https://github.com/TUSF/zigimg#0ce4eca3560d5553b13263d6b6bb72e146dd43d0
git+https://github.com/rockorager/libvaxis#4182b7fa42f27cf14a71dbdb54cfd82c5c6e3447
https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz
https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
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/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz
https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz
https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz
https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz
https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz
https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz
https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz
https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz
https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.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/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz
https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz
https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz
https://github.com/natecraddock/zf/archive/1039cf75447a8d5b8d481fedb914fe848d246276.tar.gz
https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz

62
flake.lock generated
View File

@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -21,11 +21,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -36,11 +36,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1733423277,
"narHash": "sha256-TxabjxEgkNbCGFRHgM/b9yZWlBj60gUOUnRT/wbVQR8=",
"lastModified": 1741992157,
"narHash": "sha256-nlIfTsTrMSksEJc1f7YexXiPVuzD1gOfeN1ggwZyUoc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e36963a147267afc055f7cf65225958633e536bf",
"rev": "da4b122f63095ca1199bd4d526f9e26426697689",
"type": "github"
},
"original": {
@@ -52,11 +52,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1733229606,
"narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=",
"lastModified": 1741865919,
"narHash": "sha256-4thdbnP6dlbdq+qZWTsm4ffAwoS8Tiq1YResB+RP6WE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550",
"rev": "573c650e8a14b2faa0041645ab18aed7e60f0c9a",
"type": "github"
},
"original": {
@@ -69,9 +69,11 @@
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs-stable": "nixpkgs-stable",
"nixpkgs-unstable": "nixpkgs-unstable",
"zig": "zig"
"zig": "zig",
"zon2nix": "zon2nix"
}
},
"systems": {
@@ -92,17 +94,19 @@
"zig": {
"inputs": {
"flake-compat": [],
"flake-utils": "flake-utils",
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs-stable"
]
},
"locked": {
"lastModified": 1717848532,
"narHash": "sha256-d+xIUvSTreHl8pAmU1fnmkfDTGQYCn2Rb/zOwByxS2M=",
"lastModified": 1741825901,
"narHash": "sha256-aeopo+aXg5I2IksOPFN79usw7AeimH1+tjfuMzJHFdk=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "02fc5cc555fc14fda40c42d7c3250efa43812b43",
"rev": "0b14285e283f5a747f372fb2931835dd937c4383",
"type": "github"
},
"original": {
@@ -110,6 +114,30 @@
"repo": "zig-overlay",
"type": "github"
}
},
"zon2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1742104771,
"narHash": "sha256-LhidlyEA9MP8jGe1rEnyjGFCzLLgCdDpYeWggibayr0=",
"owner": "jcollie",
"repo": "zon2nix",
"rev": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
"type": "github"
},
"original": {
"owner": "jcollie",
"ref": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
"repo": "zon2nix",
"type": "github"
}
}
},
"root": "root",

View File

@@ -8,6 +8,7 @@
# glibc versions used by our dependencies from Nix are compatible with the
# system glibc that the user is building for.
nixpkgs-stable.url = "github:nixos/nixpkgs/release-24.11";
flake-utils.url = "github:numtide/flake-utils";
# Used for shell.nix
flake-compat = {
@@ -19,9 +20,18 @@
url = "github:mitchellh/zig-overlay";
inputs = {
nixpkgs.follows = "nixpkgs-stable";
flake-utils.follows = "flake-utils";
flake-compat.follows = "";
};
};
zon2nix = {
url = "github:jcollie/zon2nix?ref=56c159be489cc6c0e73c3930bd908ddc6fe89613";
inputs = {
nixpkgs.follows = "nixpkgs-unstable";
flake-utils.follows = "flake-utils";
};
};
};
outputs = {
@@ -29,6 +39,7 @@
nixpkgs-unstable,
nixpkgs-stable,
zig,
zon2nix,
...
}:
builtins.foldl' nixpkgs-stable.lib.recursiveUpdate {} (
@@ -36,10 +47,16 @@
system: let
pkgs-stable = nixpkgs-stable.legacyPackages.${system};
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
# These are all of our VM tests.
tests = import ./nix/tests/default.nix {
nixpkgs = pkgs-unstable;
};
in {
devShell.${system} = pkgs-stable.callPackage ./nix/devShell.nix {
zig = zig.packages.${system}."0.13.0";
zig = zig.packages.${system}."0.14.0";
wraptest = pkgs-stable.callPackage ./nix/wraptest.nix {};
zon2nix = zon2nix;
};
packages.${system} = let
@@ -49,9 +66,10 @@
revision = self.shortRev or self.dirtyShortRev or "dirty";
};
in rec {
ghostty-debug = pkgs-stable.callPackage ./nix/package.nix (mkArgs "Debug");
ghostty-releasesafe = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseSafe");
ghostty-releasefast = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseFast");
deps = pkgs-unstable.callPackage ./build.zig.zon.nix {};
ghostty-debug = pkgs-unstable.callPackage ./nix/package.nix (mkArgs "Debug");
ghostty-releasesafe = pkgs-unstable.callPackage ./nix/package.nix (mkArgs "ReleaseSafe");
ghostty-releasefast = pkgs-unstable.callPackage ./nix/package.nix (mkArgs "ReleaseFast");
ghostty = ghostty-releasefast;
default = ghostty;
@@ -59,34 +77,50 @@
formatter.${system} = pkgs-stable.alejandra;
checks.${system} = tests;
apps.${system} = let
runVM = (
module: let
vm = import ./nix/vm/create.nix {
inherit system module;
nixpkgs = nixpkgs-stable;
nixpkgs = nixpkgs-unstable;
overlay = self.overlays.debug;
};
program = pkgs-stable.writeShellScript "run-ghostty-vm" ''
program = pkgs-unstable.writeShellScript "run-ghostty-vm" ''
SHARED_DIR=$(pwd)
export SHARED_DIR
${pkgs-stable.lib.getExe vm.config.system.build.vm} "$@"
${pkgs-unstable.lib.getExe vm.config.system.build.vm} "$@"
'';
in {
type = "app";
program = "${program}";
}
);
in {
wayland-cinnamon = runVM ./nix/vm/wayland-cinnamon.nix;
wayland-gnome = runVM ./nix/vm/wayland-gnome.nix;
wayland-plasma6 = runVM ./nix/vm/wayland-plasma6.nix;
x11-cinnamon = runVM ./nix/vm/x11-cinnamon.nix;
x11-gnome = runVM ./nix/vm/x11-gnome.nix;
x11-plasma6 = runVM ./nix/vm/x11-plasma6.nix;
x11-xfce = runVM ./nix/vm/x11-xfce.nix;
};
in
{
wayland-cinnamon = runVM ./nix/vm/wayland-cinnamon.nix;
wayland-gnome = runVM ./nix/vm/wayland-gnome.nix;
wayland-plasma6 = runVM ./nix/vm/wayland-plasma6.nix;
x11-cinnamon = runVM ./nix/vm/x11-cinnamon.nix;
x11-gnome = runVM ./nix/vm/x11-gnome.nix;
x11-plasma6 = runVM ./nix/vm/x11-plasma6.nix;
x11-xfce = runVM ./nix/vm/x11-xfce.nix;
}
// (pkgs-stable.lib.mapAttrs' (
# This adds all of our VM tests as runnable apps that load
# the interactive driver (repl). We map all the names of the
# tests to "test-<name>" so its distinct from other apps.
name: value: {
name = "test-${name}";
value = {
type = "app";
program = pkgs-unstable.lib.getExe tests.${name}.driverInteractive;
};
}
)
tests);
}
# Our supported systems are the same supported systems as the Zig binaries.
) (builtins.attrNames zig.packages)

View File

@@ -412,6 +412,7 @@ typedef enum {
GHOSTTY_FULLSCREEN_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
} ghostty_action_fullscreen_e;
// apprt.action.SecureInput
@@ -579,12 +580,14 @@ typedef enum {
GHOSTTY_ACTION_TOGGLE_SPLIT_ZOOM,
GHOSTTY_ACTION_PRESENT_TERMINAL,
GHOSTTY_ACTION_SIZE_LIMIT,
GHOSTTY_ACTION_RESET_WINDOW_SIZE,
GHOSTTY_ACTION_INITIAL_SIZE,
GHOSTTY_ACTION_CELL_SIZE,
GHOSTTY_ACTION_INSPECTOR,
GHOSTTY_ACTION_RENDER_INSPECTOR,
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
GHOSTTY_ACTION_SET_TITLE,
GHOSTTY_ACTION_PROMPT_TITLE,
GHOSTTY_ACTION_PWD,
GHOSTTY_ACTION_MOUSE_SHAPE,
GHOSTTY_ACTION_MOUSE_VISIBILITY,
@@ -597,6 +600,7 @@ typedef enum {
GHOSTTY_ACTION_COLOR_CHANGE,
GHOSTTY_ACTION_RELOAD_CONFIG,
GHOSTTY_ACTION_CONFIG_CHANGE,
GHOSTTY_ACTION_CLOSE_WINDOW,
} ghostty_action_tag_e;
typedef union {
@@ -644,7 +648,7 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
ghostty_clipboard_e,
bool);
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
typedef void (*ghostty_runtime_action_cb)(ghostty_app_t,
typedef bool (*ghostty_runtime_action_cb)(ghostty_app_t,
ghostty_target_s,
ghostty_action_s);
@@ -665,6 +669,7 @@ typedef struct {
int ghostty_init(void);
void ghostty_cli_main(uintptr_t, char**);
ghostty_info_s ghostty_info(void);
const char* ghostty_translate(const char*);
ghostty_config_t ghostty_config_new();
void ghostty_config_free(ghostty_config_t);

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

View File

@@ -40,6 +40,7 @@
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */; };
A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BB729B6F53A0055DE60 /* Package.swift */; };
A546F1142D7B68D7003B11A0 /* locale in Resources */ = {isa = PBXBuildFile; fileRef = A546F1132D7B68D7003B11A0 /* locale */; };
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B0CE82D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift */; };
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */; };
A54B0CED2D0CFB7700CBEFF8 /* ColorizedGhosttyIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */; };
@@ -72,6 +73,7 @@
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3C92D4445E20033CF96 /* Dock.swift */; };
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */; };
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
@@ -137,6 +139,7 @@
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Action.swift; sourceTree = "<group>"; };
A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = "<group>"; };
A546F1132D7B68D7003B11A0 /* locale */ = {isa = PBXFileReference; lastKnownFileType = folder; name = locale; path = "../zig-out/share/locale"; sourceTree = "<group>"; };
A54B0CE82D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIconView.swift; sourceTree = "<group>"; };
A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+Extension.swift"; sourceTree = "<group>"; };
A54B0CEC2D0CFB7300CBEFF8 /* ColorizedGhosttyIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorizedGhosttyIcon.swift; sourceTree = "<group>"; };
@@ -168,6 +171,7 @@
A5A2A3C92D4445E20033CF96 /* Dock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dock.swift; sourceTree = "<group>"; };
A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApplication+Extension.swift"; sourceTree = "<group>"; };
A5A6F7292CC41B8700B232A5 /* Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xcode.swift; sourceTree = "<group>"; };
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastWindowPosition.swift; sourceTree = "<group>"; };
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
@@ -270,6 +274,7 @@
A534263D2A7DCBB000EBB7A2 /* Helpers */ = {
isa = PBXGroup;
children = (
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */,
A5A6F7292CC41B8700B232A5 /* Xcode.swift */,
A5CEAFFE29C2410700646FDA /* Backport.swift */,
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
@@ -421,6 +426,7 @@
29C15B1C2CDC3B2000520DD4 /* bat */,
A586167B2B7703CC009BDB1D /* fish */,
55154BDF2B33911F001622DC /* ghostty */,
A546F1132D7B68D7003B11A0 /* locale */,
A5985CE52C33060F00C57AD3 /* man */,
9351BE8E2D22937F003B3499 /* nvim */,
A5A1F8842A489D6800D1E8BC /* terminfo */,
@@ -590,20 +596,21 @@
buildActionMask = 2147483647;
files = (
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */,
29C15B1D2CDC3B2900520DD4 /* bat in Resources */,
A586167C2B7703CC009BDB1D /* fish in Resources */,
55154BE02B33911F001622DC /* ghostty in Resources */,
A546F1142D7B68D7003B11A0 /* locale in Resources */,
A5985CE62C33060F00C57AD3 /* man in Resources */,
9351BE8E3D22937F003B3499 /* nvim in Resources */,
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
552964E62B34A9B400030505 /* vim in Resources */,
FC5218FA2D10FFCE004C93E0 /* zsh in Resources */,
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */,
A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */,
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */,
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */,
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
29C15B1D2CDC3B2900520DD4 /* bat in Resources */,
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */,
A586167C2B7703CC009BDB1D /* fish in Resources */,
FC5218FA2D10FFCE004C93E0 /* zsh in Resources */,
55154BE02B33911F001622DC /* ghostty in Resources */,
A5985CE62C33060F00C57AD3 /* man in Resources */,
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
552964E62B34A9B400030505 /* vim in Resources */,
9351BE8E3D22937F003B3499 /* nvim in Resources */,
A5CBD05C2CA0C5C70017A1AE /* QuickTerminal.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -623,6 +630,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */,
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */,
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,

View File

@@ -6,8 +6,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle",
"state" : {
"revision" : "b456fd404954a9e13f55aa0c88cd5a40b8399638",
"version" : "2.6.3"
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
"version" : "2.6.4"
}
}
],

View File

@@ -28,7 +28,9 @@ class AppDelegate: NSObject,
@IBOutlet private var menuNewWindow: NSMenuItem?
@IBOutlet private var menuNewTab: NSMenuItem?
@IBOutlet private var menuSplitRight: NSMenuItem?
@IBOutlet private var menuSplitLeft: NSMenuItem?
@IBOutlet private var menuSplitDown: NSMenuItem?
@IBOutlet private var menuSplitUp: NSMenuItem?
@IBOutlet private var menuClose: NSMenuItem?
@IBOutlet private var menuCloseTab: NSMenuItem?
@IBOutlet private var menuCloseWindow: NSMenuItem?
@@ -41,6 +43,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuToggleVisibility: NSMenuItem?
@IBOutlet private var menuToggleFullScreen: NSMenuItem?
@IBOutlet private var menuBringAllToFront: NSMenuItem?
@IBOutlet private var menuZoomSplit: NSMenuItem?
@IBOutlet private var menuPreviousSplit: NSMenuItem?
@IBOutlet private var menuNextSplit: NSMenuItem?
@@ -48,10 +51,12 @@ class AppDelegate: NSObject,
@IBOutlet private var menuSelectSplitBelow: NSMenuItem?
@IBOutlet private var menuSelectSplitLeft: NSMenuItem?
@IBOutlet private var menuSelectSplitRight: NSMenuItem?
@IBOutlet private var menuReturnToDefaultSize: NSMenuItem?
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
@IBOutlet private var menuResetFontSize: NSMenuItem?
@IBOutlet private var menuChangeTitle: NSMenuItem?
@IBOutlet private var menuQuickTerminal: NSMenuItem?
@IBOutlet private var menuTerminalInspector: NSMenuItem?
@@ -93,7 +98,7 @@ class AppDelegate: NSObject,
}
/// Tracks the windows that we hid for toggleVisibility.
private var hiddenWindows: [Weak<NSWindow>] = []
private var hiddenState: ToggleVisibilityState? = nil
/// The observer for the app appearance.
private var appearanceObserver: NSKeyValueObservation? = nil
@@ -217,8 +222,8 @@ class AppDelegate: NSObject,
}
func applicationDidBecomeActive(_ notification: Notification) {
// If we're back then clear the hidden windows
self.hiddenWindows = []
// If we're back manually then clear the hidden state because macOS handles it.
self.hiddenState = nil
// First launch stuff
if (!applicationHasBecomeActive) {
@@ -245,7 +250,13 @@ class AppDelegate: NSObject,
// This probably isn't fully safe. The isEmpty check above is aspirational, it doesn't
// quite work with SwiftUI because windows are retained on close. So instead we check
// if there are any that are visible. I'm guessing this breaks under certain scenarios.
if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow }
//
// NOTE(mitchellh): I don't think we need this check at all anymore. I'm keeping it
// here because I don't want to remove it in a patch release cycle but we should
// target removing it soon.
if (self.quickController == nil && windows.allSatisfy { !$0.isVisible }) {
return .terminateNow
}
// If the user is shutting down, restarting, or logging out, we don't confirm quit.
why: if let event = NSAppleEventManager.shared().currentAppleEvent {
@@ -355,7 +366,9 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "close_window", menuItem: self.menuCloseWindow)
syncMenuShortcut(config, action: "close_all_windows", menuItem: self.menuCloseAllWindows)
syncMenuShortcut(config, action: "new_split:right", menuItem: self.menuSplitRight)
syncMenuShortcut(config, action: "new_split:left", menuItem: self.menuSplitLeft)
syncMenuShortcut(config, action: "new_split:down", menuItem: self.menuSplitDown)
syncMenuShortcut(config, action: "new_split:up", menuItem: self.menuSplitUp)
syncMenuShortcut(config, action: "copy_to_clipboard", menuItem: self.menuCopy)
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
@@ -374,10 +387,12 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "resize_split:right,10", menuItem: self.menuMoveSplitDividerRight)
syncMenuShortcut(config, action: "resize_split:left,10", menuItem: self.menuMoveSplitDividerLeft)
syncMenuShortcut(config, action: "equalize_splits", menuItem: self.menuEqualizeSplits)
syncMenuShortcut(config, action: "reset_window_size", menuItem: self.menuReturnToDefaultSize)
syncMenuShortcut(config, action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize)
syncMenuShortcut(config, action: "change_title_prompt", menuItem: self.menuChangeTitle)
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
@@ -431,7 +446,7 @@ class AppDelegate: NSObject,
// If we have a main window then we don't process any of the keys
// because we let it capture and propagate.
guard NSApp.mainWindow == nil else { return event }
// If this event as-is would result in a key binding then we send it.
if let app = ghostty.app,
ghostty_app_key_is_binding(
@@ -447,26 +462,26 @@ class AppDelegate: NSObject,
return nil
}
}
// If this event would be handled by our menu then we do nothing.
if let mainMenu = NSApp.mainMenu,
mainMenu.performKeyEquivalent(with: event) {
return nil
}
// If we reach this point then we try to process the key event
// through the Ghostty key mechanism.
// Ghostty must be loaded
guard let ghostty = self.ghostty.app else { return event }
// Build our event input and call ghostty
if (ghostty_app_key(ghostty, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))) {
// The key was used so we want to stop it from going to our Mac app
Ghostty.logger.debug("local key event handled event=\(event)")
return nil
}
return event
}
@@ -524,6 +539,15 @@ class AppDelegate: NSObject,
// AppKit mutex on the appearance.
DispatchQueue.main.async { self.syncAppearance(config: config) }
// Decide whether to hide/unhide app from dock and app switcher
switch (config.macosHidden) {
case .never:
NSApp.setActivationPolicy(.regular)
case .always:
NSApp.setActivationPolicy(.accessory)
}
// If we have configuration errors, we need to show them.
let c = ConfigurationErrorsController.sharedInstance
c.errors = config.errors
@@ -557,6 +581,30 @@ class AppDelegate: NSObject,
self.appIcon = nil
break
case .blueprint:
self.appIcon = NSImage(named: "BlueprintImage")!
case .chalkboard:
self.appIcon = NSImage(named: "ChalkboardImage")!
case .glass:
self.appIcon = NSImage(named: "GlassImage")!
case .holographic:
self.appIcon = NSImage(named: "HolographicImage")!
case .microchip:
self.appIcon = NSImage(named: "MicrochipImage")!
case .paper:
self.appIcon = NSImage(named: "PaperImage")!
case .retro:
self.appIcon = NSImage(named: "RetroImage")!
case .xray:
self.appIcon = NSImage(named: "XrayImage")!
case .customStyle:
guard let ghostColor = config.macosIconGhostColor else { break }
guard let screenColors = config.macosIconScreenColor else { break }
@@ -711,9 +759,13 @@ class AppDelegate: NSObject,
@IBAction func toggleVisibility(_ sender: Any) {
// If we have focus, then we hide all windows.
if NSApp.isActive {
// We need to keep track of the windows that were visible because we only
// want to bring back these windows if we remove the toggle.
self.hiddenWindows = NSApp.windows.filter { $0.isVisible }.map { Weak($0) }
// Toggle visibility doesn't do anything if the focused window is native
// fullscreen. This is only relevant if Ghostty is active.
guard let keyWindow = NSApp.keyWindow,
!keyWindow.styleMask.contains(.fullScreen) else { return }
// Keep track of our hidden state to restore properly
self.hiddenState = .init()
NSApp.hide(nil)
return
}
@@ -724,8 +776,16 @@ class AppDelegate: NSObject,
// Bring all windows to the front. Note: we don't use NSApp.unhide because
// that will unhide ALL hidden windows. We want to only bring forward the
// ones that we hid.
self.hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
self.hiddenWindows = []
hiddenState?.restore()
hiddenState = nil
}
@IBAction func bringAllToFront(_ sender: Any) {
if !NSApp.isActive {
NSApp.activate(ignoringOtherApps: true)
}
NSApplication.shared.arrangeInFront(sender)
}
private struct DerivedConfig {
@@ -745,4 +805,33 @@ class AppDelegate: NSObject,
self.quickTerminalPosition = config.quickTerminalPosition
}
}
private struct ToggleVisibilityState {
let hiddenWindows: [Weak<NSWindow>]
let keyWindow: Weak<NSWindow>?
init() {
// We need to know the key window so that we can bring focus back to the
// right window if it was hidden.
self.keyWindow = if let keyWindow = NSApp.keyWindow {
.init(keyWindow)
} else {
nil
}
// We need to keep track of the windows that were visible because we only
// want to bring back these windows if we remove the toggle.
//
// We also ignore fullscreen windows because they don't hide anyways.
self.hiddenWindows = NSApp.windows.filter {
$0.isVisible &&
!$0.styleMask.contains(.fullScreen)
}.map { Weak($0) }
}
func restore() {
hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
keyWindow?.value?.makeKey()
}
}
}

View File

@@ -14,6 +14,8 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
<connections>
<outlet property="menuBringAllToFront" destination="LE2-aR-0XJ" id="AP9-oK-60V"/>
<outlet property="menuChangeTitle" destination="24I-xg-qIq" id="kg6-kT-jNL"/>
<outlet property="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
@@ -38,6 +40,7 @@
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
<outlet property="menuReloadConfig" destination="KKH-XX-5py" id="Wvp-7J-wqX"/>
<outlet property="menuResetFontSize" destination="Jah-MY-aLX" id="ger-qM-wrm"/>
<outlet property="menuReturnToDefaultSize" destination="Gbx-Vi-OGC" id="po9-qC-Iz6"/>
<outlet property="menuSecureInput" destination="oC6-w4-qI7" id="PCc-pe-Mda"/>
<outlet property="menuSelectAll" destination="q2h-lq-e4r" id="s98-r1-Jcv"/>
<outlet property="menuSelectSplitAbove" destination="0yU-hC-8xF" id="aPc-lS-own"/>
@@ -45,8 +48,10 @@
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
<outlet property="menuSelectSplitRight" destination="upj-mc-L7X" id="nLY-o1-lky"/>
<outlet property="menuServices" destination="aQe-vS-j8Q" id="uWQ-Wo-T1L"/>
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="fgZ-Wb-8OR"/>
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="ptr-mj-Azh"/>
<outlet property="menuSplitLeft" destination="Ppv-GP-lQU" id="Xd5-Cd-Jut"/>
<outlet property="menuSplitRight" destination="VUR-Ld-nLx" id="RxO-Zw-ovb"/>
<outlet property="menuSplitUp" destination="Ggp-7N-GbX" id="YJF-uq-S4Y"/>
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
<outlet property="menuToggleVisibility" destination="DOX-wA-ilh" id="iBj-Bc-2bq"/>
@@ -143,10 +148,22 @@
<action selector="splitRight:" target="-1" id="cv2-Xg-FR4"/>
</connections>
</menuItem>
<menuItem title="Split Left" id="Ppv-GP-lQU">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="splitLeft:" target="-1" id="Cey-Mf-bD2"/>
</connections>
</menuItem>
<menuItem title="Split Down" id="UDZ-4y-6xL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="splitDown:" target="-1" id="c6x-CF-u52"/>
<action selector="splitDown:" target="-1" id="Zej-CF-6nO"/>
</connections>
</menuItem>
<menuItem title="Split Up" id="Ggp-7N-GbX">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="splitUp:" target="-1" id="bbi-dK-pOS"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="sjq-M1-UGS"/>
@@ -232,6 +249,13 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/>
<menuItem title="Change Title..." id="24I-xg-qIq">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Vkj-tP-dMZ"/>
<menuItem title="Quick Terminal" id="1pv-LF-NBJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@@ -270,12 +294,6 @@
<action selector="toggleGhosttyFullScreen:" target="-1" id="QB9-7R-xyc"/>
</connections>
</menuItem>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
<menuItem title="Show/Hide All Terminals" id="DOX-wA-ilh">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@@ -370,6 +388,20 @@
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="dgt-Tx-d4e"/>
<menuItem title="Return To Default Size" id="Gbx-Vi-OGC" userLabel="Return To Default Size">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="returnToDefaultSize:" target="-1" id="Bpt-GO-UU1"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="CpM-rI-Sc1"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>

View File

@@ -59,6 +59,11 @@ class QuickTerminalController: BaseTerminalController {
selector: #selector(ghosttyConfigDidChange(_:)),
name: .ghosttyConfigDidChange,
object: nil)
center.addObserver(
self,
selector: #selector(onNewTab),
name: Ghostty.Notification.ghosttyNewTab,
object: nil)
}
required init?(coder: NSCoder) {
@@ -437,14 +442,7 @@ class QuickTerminalController: BaseTerminalController {
}
}
// MARK: First Responder
@IBAction override func closeWindow(_ sender: Any) {
// Instead of closing the window, we animate it out.
animateOut()
}
@IBAction func newTab(_ sender: Any?) {
private func showNoNewTabAlert() {
guard let window else { return }
let alert = NSAlert()
alert.messageText = "Cannot Create New Tab"
@@ -454,11 +452,27 @@ class QuickTerminalController: BaseTerminalController {
alert.beginSheetModal(for: window)
}
// MARK: First Responder
@IBAction override func closeWindow(_ sender: Any) {
// Instead of closing the window, we animate it out.
animateOut()
}
@IBAction func newTab(_ sender: Any?) {
showNoNewTabAlert()
}
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
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) {
@@ -492,6 +506,14 @@ class QuickTerminalController: BaseTerminalController {
syncAppearance()
}
@objc private func onNewTab(notification: SwiftUI.Notification) {
guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return }
guard let window = surfaceView.window else { return }
guard window.windowController is QuickTerminalController else { return }
// Tabs aren't supported with Quick Terminals or derivatives
showNoNewTabAlert()
}
private struct DerivedConfig {
let quickTerminalScreen: QuickTerminalScreen
let quickTerminalAnimationDuration: Double

View File

@@ -413,6 +413,14 @@ class BaseTerminalController: NSWindowController,
override func windowDidLoad() {
guard let window else { return }
// If there is a hardcoded title in the configuration, we set that
// immediately. Future `set_title` apprt actions will override this
// if necessary but this ensures our window loads with the proper
// title immediately rather than on another event loop tick (see #5934)
if let title = derivedConfig.title {
window.title = title
}
// We always initialize our fullscreen style to native if we can because
// initialization sets up some state (i.e. observers). If its set already
// somehow we don't do this.
@@ -521,11 +529,21 @@ class BaseTerminalController: NSWindowController,
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_RIGHT)
}
@IBAction func splitLeft(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_LEFT)
}
@IBAction func splitDown(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_DOWN)
}
@IBAction func splitUp(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_UP)
}
@IBAction func splitZoom(_ sender: Any) {
guard let surface = focusedSurface?.surface else { return }
ghostty.splitToggleZoom(surface: surface)
@@ -607,17 +625,20 @@ class BaseTerminalController: NSWindowController,
}
private struct DerivedConfig {
let title: String?
let macosTitlebarProxyIcon: Ghostty.MacOSTitlebarProxyIcon
let windowStepResize: Bool
let focusFollowsMouse: Bool
init() {
self.title = nil
self.macosTitlebarProxyIcon = .visible
self.windowStepResize = false
self.focusFollowsMouse = false
}
init(_ config: Ghostty.Config) {
self.title = config.title
self.macosTitlebarProxyIcon = config.macosTitlebarProxyIcon
self.windowStepResize = config.windowStepResize
self.focusFollowsMouse = config.focusFollowsMouse

View File

@@ -27,6 +27,9 @@ class TerminalController: BaseTerminalController {
/// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
/// This will be set to the initial frame of the window from the xib on load.
private var initialFrame: NSRect? = nil
init(_ ghostty: Ghostty.App,
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
withSurfaceTree tree: Ghostty.SplitNode? = nil
@@ -65,6 +68,12 @@ class TerminalController: BaseTerminalController {
selector: #selector(onCloseTab),
name: .ghosttyCloseTab,
object: nil)
center.addObserver(
self,
selector: #selector(onResetWindowSize),
name: .ghosttyResetWindowSize,
object: nil
)
center.addObserver(
self,
selector: #selector(ghosttyConfigDidChange(_:)),
@@ -76,6 +85,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 +227,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.
@@ -283,9 +301,12 @@ class TerminalController: BaseTerminalController {
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
guard let window else { return }
// If we don't have both an X and Y we center.
// If we don't have an X/Y then we try to use the previously saved window pos.
guard let x, let y else {
window.center()
if (!LastWindowPosition.shared.restore(window)) {
window.center()
}
return
}
@@ -302,6 +323,55 @@ class TerminalController: BaseTerminalController {
y: frame.maxY - (CGFloat(y) + window.frame.height)))
}
/// Returns the default size of the window. This is contextual based on the focused surface because
/// the focused surface may specify a different default size than others.
private var defaultSize: NSRect? {
guard let screen = window?.screen ?? NSScreen.main else { return nil }
if derivedConfig.maximize {
return screen.visibleFrame
} else if let focusedSurface,
let initialSize = focusedSurface.initialSize {
// Get the current frame of the window
guard var frame = window?.frame else { return nil }
// Calculate the chrome size (window size minus view size)
let chromeWidth = frame.size.width - focusedSurface.frame.size.width
let chromeHeight = frame.size.height - focusedSurface.frame.size.height
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
return frame
}
guard let initialFrame else { return nil }
guard var frame = window?.frame else { return nil }
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialFrame.size.width, screen.visibleFrame.width)
let newHeight = min(initialFrame.size.height, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
return frame
}
//MARK: - NSWindowController
override func windowWillLoad() {
@@ -350,6 +420,9 @@ class TerminalController: BaseTerminalController {
super.windowDidLoad()
guard let window = window as? TerminalWindow else { return }
// Store our initial frame so we can know our default later.
initialFrame = window.frame
// I copy this because we may change the source in the future but also because
// I regularly audit our codebase for "ghostty.config" access because generally
// you shouldn't use it. Its safe in this case because for a new window we should
@@ -366,32 +439,15 @@ class TerminalController: BaseTerminalController {
// If window decorations are disabled, remove our title
if (!config.windowDecorations) { window.styleMask.remove(.titled) }
// If we have only a single surface (no splits) and that surface requested
// an initial size then we set it here now.
// If we have only a single surface (no splits) and there is a default size then
// we should resize to that default size.
if case let .leaf(leaf) = surfaceTree {
if let initialSize = leaf.surface.initialSize,
let screen = window.screen ?? NSScreen.main {
// Get the current frame of the window
var frame = window.frame
// If this is our first surface then our focused surface will be nil
// so we force the focused surface to the leaf.
focusedSurface = leaf.surface
// Calculate the chrome size (window size minus view size)
let chromeWidth = frame.size.width - leaf.surface.frame.size.width
let chromeHeight = frame.size.height - leaf.surface.frame.size.height
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
// Set the updated frame to the window
window.setFrame(frame, display: true)
if let defaultSize {
window.setFrame(defaultSize, display: true)
}
}
@@ -490,6 +546,20 @@ class TerminalController: BaseTerminalController {
override func windowDidMove(_ notification: Notification) {
super.windowDidMove(notification)
self.fixTabBar()
// Whenever we move save our last position for the next start.
if let window {
LastWindowPosition.shared.save(window)
}
}
func windowDidBecomeMain(_ notification: Notification) {
// Whenever we get focused, use that as our last window position for
// restart. This differs from Terminal.app but matches iTerm2 behavior
// and I think its sensible.
if let window {
LastWindowPosition.shared.save(window)
}
}
// Called when the window will be encoded. We handle the data encoding here in the
@@ -554,6 +624,11 @@ class TerminalController: BaseTerminalController {
window.close()
}
@IBAction func returnToDefaultSize(_ sender: Any?) {
guard let defaultSize else { return }
window?.setFrame(defaultSize, display: true)
}
@IBAction override func closeWindow(_ sender: Any?) {
guard let window = window else { return }
guard let tabGroup = window.tabGroup else {
@@ -692,13 +767,21 @@ class TerminalController: BaseTerminalController {
// If our index is the same we do nothing
guard finalIndex != selectedIndex else { return }
// Get our parent
let parent = tabbedWindows[finalIndex]
// Get our target window
let targetWindow = tabbedWindows[finalIndex]
// Move our current selected window to the proper index
// Begin a group of window operations to minimize visual updates
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 0
// Remove and re-add the window in the correct position
tabGroup.removeWindow(selectedWindow)
parent.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
selectedWindow.makeKeyAndOrderFront(nil)
targetWindow.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
// Ensure our window remains selected
selectedWindow.makeKey()
NSAnimationContext.endGrouping()
}
@objc private func onGotoTab(notification: SwiftUI.Notification) {
@@ -759,6 +842,12 @@ class TerminalController: BaseTerminalController {
closeTab(self)
}
@objc private func onResetWindowSize(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard surfaceTree?.contains(view: target) ?? false else { return }
returnToDefaultSize(nil)
}
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
@@ -776,18 +865,69 @@ 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
let maximize: Bool
init() {
self.backgroundColor = Color(NSColor.windowBackgroundColor)
self.macosTitlebarStyle = "system"
self.maximize = false
}
init(_ config: Ghostty.Config) {
self.backgroundColor = config.backgroundColor
self.macosTitlebarStyle = config.macosTitlebarStyle
self.maximize = config.maximize
}
}
}
extension TerminalController: NSMenuItemValidation {
func validateMenuItem(_ item: NSMenuItem) -> Bool {
switch item.action {
case #selector(returnToDefaultSize):
guard let window else { return false }
// Native fullscreen windows can't revert to default size.
if window.styleMask.contains(.fullScreen) {
return false
}
// If we're fullscreen at all then we can't change size
if fullscreenStyle?.isFullscreen ?? false {
return false
}
// If our window is already the default size or we don't have a
// default size, then disable.
guard let defaultSize,
window.frame.size != .init(
width: defaultSize.size.width,
height: defaultSize.size.height
)
else {
return false
}
return true
default:
return true
}
}
}

View File

@@ -86,7 +86,7 @@ class TerminalManager {
// fullscreen for the logic later in this method.
c.toggleFullscreen(mode: .native)
case .nonNative, .nonNativeVisibleMenu:
case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch:
// If we're non-native then we have to do it on a later loop
// so that the content view is setup.
DispatchQueue.main.async {
@@ -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 {
NSApp.activate(ignoringOtherApps: true)
}
// 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
@@ -128,6 +125,9 @@ class TerminalManager {
}
private func newTab(to parent: NSWindow, withBaseConfig base: Ghostty.SurfaceConfiguration?) {
// Making sure that we're dealing with a TerminalController
guard parent.windowController is TerminalController else { return }
// If our parent is in non-native fullscreen, then new tabs do not work.
// See: https://github.com/mitchellh/ghostty/issues/392
if let controller = parent.windowController as? TerminalController,

View File

@@ -95,6 +95,23 @@ fileprivate class CenteredDynamicLabel: NSTextField {
setContentHuggingPriority(.defaultLow, for: .horizontal)
setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
}
// Vertically center the text
override func draw(_ dirtyRect: NSRect) {
guard let attributedString = self.attributedStringValue.mutableCopy() as? NSMutableAttributedString else {
super.draw(dirtyRect)
return
}
let textSize = attributedString.size()
let yOffset = (self.bounds.height - textSize.height) / 2 - 1 // -1 to center it better
let centeredRect = NSRect(x: self.bounds.origin.x, y: self.bounds.origin.y + yOffset,
width: self.bounds.width, height: textSize.height)
attributedString.draw(in: centeredRect)
}
}
extension NSToolbarItem.Identifier {

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

@@ -13,6 +13,9 @@ extension FullscreenMode {
case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
.nonNativeVisibleMenu
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
.nonNativePaddedNotch
default:
nil
}

View File

@@ -257,7 +257,7 @@ extension Ghostty {
// MARK: Ghostty Callbacks (iOS)
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {}
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool { return false }
static func readClipboard(
_ userdata: UnsafeMutableRawPointer?,
location: ghostty_clipboard_e,
@@ -423,7 +423,7 @@ extension Ghostty {
// MARK: Actions (macOS)
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool {
// Make sure it a target we understand so all our action handlers can assert
switch (target.tag) {
case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE:
@@ -431,7 +431,7 @@ extension Ghostty {
default:
Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)")
return
return false
}
// Action dispatch
@@ -455,13 +455,13 @@ extension Ghostty {
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
case GHOSTTY_ACTION_MOVE_TAB:
moveTab(app, target: target, move: action.action.move_tab)
return moveTab(app, target: target, move: action.action.move_tab)
case GHOSTTY_ACTION_GOTO_TAB:
gotoTab(app, target: target, tab: action.action.goto_tab)
return gotoTab(app, target: target, tab: action.action.goto_tab)
case GHOSTTY_ACTION_GOTO_SPLIT:
gotoSplit(app, target: target, direction: action.action.goto_split)
return gotoSplit(app, target: target, direction: action.action.goto_split)
case GHOSTTY_ACTION_RESIZE_SPLIT:
resizeSplit(app, target: target, resize: action.action.resize_split)
@@ -484,6 +484,9 @@ extension Ghostty {
case GHOSTTY_ACTION_SET_TITLE:
setTitle(app, target: target, v: action.action.set_title)
case GHOSTTY_ACTION_PROMPT_TITLE:
return promptTitle(app, target: target)
case GHOSTTY_ACTION_PWD:
pwdChanged(app, target: target, v: action.action.pwd)
@@ -505,6 +508,9 @@ extension Ghostty {
case GHOSTTY_ACTION_INITIAL_SIZE:
setInitialSize(app, target: target, v: action.action.initial_size)
case GHOSTTY_ACTION_RESET_WINDOW_SIZE:
resetWindowSize(app, target: target)
case GHOSTTY_ACTION_CELL_SIZE:
setCellSize(app, target: target, v: action.action.cell_size)
@@ -541,10 +547,15 @@ extension Ghostty {
fallthrough
case GHOSTTY_ACTION_QUIT_TIMER:
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
return false
default:
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
return false
}
// If we reached here then we assume performed since all unknown actions
// are captured in the switch and return false.
return true
}
private static func quit(_ app: ghostty_app_t) {
@@ -716,15 +727,19 @@ extension Ghostty {
private static func moveTab(
_ app: ghostty_app_t,
target: ghostty_target_s,
move: ghostty_action_move_tab_s) {
move: ghostty_action_move_tab_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("move tab does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
// See gotoTab for notes on this check.
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
NotificationCenter.default.post(
name: .ghosttyMoveTab,
object: surfaceView,
@@ -736,20 +751,27 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func gotoTab(
_ app: ghostty_app_t,
target: ghostty_target_s,
tab: ghostty_action_goto_tab_e) {
tab: ghostty_action_goto_tab_e) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto tab does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
// Similar to goto_split (see comment there) about our performability,
// we should make this more accurate later.
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
NotificationCenter.default.post(
name: Notification.ghosttyGotoTab,
object: surfaceView,
@@ -761,20 +783,31 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func gotoSplit(
_ app: ghostty_app_t,
target: ghostty_target_s,
direction: ghostty_action_goto_split_e) {
direction: ghostty_action_goto_split_e) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto split does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else { return false }
// For now, we return false if the window has no splits and we return
// true if the window has ANY splits. This isn't strictly correct because
// we should only be returning true if we actually performed the action,
// but this handles the most common case of caring about goto_split performability
// which is the no-split case.
guard controller.surfaceTree?.isSplit ?? false else { return false }
NotificationCenter.default.post(
name: Notification.ghosttyFocusSplit,
object: surfaceView,
@@ -786,6 +819,8 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func resizeSplit(
@@ -978,6 +1013,26 @@ extension Ghostty {
}
}
private static func promptTitle(
_ app: ghostty_app_t,
target: ghostty_target_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("set title prompt does nothing with an app target")
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
surfaceView.promptTitle()
default:
assertionFailure()
}
return true
}
private static func pwdChanged(
_ app: ghostty_app_t,
target: ghostty_target_s,
@@ -1079,7 +1134,7 @@ extension Ghostty {
v: ghostty_action_initial_size_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("mouse over link does nothing with an app target")
Ghostty.logger.warning("initial size does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
@@ -1093,6 +1148,28 @@ extension Ghostty {
}
}
private static func resetWindowSize(
_ app: ghostty_app_t,
target: ghostty_target_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("reset window size does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
NotificationCenter.default.post(
name: .ghosttyResetWindowSize,
object: surfaceView
)
default:
assertionFailure()
}
}
private static func setCellSize(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@@ -132,6 +132,15 @@ extension Ghostty {
return v
}
var title: String? {
guard let config = self.config else { return nil }
var v: UnsafePointer<Int8>? = nil
let key = "title"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return nil }
guard let ptr = v else { return nil }
return String(cString: ptr)
}
var windowSaveState: String {
guard let config = self.config else { return "" }
var v: UnsafePointer<Int8>? = nil
@@ -140,7 +149,7 @@ extension Ghostty {
guard let ptr = v else { return "" }
return String(cString: ptr)
}
var windowPositionX: Int16? {
guard let config = self.config else { return nil }
var v: Int16 = 0
@@ -216,6 +225,8 @@ extension Ghostty {
.nonNative
case "visible-menu":
.nonNativeVisibleMenu
case "padded-notch":
.nonNativePaddedNotch
default:
defaultValue
}
@@ -300,6 +311,16 @@ extension Ghostty {
return buffer.map { .init(ghostty: $0) }
}
var macosHidden: MacHidden {
guard let config = self.config else { return .never }
var v: UnsafePointer<Int8>? = nil
let key = "macos-hidden"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .never }
guard let ptr = v else { return .never }
let str = String(cString: ptr)
return MacHidden(rawValue: str) ?? .never
}
var focusFollowsMouse : Bool {
guard let config = self.config else { return false }
var v = false;
@@ -502,6 +523,14 @@ extension Ghostty {
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v
}
var maximize: Bool {
guard let config = self.config else { return true }
var v = false;
let key = "maximize"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v
}
}
}
@@ -514,6 +543,11 @@ extension Ghostty.Config {
case download
}
enum MacHidden : String {
case never
case always
}
enum ResizeOverlay : String {
case always
case never

View File

@@ -38,6 +38,15 @@ extension Ghostty {
}
}
/// Returns true if the tree is split.
var isSplit: Bool {
return if case .leaf = self {
false
} else {
true
}
}
func topLeft() -> SurfaceView {
switch (self) {
case .leaf(let leaf):
@@ -120,14 +129,7 @@ extension Ghostty {
/// Returns true if the split tree contains the given view.
func contains(view: SurfaceView) -> Bool {
switch (self) {
case .leaf(let leaf):
return leaf.surface == view
case .split(let container):
return container.topLeft.contains(view: view) ||
container.bottomRight.contains(view: view)
}
return leaf(for: view) != nil
}
/// Find a surface view by UUID.
@@ -164,6 +166,22 @@ extension Ghostty {
}
}
/// Return the node for the given view if its in the tree.
func leaf(for view: SurfaceView) -> Leaf? {
switch (self) {
case .leaf(let leaf):
if leaf.surface == view {
return leaf
} else {
return nil
}
case .split(let container):
return container.topLeft.leaf(for: view) ??
container.bottomRight.leaf(for: view)
}
}
// MARK: - Sequence
func makeIterator() -> IndexingIterator<[Leaf]> {

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

@@ -198,6 +198,14 @@ extension Ghostty {
/// macos-icon
enum MacOSIcon: String {
case official
case blueprint
case chalkboard
case glass
case holographic
case microchip
case paper
case retro
case xray
case customStyle = "custom-style"
}
@@ -239,6 +247,9 @@ extension Notification.Name {
/// Close tab
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
/// Resize the window to a default size.
static let ghosttyResetWindowSize = Notification.Name("com.mitchellh.ghostty.resetWindowSize")
}
// NOTE: I am moving all of these to Notification.Name extensions over time. This

View File

@@ -124,6 +124,11 @@ extension Ghostty {
// A timer to fallback to ghost emoji if no title is set within the grace period
private var titleFallbackTimer: Timer?
// This is the title from the terminal. This is nil if we're currently using
// the terminal title as the main title property. If the title is set manually
// by the user, this is set to the prior value (which may be empty, but non-nil).
private var titleFromTerminal: String?
/// Event monitor (see individual events for why)
private var eventMonitor: Any? = nil
@@ -380,6 +385,45 @@ extension Ghostty {
NSCursor.setHiddenUntilMouseMoves(!visible)
}
/// Set the title by prompting the user.
func promptTitle() {
// Create an alert dialog
let alert = NSAlert()
alert.messageText = "Change Terminal Title"
alert.informativeText = "Leave blank to restore the default."
alert.alertStyle = .informational
// Add a text field to the alert
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
textField.stringValue = title
alert.accessoryView = textField
// Add buttons
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
// Check if the user clicked "OK"
if response == .alertFirstButtonReturn {
// Get the input text
let newTitle = textField.stringValue
if newTitle.isEmpty {
// Empty means that user wants the title to be set automatically
// We also need to reload the config for the "title" property to be
// used again by this tab.
let prevTitle = titleFromTerminal ?? "👻"
titleFromTerminal = nil
setTitle(prevTitle)
} else {
// Set the title and prevent it from being changed automatically
titleFromTerminal = title
title = newTitle
}
}
}
func setTitle(_ title: String) {
// This fixes an issue where very quick changes to the title could
// cause an unpleasant flickering. We set a timer so that we can
@@ -390,6 +434,11 @@ extension Ghostty {
withTimeInterval: 0.075,
repeats: false
) { [weak self] _ in
// Set the title if it wasn't manually set.
guard self?.titleFromTerminal == nil else {
self?.titleFromTerminal = title
return
}
self?.title = title
}
}
@@ -849,28 +898,8 @@ extension Ghostty {
var handled: Bool = false
if let list = keyTextAccumulator, list.count > 0 {
handled = true
// This is a hack. libghostty on macOS treats ctrl input as not having
// text because some keyboard layouts generate bogus characters for
// ctrl+key. libghostty can't tell this is from an IM keyboard giving
// us direct values. So, we just remove control.
var modifierFlags = event.modifierFlags
modifierFlags.remove(.control)
if let keyTextEvent = NSEvent.keyEvent(
with: .keyDown,
location: event.locationInWindow,
modifierFlags: modifierFlags,
timestamp: event.timestamp,
windowNumber: event.windowNumber,
context: nil,
characters: event.characters ?? "",
charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "",
isARepeat: event.isARepeat,
keyCode: event.keyCode
) {
for text in list {
_ = keyAction(action, event: keyTextEvent, text: text)
}
for text in list {
_ = keyAction(action, event: event, text: text)
}
}
@@ -1132,11 +1161,15 @@ extension Ghostty {
menu.addItem(.separator())
menu.addItem(withTitle: "Split Right", action: #selector(splitRight(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Split Left", action: #selector(splitLeft(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Split Down", action: #selector(splitDown(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Split Up", action: #selector(splitUp(_:)), keyEquivalent: "")
menu.addItem(.separator())
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
menu.addItem(.separator())
menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
return menu
}
@@ -1189,11 +1222,21 @@ extension Ghostty {
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
}
@IBAction func splitLeft(_ sender: Any) {
guard let surface = self.surface else { return }
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_LEFT)
}
@IBAction func splitDown(_ sender: Any) {
guard let surface = self.surface else { return }
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_DOWN)
}
@IBAction func splitUp(_ sender: Any) {
guard let surface = self.surface else { return }
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_UP)
}
@objc func resetTerminal(_ sender: Any) {
guard let surface = self.surface else { return }
let action = "reset"
@@ -1209,6 +1252,10 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
/// Show a user notification and associate it with this surface
func showUserNotification(title: String, body: String) {

View File

@@ -6,6 +6,7 @@ enum FullscreenMode {
case native
case nonNative
case nonNativeVisibleMenu
case nonNativePaddedNotch
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
/// fullscreen properties. This may fail if the window isn't configured properly for a given
@@ -20,6 +21,9 @@ enum FullscreenMode {
case .nonNativeVisibleMenu:
return NonNativeFullscreenVisibleMenu(window)
case .nonNativePaddedNotch:
return NonNativeFullscreenPaddedNotch(window)
}
}
}
@@ -141,6 +145,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
struct Properties {
var hideMenu: Bool = true
var paddedNotch: Bool = false
}
private var savedState: SavedState?
@@ -278,6 +283,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// put an #available check, but it was in a bug fix release so I think
// if a bug is reported to Ghostty we can just advise the user to
// update.
} else if (properties.paddedNotch) {
// We are hiding the menu, we may need to avoid the notch.
frame.size.height -= screen.safeAreaInsets.top
}
return frame
@@ -349,3 +357,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
override var properties: Properties { Properties(hideMenu: false) }
}
class NonNativeFullscreenPaddedNotch: NonNativeFullscreen {
override var properties: Properties { Properties(paddedNotch: true) }
}

View File

@@ -0,0 +1,34 @@
import Cocoa
/// Manages the persistence and restoration of window positions across app launches.
class LastWindowPosition {
static let shared = LastWindowPosition()
private let positionKey = "NSWindowLastPosition"
func save(_ window: NSWindow) {
let origin = window.frame.origin
let point = [origin.x, origin.y]
UserDefaults.standard.set(point, forKey: positionKey)
}
func restore(_ window: NSWindow) -> Bool {
guard let points = UserDefaults.standard.array(forKey: positionKey) as? [Double],
points.count == 2 else { return false }
let lastPosition = CGPoint(x: points[0], y: points[1])
guard let screen = window.screen ?? NSScreen.main else { return false }
let visibleFrame = screen.visibleFrame
var newFrame = window.frame
newFrame.origin = lastPosition
if !visibleFrame.contains(newFrame.origin) {
newFrame.origin.x = max(visibleFrame.minX, min(visibleFrame.maxX - newFrame.width, newFrame.origin.x))
newFrame.origin.y = max(visibleFrame.minY, min(visibleFrame.maxY - newFrame.height, newFrame.origin.y))
}
window.setFrame(newFrame, display: true)
return true
}
}

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env bash
# Nothing in this script should fail.
set -e
CACHE_HASH_FILE="$(realpath "$(dirname "$0")/../zigCacheHash.nix")"
help() {
echo ""
echo "To fix, please (manually) re-run the script from the repository root,"
echo "commit, and push the update:"
echo ""
echo " ./nix/build-support/check-zig-cache-hash.sh --update"
echo " git add nix/zigCacheHash.nix"
echo " git commit -m \"nix: update Zig cache hash\""
echo " git push"
echo ""
}
if [ -f "${CACHE_HASH_FILE}" ]; then
OLD_CACHE_HASH="$(nix eval --raw --file "${CACHE_HASH_FILE}")"
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: Zig cache hash file missing."
help
exit 1
fi
ZIG_GLOBAL_CACHE_DIR="$(mktemp --directory --suffix nix-zig-cache)"
export ZIG_GLOBAL_CACHE_DIR
# This is not 100% necessary in CI but is helpful when running locally to keep
# a local workstation clean.
trap 'rm -rf "${ZIG_GLOBAL_CACHE_DIR}"' EXIT
# Run Zig and download the cache to the temporary directory.
sh ./nix/build-support/fetch-zig-cache.sh
# Now, calculate the hash.
ZIG_CACHE_HASH="sha256-$(nix-hash --type sha256 --to-base64 "$(nix-hash --type sha256 "${ZIG_GLOBAL_CACHE_DIR}")")"
if [ "${OLD_CACHE_HASH}" == "${ZIG_CACHE_HASH}" ]; then
echo -e "\nOK: Zig cache store hash unchanged."
exit 0
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: The Zig cache store hash has updated."
echo ""
echo " * Old hash: ${OLD_CACHE_HASH}"
echo " * New hash: ${ZIG_CACHE_HASH}"
help
exit 1
else
echo -e "\nNew Zig cache store hash: ${ZIG_CACHE_HASH}"
fi
# Write out the cache file
cat > "${CACHE_HASH_FILE}" <<EOS
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"${ZIG_CACHE_HASH}"
EOS
echo -e "\nOK: Wrote new hash to file: ${CACHE_HASH_FILE}"

View File

@@ -0,0 +1,104 @@
#!/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.zon.json
#
# All of these are auto-generated and should not be edited manually.
# Nothing in this script should fail.
set -e
WORK_DIR=$(mktemp -d)
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
echo "could not create temp dir"
exit 1
fi
function cleanup {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
help() {
echo ""
echo "To fix, please (manually) re-run the script from the repository root,"
echo "commit, and submit a PR with the update:"
echo ""
echo " ./nix/build-support/check-zig-cache-hash.sh --update"
echo " git add build.zig.zon.nix build.zig.zon.txt build.zig.zon.json"
echo " git commit -m \"nix: update build.zig.zon.nix build.zig.zon.txt build.zig.zon.json\""
echo ""
}
ROOT="$(realpath "$(dirname "$0")/../../")"
BUILD_ZIG_ZON="$ROOT/build.zig.zon"
BUILD_ZIG_ZON_NIX="$ROOT/build.zig.zon.nix"
BUILD_ZIG_ZON_TXT="$ROOT/build.zig.zon.txt"
BUILD_ZIG_ZON_JSON="$ROOT/build.zig.zon.json"
if [ -f "${BUILD_ZIG_ZON_NIX}" ]; then
OLD_HASH_NIX=$(sha512sum "${BUILD_ZIG_ZON_NIX}" | awk '{print $1}')
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.nix missing."
help
exit 1
fi
if [ -f "${BUILD_ZIG_ZON_TXT}" ]; then
OLD_HASH_TXT=$(sha512sum "${BUILD_ZIG_ZON_TXT}" | awk '{print $1}')
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.txt missing."
help
exit 1
fi
if [ -f "${BUILD_ZIG_ZON_JSON}" ]; then
OLD_HASH_JSON=$(sha512sum "${BUILD_ZIG_ZON_JSON}" | awk '{print $1}')
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.json missing."
help
exit 1
fi
zon2nix "$BUILD_ZIG_ZON" --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json"
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
prettier --write "$WORK_DIR/build.zig.zon.json"
NEW_HASH_NIX=$(sha512sum "$WORK_DIR/build.zig.zon.nix" | awk '{print $1}')
NEW_HASH_TXT=$(sha512sum "$WORK_DIR/build.zig.zon.txt" | awk '{print $1}')
NEW_HASH_JSON=$(sha512sum "$WORK_DIR/build.zig.zon.json" | awk '{print $1}')
if [ "${OLD_HASH_NIX}" == "${NEW_HASH_NIX}" ] && [ "${OLD_HASH_TXT}" == "${NEW_HASH_TXT}" ] && [ "${OLD_HASH_JSON}" == "${NEW_HASH_JSON}" ]; then
echo -e "\nOK: build.zig.zon.nix unchanged."
echo -e "OK: build.zig.zon.txt unchanged."
echo -e "OK: build.zig.zon.json unchanged."
exit 0
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.nix, build.zig.zon.txt, or build.zig.zon.json needs to be updated.\n"
echo " * Old build.zig.zon.nix hash: ${OLD_HASH_NIX}"
echo " * New build.zig.zon.nix hash: ${NEW_HASH_NIX}"
echo " * Old build.zig.zon.txt hash: ${OLD_HASH_TXT}"
echo " * New build.zig.zon.txt hash: ${NEW_HASH_TXT}"
echo " * Old build.zig.zon.json hash: ${OLD_HASH_JSON}"
echo " * New build.zig.zon.json hash: ${NEW_HASH_JSON}"
help
exit 1
else
mv "$WORK_DIR/build.zig.zon.nix" "$BUILD_ZIG_ZON_NIX"
echo -e "\nOK: build.zig.zon.nix updated."
mv "$WORK_DIR/build.zig.zon.txt" "$BUILD_ZIG_ZON_TXT"
echo -e "OK: build.zig.zon.txt updated."
mv "$WORK_DIR/build.zig.zon.json" "$BUILD_ZIG_ZON_JSON"
echo -e "OK: build.zig.zon.json updated."
exit 0
fi

View File

@@ -1,32 +1,13 @@
#!/bin/sh
set -e
# Because Zig does not fetch recursive dependencies when you run `zig build
# --fetch` (see https://github.com/ziglang/zig/issues/20976) we need to do some
# extra work to fetch everything that we actually need to build without Internet
# access (such as when building a Nix package).
# NOTE THIS IS A TEMPORARY SCRIPT TO SUPPORT PACKAGE MAINTAINERS.
#
# An example of this happening:
# 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`.
#
# error: builder for '/nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv' failed with exit code 1;
# la/build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:7:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
# > .url = "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e",
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# > /build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:16:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
# > .url = "git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b",
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# >
# For full logs, run 'nix log /nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv'.
#
# To update this script, add any failing URLs with a line like this:
#
# zig fetch <url>
#
# Periodically old URLs may need to be cleaned out.
#
# Hopefully when the Zig issue is fixed this script can be eliminated in favor
# of a plain `zig build --fetch`.
# [1]: https://github.com/ziglang/zig/issues/20976
if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ]
then
@@ -34,6 +15,13 @@ then
exit 1
fi
zig build --fetch
zig fetch git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e
zig fetch git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b
# 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

@@ -0,0 +1,30 @@
#!/bin/sh
#
# This script generates a directory that can be uploaded to blob
# storage to mirror our dependencies. The dependencies are unmodified
# so their checksum and content hashes will match.
set -e # Exit immediately if a command exits with a non-zero status
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
INPUT_FILE="$SCRIPT_PATH/../../build.zig.zon2json-lock"
OUTPUT_DIR="blob"
# Ensure the output directory exists
mkdir -p "$OUTPUT_DIR"
# Use jq to iterate over the JSON and download files
jq -r 'to_entries[] | "\(.key) \(.value.name) \(.value.url)"' "$INPUT_FILE" | while read -r key name url; do
# Skip URLs that don't start with http(s). They aren't necessary for
# our mirror.
if ! echo "$url" | grep -Eq "^https?://"; then
continue
fi
# Extract the file extension from the URL
extension=$(echo "$url" | grep -oE '\.[a-z0-9]+(\.[a-z0-9]+)?$')
filename="${name}-${key}${extension}"
echo "$url -> $filename"
curl -L -o "$OUTPUT_DIR/$filename" "$url"
done

View File

@@ -14,6 +14,7 @@
python3,
qemu,
scdoc,
snapcraft,
valgrind,
#, vulkan-loader # unused
vttest,
@@ -30,12 +31,17 @@
glib,
glslang,
gtk4,
gtk4-layer-shell,
gobject-introspection,
libadwaita,
blueprint-compiler,
gettext,
adwaita-icon-theme,
hicolor-icon-theme,
harfbuzz,
libpng,
libGL,
libxkbcommon,
libX11,
libXcursor,
libXext,
@@ -47,6 +53,7 @@
simdutf,
zlib,
alejandra,
jq,
minisign,
pandoc,
hyperfine,
@@ -54,6 +61,8 @@
wayland,
wayland-scanner,
wayland-protocols,
zon2nix,
system,
}: let
# See package.nix. Keep in sync.
rpathLibs =
@@ -75,6 +84,7 @@
glslang
spirv-cross
libxkbcommon
libX11
libXcursor
libXi
@@ -82,7 +92,9 @@
libadwaita
gtk4
gtk4-layer-shell
glib
gobject-introspection
wayland
];
in
@@ -92,6 +104,7 @@ in
packages =
[
# For builds
jq
llvmPackages_latest.llvm
minisign
ncurses
@@ -100,6 +113,7 @@ in
scdoc
zig
zip
zon2nix.packages.${system}.zon2nix
# For web and wasm stuff
nodejs
@@ -118,6 +132,9 @@ in
# wasm
wabt
wasmtime
# Localization
gettext
]
++ lib.optionals stdenv.hostPlatform.isLinux [
# My nix shell environment installs the non-interactive version
@@ -129,6 +146,7 @@ in
qemu
gdb
snapcraft
valgrind
wraptest
@@ -146,6 +164,7 @@ in
glslang
spirv-cross
libxkbcommon
libX11
libXcursor
libXext
@@ -154,9 +173,12 @@ in
libXrandr
# Only needed for GTK builds
blueprint-compiler
libadwaita
gtk4
gtk4-layer-shell
glib
gobject-introspection
wayland
wayland-scanner
wayland-protocols
@@ -181,5 +203,9 @@ in
# and we need iOS too.
unset SDKROOT
unset DEVELOPER_DIR
# We need to remove "xcrun" from the PATH. It is injected by
# some dependency but we need to rely on system Xcode tools
export PATH=$(echo "$PATH" | awk -v RS=: -v ORS=: '$0 !~ /xcrun/ || $0 == "/usr/bin" {print}' | sed 's/:$//')
'');
}

View File

@@ -2,6 +2,7 @@
lib,
stdenv,
bzip2,
callPackage,
expat,
fontconfig,
freetype,
@@ -12,13 +13,18 @@
libGL,
glib,
gtk4,
gtk4-layer-shell,
gobject-introspection,
libadwaita,
blueprint-compiler,
libxml2,
gettext,
wrapGAppsHook4,
gsettings-desktop-schemas,
git,
ncurses,
pkg-config,
zig_0_13,
zig_0_14,
pandoc,
revision ? "dirty",
optimize ? "Debug",
@@ -39,83 +45,38 @@
# https://github.com/ziglang/zig/issues/14281#issuecomment-1624220653 is
# ultimately acted on and has made its way to a nixpkgs implementation, this
# can probably be removed in favor of that.
zig_hook = zig_0_13.hook.overrideAttrs {
zig_hook = zig_0_14.hook.overrideAttrs {
zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize} --color off";
};
# 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
#
# NOTE: as of the current moment only linux files are provided,
# since darwin support is not finished
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../dist/linux
../images
../include
../pkg
../src
../vendor
../build.zig
../build.zig.zon
./build-support/fetch-zig-cache.sh
]
);
};
# This hash is the computation of the zigCache fixed-output derivation. This
# allows us to use remote package dependencies without breaking the sandbox.
#
# This will need updating whenever dependencies get updated (e.g. changes are
# made to zig.build.zon). If you see that the main build is trying to reach
# out to the internet and failing, this is likely the cause. Change this
# value back to lib.fakeHash, and re-run. The build failure should emit the
# updated hash, which of course, should be validated before updating here.
#
# (It's also possible that you might see a hash mismatch - without the
# network errors - if you don't have a previous instance of the cache
# derivation in your store already. If so, just update the value as above.)
zigCacheHash = import ./zigCacheHash.nix;
zigCache = stdenv.mkDerivation {
inherit src;
name = "ghostty-cache";
nativeBuildInputs = [
git
zig_hook
];
dontConfigure = true;
dontUseZigBuild = true;
dontUseZigInstall = true;
dontFixup = true;
buildPhase = ''
runHook preBuild
sh ./nix/build-support/fetch-zig-cache.sh
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r --reflink=auto $ZIG_GLOBAL_CACHE_DIR $out
runHook postInstall
'';
outputHashMode = "recursive";
outputHash = zigCacheHash;
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "ghostty";
version = "1.1.0";
inherit src;
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
#
# NOTE: as of the current moment only linux files are provided,
# since darwin support is not finished
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../dist/linux
../images
../include
../po
../pkg
../src
../vendor
../build.zig
../build.zig.zon
../build.zig.zon.nix
]
);
};
deps = callPackage ../build.zig.zon.nix {name = "ghostty-cache-${finalAttrs.version}";};
nativeBuildInputs =
[
@@ -124,7 +85,11 @@ in
pandoc
pkg-config
zig_hook
gobject-introspection
wrapGAppsHook4
blueprint-compiler
libxml2 # for xmllint
gettext
]
++ lib.optionals enableWayland [
wayland-scanner
@@ -157,6 +122,7 @@ in
libXrandr
]
++ lib.optionals enableWayland [
gtk4-layer-shell
wayland
];
@@ -164,7 +130,7 @@ in
zigBuildFlags = [
"--system"
"${zigCache}/p"
"${finalAttrs.deps}"
"-Dversion-string=${finalAttrs.version}-${revision}-nix"
"-Dgtk-x11=${lib.boolToString enableX11}"
"-Dgtk-wayland=${lib.boolToString enableWayland}"

26
nix/tests/basic.nix Normal file
View File

@@ -0,0 +1,26 @@
{
name = "basic";
nodes.machine = {
config,
pkgs,
...
}: {
users.users.alice = {
isNormalUser = true;
extraGroups = ["wheel"];
packages = with pkgs; [
firefox
(pkgs.callPackage ../package.nix {})
];
};
system.stateVersion = "24.11";
};
testScript = ''
machine.wait_for_unit("default.target")
machine.succeed("su -- alice -c 'which firefox'")
machine.fail("su -- root -c 'which firefox'")
'';
}

14
nix/tests/default.nix Normal file
View File

@@ -0,0 +1,14 @@
{nixpkgs}: let
# mkTest does nothing special right now, its just a wrapper around
# runNixOSTest, but it could be extended in the future so I pulled it out.
mkTest = path:
nixpkgs.testers.runNixOSTest {
imports = [
path
];
};
in {
basic = mkTest ./basic.nix;
gnome = mkTest ./gnome.nix;
i3 = mkTest ./i3.nix;
}

56
nix/tests/gnome.nix Normal file
View File

@@ -0,0 +1,56 @@
{
name = "gnome";
nodes.machine = {
config,
pkgs,
...
}: {
users.users.alice = {
isNormalUser = true;
password = "alice";
extraGroups = ["wheel"];
packages = with pkgs; [
(pkgs.callPackage ../package.nix {})
];
};
services.xserver.enable = true;
services.xserver.displayManager = {
gdm.enable = true;
gdm.debug = true;
};
services.displayManager.autoLogin = {
enable = true;
user = "alice";
};
services.xserver.desktopManager.gnome.enable = true;
services.xserver.desktopManager.gnome.debug = true;
systemd.user.services = {
"org.gnome.Shell@wayland" = {
serviceConfig = {
ExecStart = [
# Clear the list before overriding it.
""
# Eval API is now internal so Shell needs to run in unsafe mode.
# TODO: improve test driver so that it supports openqa-like manipulation
# that would allow us to drop this mess.
"${pkgs.gnome-shell}/bin/gnome-shell --unsafe-mode"
];
};
};
};
system.stateVersion = "24.11";
};
testScript = ''
machine.wait_for_unit("default.target")
machine.succeed("su -- alice -c 'which firefox'")
machine.fail("su -- root -c 'which firefox'")
'';
}

59
nix/tests/i3.nix Normal file
View File

@@ -0,0 +1,59 @@
{
name = "gnome";
nodes.machine = {
config,
pkgs,
...
}: {
users.users.alice = {
isNormalUser = true;
password = "alice";
extraGroups = ["wheel"];
packages = with pkgs; [
(pkgs.callPackage ../package.nix {})
];
};
# We need an XDG portal for various applications to work properly,
# such as Flatpak applications.
xdg.portal = {
enable = true;
extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
config.common.default = "*";
};
services.xserver = {
enable = true;
xkb.layout = "us";
dpi = 220;
desktopManager = {
xterm.enable = false;
wallpaper.mode = "fill";
};
displayManager = {
defaultSession = "none+i3";
lightdm.enable = true;
};
windowManager = {
i3.enable = true;
};
};
services.displayManager.autoLogin = {
enable = true;
user = "alice";
};
system.stateVersion = "24.11";
};
testScript = ''
machine.wait_for_unit("default.target")
machine.succeed("su -- alice -c 'which firefox'")
machine.fail("su -- root -c 'which firefox'")
'';
}

View File

@@ -1,3 +1,3 @@
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"sha256-Bjy31evaKgpRX1mGwAFkai44eiiorTV1gW3VdP9Ins8="
"sha256-S8kS+gO17dl9LJGKL1+kgDUre+vPTmdTvXzgc585Fl8="

View File

@@ -1,5 +1,7 @@
.{
.name = "apple-sdk",
.name = .apple_sdk,
.version = "0.1.0",
.dependencies = .{},
.fingerprint = 0xdde52860f7c464d2,
.paths = .{""},
}

View File

@@ -4,85 +4,85 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const upstream = b.dependency("breakpad", .{});
const lib = b.addStaticLibrary(.{
.name = "breakpad",
.target = target,
.optimize = optimize,
});
lib.linkLibCpp();
lib.addIncludePath(upstream.path("src"));
lib.addIncludePath(b.path("vendor"));
if (target.result.isDarwin()) {
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, &lib.root_module);
try apple_sdk.addPaths(b, lib.root_module);
}
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
try flags.appendSlice(&.{});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common,
.flags = flags.items,
});
if (target.result.isDarwin()) {
if (b.lazyDependency("breakpad", .{})) |upstream| {
lib.addIncludePath(upstream.path("src"));
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_apple,
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_apple,
.files = common,
.flags = flags.items,
});
switch (target.result.os.tag) {
.macos => {
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_mac,
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_mac,
.flags = flags.items,
});
},
.ios => lib.addCSourceFiles(.{
if (target.result.os.tag.isDarwin()) {
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_ios,
.files = common_apple,
.flags = flags.items,
}),
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_apple,
.flags = flags.items,
});
else => {},
switch (target.result.os.tag) {
.macos => {
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_mac,
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_mac,
.flags = flags.items,
});
},
.ios => lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_ios,
.flags = flags.items,
}),
else => {},
}
}
}
if (target.result.os.tag == .linux) {
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_linux,
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_linux,
.flags = flags.items,
});
}
if (target.result.os.tag == .linux) {
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_linux,
.flags = flags.items,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_linux,
.flags = flags.items,
});
}
lib.installHeadersDirectory(
upstream.path("src"),
"",
.{ .include_extensions = &.{".h"} },
);
lib.installHeadersDirectory(
upstream.path("src"),
"",
.{ .include_extensions = &.{".h"} },
);
}
b.installArtifact(lib);
}

View File

@@ -1,11 +1,13 @@
.{
.name = "breakpad",
.name = .breakpad,
.version = "0.1.0",
.fingerprint = 0xfe9f9e4c76d5f962,
.paths = .{""},
.dependencies = .{
.breakpad = .{
.url = "https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
.hash = "12207fd37bb8251919c112dcdd8f616a491857b34a451f7e4486490077206dc2a1ea",
.url = "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
.hash = "N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ",
.lazy = true,
},
.apple_sdk = .{ .path = "../apple-sdk" },

View File

@@ -40,7 +40,13 @@ pub fn build(b: *std.Build) !void {
.@"enable-libpng" = true,
});
lib.linkLibrary(freetype.artifact("freetype"));
module.addIncludePath(freetype.builder.dependency("freetype", .{}).path("include"));
if (freetype.builder.lazyDependency(
"freetype",
.{},
)) |freetype_dep| {
module.addIncludePath(freetype_dep.path("include"));
}
}
lib.addIncludePath(imgui.path(""));
@@ -76,9 +82,9 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
});
if (target.result.isDarwin()) {
if (target.result.os.tag.isDarwin()) {
if (!target.query.isNative()) {
try @import("apple_sdk").addPaths(b, &lib.root_module);
try @import("apple_sdk").addPaths(b, lib.root_module);
try @import("apple_sdk").addPaths(b, module);
}
lib.addCSourceFile(.{

View File

@@ -1,13 +1,15 @@
.{
.name = "cimgui",
.name = .cimgui,
.version = "1.90.6", // -docking branch
.fingerprint = 0x49726f5f8acbc90d,
.paths = .{""},
.dependencies = .{
// This should be kept in sync with the submodule in the cimgui source
// code in ./vendor/ to be safe that they're compatible.
.imgui = .{
.url = "https://github.com/ocornut/imgui/archive/e391fe2e66eb1c96b1624ae8444dc64c23146ef4.tar.gz",
.hash = "1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402",
// ocornut/imgui
.url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
.hash = "N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3",
},
.apple_sdk = .{ .path = "../apple-sdk" },

View File

@@ -64,7 +64,6 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
const libxml2_iconv_enabled = options.libxml2_iconv_enabled;
const freetype_enabled = options.freetype_enabled;
const upstream = b.dependency("fontconfig", .{});
const lib = b.addStaticLibrary(.{
.name = "fontconfig",
.target = target,
@@ -75,9 +74,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
lib.linkSystemLibrary("pthread");
}
lib.addIncludePath(upstream.path(""));
lib.addIncludePath(b.path("override/include"));
module.addIncludePath(upstream.path(""));
module.addIncludePath(b.path("override/include"));
var flags = std.ArrayList([]const u8).init(b.allocator);
@@ -188,11 +185,12 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
} else {
const freetype_dep = b.dependency(
if (b.lazyDependency(
"freetype",
.{ .target = target, .optimize = optimize },
);
lib.linkLibrary(freetype_dep.artifact("freetype"));
)) |freetype_dep| {
lib.linkLibrary(freetype_dep.artifact("freetype"));
}
}
}
@@ -214,26 +212,31 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
if (b.systemIntegrationOption("libxml2", .{})) {
lib.linkSystemLibrary2("libxml-2.0", dynamic_link_opts);
} else {
const libxml2_dep = b.dependency("libxml2", .{
if (b.lazyDependency("libxml2", .{
.target = target,
.optimize = optimize,
.iconv = libxml2_iconv_enabled,
});
lib.linkLibrary(libxml2_dep.artifact("xml2"));
})) |libxml2_dep| {
lib.linkLibrary(libxml2_dep.artifact("xml2"));
}
}
}
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
});
if (b.lazyDependency("fontconfig", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
});
lib.installHeadersDirectory(
upstream.path("fontconfig"),
"fontconfig",
.{ .include_extensions = &.{".h"} },
);
lib.installHeadersDirectory(
upstream.path("fontconfig"),
"fontconfig",
.{ .include_extensions = &.{".h"} },
);
}
b.installArtifact(lib);

View File

@@ -1,13 +1,16 @@
.{
.name = "fontconfig",
.name = .fontconfig,
.version = "2.14.2",
.fingerprint = 0x4a79a5a40c6d6d8,
.paths = .{""},
.dependencies = .{
.fontconfig = .{
.url = "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz",
.hash = "12201149afb3326c56c05bb0a577f54f76ac20deece63aa2f5cd6ff31a4fa4fcb3b7",
.hash = "N-V-__8AAIrfdwARSa-zMmxWwFuwpXf1T3asIN7s5jqi9c1v",
.lazy = true,
},
.freetype = .{ .path = "../freetype" },
.libxml2 = .{ .path = "../libxml2" },
.freetype = .{ .path = "../freetype", .lazy = true },
.libxml2 = .{ .path = "../libxml2", .lazy = true },
},
}

View File

@@ -84,7 +84,7 @@ pub const Property = enum {
pub fn cval(self: Property) [:0]const u8 {
@setEvalBranchQuota(10_000);
inline for (@typeInfo(Property).Enum.fields) |field| {
inline for (@typeInfo(Property).@"enum".fields) |field| {
if (self == @field(Property, field.name)) {
// Build our string in a comptime context so it is a binary
// constant and not stack allocated.

View File

@@ -61,20 +61,17 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
const libpng_enabled = options.libpng_enabled;
const upstream = b.dependency("freetype", .{});
const lib = b.addStaticLibrary(.{
.name = "freetype",
.target = target,
.optimize = optimize,
});
lib.linkLibC();
lib.addIncludePath(upstream.path("include"));
if (target.result.isDarwin()) {
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, &lib.root_module);
try apple_sdk.addPaths(b, lib.root_module);
}
module.addIncludePath(upstream.path("include"));
var flags = std.ArrayList([]const u8).init(b.allocator);
defer flags.deinit();
try flags.appendSlice(&.{
@@ -114,48 +111,52 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
}
}
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
});
if (b.lazyDependency("freetype", .{})) |upstream| {
lib.addIncludePath(upstream.path("include"));
module.addIncludePath(upstream.path("include"));
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
});
switch (target.result.os.tag) {
.linux => lib.addCSourceFile(.{
.file = upstream.path("builds/unix/ftsystem.c"),
.flags = flags.items,
}),
.windows => lib.addCSourceFile(.{
.file = upstream.path("builds/windows/ftsystem.c"),
.flags = flags.items,
}),
else => lib.addCSourceFile(.{
.file = upstream.path("src/base/ftsystem.c"),
.flags = flags.items,
}),
}
switch (target.result.os.tag) {
.windows => {
lib.addCSourceFile(.{
.file = upstream.path("builds/windows/ftdebug.c"),
switch (target.result.os.tag) {
.linux => lib.addCSourceFile(.{
.file = upstream.path("builds/unix/ftsystem.c"),
.flags = flags.items,
});
lib.addWin32ResourceFile(.{
.file = upstream.path("src/base/ftver.rc"),
});
},
else => lib.addCSourceFile(.{
.file = upstream.path("src/base/ftdebug.c"),
.flags = flags.items,
}),
}
}),
.windows => lib.addCSourceFile(.{
.file = upstream.path("builds/windows/ftsystem.c"),
.flags = flags.items,
}),
else => lib.addCSourceFile(.{
.file = upstream.path("src/base/ftsystem.c"),
.flags = flags.items,
}),
}
switch (target.result.os.tag) {
.windows => {
lib.addCSourceFile(.{
.file = upstream.path("builds/windows/ftdebug.c"),
.flags = flags.items,
});
lib.addWin32ResourceFile(.{
.file = upstream.path("src/base/ftver.rc"),
});
},
else => lib.addCSourceFile(.{
.file = upstream.path("src/base/ftdebug.c"),
.flags = flags.items,
}),
}
lib.installHeader(b.path("freetype-zig.h"), "freetype-zig.h");
lib.installHeadersDirectory(
upstream.path("include"),
"",
.{ .include_extensions = &.{".h"} },
);
lib.installHeader(b.path("freetype-zig.h"), "freetype-zig.h");
lib.installHeadersDirectory(
upstream.path("include"),
"",
.{ .include_extensions = &.{".h"} },
);
}
b.installArtifact(lib);

View File

@@ -1,11 +1,14 @@
.{
.name = "freetype",
.name = .freetype,
.version = "2.13.2",
.fingerprint = 0xac2059b6f7bbfe0a,
.paths = .{""},
.dependencies = .{
// freetype/freetype
.freetype = .{
.url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz",
.hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d",
.url = "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz",
.hash = "N-V-__8AAKLKpwC4H27Ps_0iL3bPkQb-z6ZVSrB-x_3EEkub",
.lazy = true,
},
.apple_sdk = .{ .path = "../apple-sdk" },

209
pkg/glfw/Cursor.zig Normal file
View File

@@ -0,0 +1,209 @@
//! Represents a cursor and provides facilities for setting cursor images.
const std = @import("std");
const testing = std.testing;
const c = @import("c.zig").c;
const Image = @import("Image.zig");
const internal_debug = @import("internal_debug.zig");
const Cursor = @This();
ptr: *c.GLFWcursor,
/// Standard system cursor shapes.
///
/// These are the standard cursor shapes that can be requested from the platform (window system).
pub const Shape = enum(i32) {
/// The regular arrow cursor shape.
arrow = c.GLFW_ARROW_CURSOR,
/// The text input I-beam cursor shape.
ibeam = c.GLFW_IBEAM_CURSOR,
/// The crosshair cursor shape.
crosshair = c.GLFW_CROSSHAIR_CURSOR,
/// The pointing hand cursor shape.
///
/// NOTE: This supersedes the old `hand` enum.
pointing_hand = c.GLFW_POINTING_HAND_CURSOR,
/// The horizontal resize/move arrow shape.
///
/// The horizontal resize/move arrow shape. This is usually a horizontal double-headed arrow.
//
// NOTE: This supersedes the old `hresize` enum.
resize_ew = c.GLFW_RESIZE_EW_CURSOR,
/// The vertical resize/move arrow shape.
///
/// The vertical resize/move shape. This is usually a vertical double-headed arrow.
///
/// NOTE: This supersedes the old `vresize` enum.
resize_ns = c.GLFW_RESIZE_NS_CURSOR,
/// The top-left to bottom-right diagonal resize/move arrow shape.
///
/// The top-left to bottom-right diagonal resize/move shape. This is usually a diagonal
/// double-headed arrow.
///
/// macos: This shape is provided by a private system API and may fail CursorUnavailable in the
/// future.
///
/// x11: This shape is provided by a newer standard not supported by all cursor themes.
///
/// wayland: This shape is provided by a newer standard not supported by all cursor themes.
resize_nwse = c.GLFW_RESIZE_NWSE_CURSOR,
/// The top-right to bottom-left diagonal resize/move arrow shape.
///
/// The top-right to bottom-left diagonal resize/move shape. This is usually a diagonal
/// double-headed arrow.
///
/// macos: This shape is provided by a private system API and may fail with CursorUnavailable
/// in the future.
///
/// x11: This shape is provided by a newer standard not supported by all cursor themes.
///
/// wayland: This shape is provided by a newer standard not supported by all cursor themes.
resize_nesw = c.GLFW_RESIZE_NESW_CURSOR,
/// The omni-directional resize/move cursor shape.
///
/// The omni-directional resize cursor/move shape. This is usually either a combined horizontal
/// and vertical double-headed arrow or a grabbing hand.
resize_all = c.GLFW_RESIZE_ALL_CURSOR,
/// The operation-not-allowed shape.
///
/// The operation-not-allowed shape. This is usually a circle with a diagonal line through it.
///
/// x11: This shape is provided by a newer standard not supported by all cursor themes.
///
/// wayland: This shape is provided by a newer standard not supported by all cursor themes.
not_allowed = c.GLFW_NOT_ALLOWED_CURSOR,
};
/// Creates a custom cursor.
///
/// Creates a new custom cursor image that can be set for a window with glfw.Cursor.set. The cursor
/// can be destroyed with glfwCursor.destroy. Any remaining cursors are destroyed by glfw.terminate.
///
/// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight bits per channel with
/// the red channel first. They are arranged canonically as packed sequential rows, starting from
/// the top-left corner.
///
/// The cursor hotspot is specified in pixels, relative to the upper-left corner of the cursor
/// image. Like all other coordinate systems in GLFW, the X-axis points to the right and the Y-axis
/// points down.
///
/// @param[in] image The desired cursor image.
/// @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
/// @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
/// @return The handle of the created cursor.
///
/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.InvalidValue
/// null is returned in the event of an error.
///
/// @pointer_lifetime The specified image data is copied before this function returns.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: cursor_object, glfw.Cursor.destroy, glfw.Cursor.createStandard
pub inline fn create(image: Image, xhot: i32, yhot: i32) ?Cursor {
internal_debug.assertInitialized();
const img = image.toC();
if (c.glfwCreateCursor(&img, @as(c_int, @intCast(xhot)), @as(c_int, @intCast(yhot)))) |cursor| return Cursor{ .ptr = cursor };
return null;
}
/// Creates a cursor with a standard shape.
///
/// Returns a cursor with a standard shape, that can be set for a window with glfw.Window.setCursor.
/// The images for these cursors come from the system cursor theme and their exact appearance will
/// vary between platforms.
///
/// Most of these shapes are guaranteed to exist on every supported platform but a few may not be
/// present. See the table below for details.
///
/// | Cursor shape | Windows | macOS | X11 | Wayland |
/// |------------------|---------|-----------------|-------------------|-------------------|
/// | `.arrow` | Yes | Yes | Yes | Yes |
/// | `.ibeam` | Yes | Yes | Yes | Yes |
/// | `.crosshair` | Yes | Yes | Yes | Yes |
/// | `.pointing_hand` | Yes | Yes | Yes | Yes |
/// | `.resize_ew` | Yes | Yes | Yes | Yes |
/// | `.resize_ns` | Yes | Yes | Yes | Yes |
/// | `.resize_nwse` | Yes | Yes<sup>1</sup> | Maybe<sup>2</sup> | Maybe<sup>2</sup> |
/// | `.resize_nesw` | Yes | Yes<sup>1</sup> | Maybe<sup>2</sup> | Maybe<sup>2</sup> |
/// | `.resize_all` | Yes | Yes | Yes | Yes |
/// | `.not_allowed` | Yes | Yes | Maybe<sup>2</sup> | Maybe<sup>2</sup> |
///
/// 1. This uses a private system API and may fail in the future.
/// 2. This uses a newer standard that not all cursor themes support.
///
/// If the requested shape is not available, this function emits a CursorUnavailable error
/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.CursorUnavailable.
/// null is returned in the event of an error.
///
/// thread_safety: This function must only be called from the main thread.
///
/// see also: cursor_object, glfwCreateCursor
pub inline fn createStandard(shape: Shape) ?Cursor {
internal_debug.assertInitialized();
if (c.glfwCreateStandardCursor(@as(c_int, @intCast(@intFromEnum(shape))))) |cursor| return Cursor{ .ptr = cursor };
return null;
}
/// Destroys a cursor.
///
/// This function destroys a cursor previously created with glfw.Cursor.create. Any remaining
/// cursors will be destroyed by glfw.terminate.
///
/// If the specified cursor is current for any window, that window will be reverted to the default
/// cursor. This does not affect the cursor mode.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: cursor_object, glfw.createCursor
pub inline fn destroy(self: Cursor) void {
internal_debug.assertInitialized();
c.glfwDestroyCursor(self.ptr);
}
test "create" {
const allocator = testing.allocator;
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const image = try Image.init(allocator, 32, 32, 32 * 32 * 4);
defer image.deinit(allocator);
const cursor = glfw.Cursor.create(image, 0, 0);
if (cursor) |cur| cur.destroy();
}
test "createStandard" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const cursor = glfw.Cursor.createStandard(.ibeam);
if (cursor) |cur| cur.destroy();
}

74
pkg/glfw/GammaRamp.zig Normal file
View File

@@ -0,0 +1,74 @@
//! Gamma ramp for monitors and related functions.
//!
//! It may be .owned (e.g. in the case of a gamma ramp initialized by you for passing into
//! glfw.Monitor.setGammaRamp) or not .owned (e.g. in the case of one gotten via
//! glfw.Monitor.getGammaRamp.) If it is .owned, deinit should be called to free the memory. It is
//! safe to call deinit even if not .owned.
//!
//! see also: monitor_gamma, glfw.Monitor.getGammaRamp
const std = @import("std");
const testing = std.testing;
const mem = std.mem;
const c = @import("c.zig").c;
const GammaRamp = @This();
red: []u16,
green: []u16,
blue: []u16,
owned: ?[]u16,
/// Initializes a new owned gamma ramp with the given array size and undefined values.
///
/// see also: glfw.Monitor.getGammaRamp
pub inline fn init(allocator: mem.Allocator, size: usize) !GammaRamp {
const buf = try allocator.alloc(u16, size * 3);
return GammaRamp{
.red = buf[size * 0 .. (size * 0) + size],
.green = buf[size * 1 .. (size * 1) + size],
.blue = buf[size * 2 .. (size * 2) + size],
.owned = buf,
};
}
/// Turns a GLFW / C gamma ramp into the nicer Zig type, and sets `.owned = false`.
///
/// The returned memory is valid for as long as the GLFW C memory is valid.
pub inline fn fromC(native: c.GLFWgammaramp) GammaRamp {
return GammaRamp{
.red = native.red[0..native.size],
.green = native.green[0..native.size],
.blue = native.blue[0..native.size],
.owned = null,
};
}
/// Turns the nicer Zig type into a GLFW / C gamma ramp, for passing into GLFW C functions.
///
/// The returned memory is valid for as long as the Zig memory is valid.
pub inline fn toC(self: GammaRamp) c.GLFWgammaramp {
std.debug.assert(self.red.len == self.green.len);
std.debug.assert(self.red.len == self.blue.len);
return c.GLFWgammaramp{
.red = &self.red[0],
.green = &self.green[0],
.blue = &self.blue[0],
.size = @as(c_uint, @intCast(self.red.len)),
};
}
/// Deinitializes the memory using the allocator iff `.owned = true`.
pub inline fn deinit(self: GammaRamp, allocator: mem.Allocator) void {
if (self.owned) |buf| allocator.free(buf);
}
test "conversion" {
const allocator = testing.allocator;
const ramp = try GammaRamp.init(allocator, 256);
defer ramp.deinit(allocator);
const glfw = ramp.toC();
_ = GammaRamp.fromC(glfw);
}

82
pkg/glfw/Image.zig Normal file
View File

@@ -0,0 +1,82 @@
//! Image data
//!
//!
//! This describes a single 2D image. See the documentation for each related function what the
//! expected pixel format is.
//!
//! see also: cursor_custom, window_icon
//!
//! It may be .owned (e.g. in the case of an image initialized by you for passing into glfw) or not
//! .owned (e.g. in the case of one gotten via glfw) If it is .owned, deinit should be called to
//! free the memory. It is safe to call deinit even if not .owned.
const std = @import("std");
const testing = std.testing;
const mem = std.mem;
const c = @import("c.zig").c;
const Image = @This();
/// The width of this image, in pixels.
width: u32,
/// The height of this image, in pixels.
height: u32,
/// The pixel data of this image, arranged left-to-right, top-to-bottom.
pixels: []u8,
/// Whether or not the pixels data is owned by you (true) or GLFW (false).
owned: bool,
/// Initializes a new owned image with the given size and pixel_data_len of undefined .pixel values.
pub inline fn init(allocator: mem.Allocator, width: u32, height: u32, pixel_data_len: usize) !Image {
const buf = try allocator.alloc(u8, pixel_data_len);
return Image{
.width = width,
.height = height,
.pixels = buf,
.owned = true,
};
}
/// Turns a GLFW / C image into the nicer Zig type, and sets `.owned = false`.
///
/// The length of pixel data must be supplied, as GLFW's image type does not itself describe the
/// number of bytes required per pixel / the length of the pixel data array.
///
/// The returned memory is valid for as long as the GLFW C memory is valid.
pub inline fn fromC(native: c.GLFWimage, pixel_data_len: usize) Image {
return Image{
.width = @as(u32, @intCast(native.width)),
.height = @as(u32, @intCast(native.height)),
.pixels = native.pixels[0..pixel_data_len],
.owned = false,
};
}
/// Turns the nicer Zig type into a GLFW / C image, for passing into GLFW C functions.
///
/// The returned memory is valid for as long as the Zig memory is valid.
pub inline fn toC(self: Image) c.GLFWimage {
return c.GLFWimage{
.width = @as(c_int, @intCast(self.width)),
.height = @as(c_int, @intCast(self.height)),
.pixels = &self.pixels[0],
};
}
/// Deinitializes the memory using the allocator iff `.owned = true`.
pub inline fn deinit(self: Image, allocator: mem.Allocator) void {
if (self.owned) allocator.free(self.pixels);
}
test "conversion" {
const allocator = testing.allocator;
const image = try Image.init(allocator, 256, 256, 256 * 256 * 4);
defer image.deinit(allocator);
const glfw = image.toC();
_ = Image.fromC(glfw, image.width * image.height * 4);
}

642
pkg/glfw/Joystick.zig Normal file
View File

@@ -0,0 +1,642 @@
//! Represents a Joystick or gamepad
//!
//! It can be manually crafted via e.g. `glfw.Joystick{.jid = .one}`, but more
//! typically you'll want to discover the joystick using `glfw.Joystick.setCallback`.
const std = @import("std");
const c = @import("c.zig").c;
const Window = @import("Window.zig");
const Action = @import("action.zig").Action;
const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis;
const GamepadButton = @import("gamepad_button.zig").GamepadButton;
const Hat = @import("hat.zig").Hat;
const internal_debug = @import("internal_debug.zig");
const Joystick = @This();
/// The GLFW joystick ID.
jid: Id,
/// Joystick IDs.
///
/// See glfw.Joystick.setCallback for how these are used.
pub const Id = enum(c_int) {
one = c.GLFW_JOYSTICK_1,
two = c.GLFW_JOYSTICK_2,
three = c.GLFW_JOYSTICK_3,
four = c.GLFW_JOYSTICK_4,
five = c.GLFW_JOYSTICK_5,
six = c.GLFW_JOYSTICK_6,
seven = c.GLFW_JOYSTICK_7,
eight = c.GLFW_JOYSTICK_8,
nine = c.GLFW_JOYSTICK_9,
ten = c.GLFW_JOYSTICK_10,
eleven = c.GLFW_JOYSTICK_11,
twelve = c.GLFW_JOYSTICK_12,
thirteen = c.GLFW_JOYSTICK_13,
fourteen = c.GLFW_JOYSTICK_14,
fifteen = c.GLFW_JOYSTICK_15,
sixteen = c.GLFW_JOYSTICK_16,
pub const last = @as(@This(), @enumFromInt(c.GLFW_JOYSTICK_LAST));
};
/// Gamepad input state
///
/// This describes the input state of a gamepad.
///
/// see also: gamepad, glfwGetGamepadState
const GamepadState = extern struct {
/// The states of each gamepad button (see gamepad_buttons), `glfw.Action.press` or `glfw.Action.release`.
///
/// Use the enumeration helper e.g. `.getButton(.dpad_up)` to access these indices.
buttons: [15]u8,
/// The states of each gamepad axis (see gamepad_axes), in the range -1.0 to 1.0 inclusive.
///
/// Use the enumeration helper e.g. `.getAxis(.left_x)` to access these indices.
axes: [6]f32,
/// Returns the state of the specified gamepad button.
pub fn getButton(self: @This(), which: GamepadButton) Action {
return @as(Action, @enumFromInt(self.buttons[@as(u32, @intCast(@intFromEnum(which)))]));
}
/// Returns the status of the specified gamepad axis, in the range -1.0 to 1.0 inclusive.
pub fn getAxis(self: @This(), which: GamepadAxis) f32 {
return self.axes[@as(u32, @intCast(@intFromEnum(which)))];
}
};
/// Returns whether the specified joystick is present.
///
/// This function returns whether the specified joystick is present.
///
/// There is no need to call this function before other functions that accept a joystick ID, as
/// they all check for presence before performing any other work.
///
/// @return `true` if the joystick is present, or `false` otherwise.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick
pub inline fn present(self: Joystick) bool {
internal_debug.assertInitialized();
const is_present = c.glfwJoystickPresent(@intFromEnum(self.jid));
return is_present == c.GLFW_TRUE;
}
/// Returns the values of all axes of the specified joystick.
///
/// This function returns the values of all axes of the specified joystick. Each element in the
/// array is a value between -1.0 and 1.0.
///
/// If the specified joystick is not present this function will return null but will not generate
/// an error. This can be used instead of first calling glfw.Joystick.present.
///
/// @return An array of axis values, or null if the joystick is not present.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// null is additionally returned in the event of an error.
///
/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected or the library is
/// terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick_axis
/// Replaces `glfwGetJoystickPos`.
pub inline fn getAxes(self: Joystick) ?[]const f32 {
internal_debug.assertInitialized();
var count: c_int = undefined;
const axes = c.glfwGetJoystickAxes(@intFromEnum(self.jid), &count);
if (axes == null) return null;
return axes[0..@as(u32, @intCast(count))];
}
/// Returns the state of all buttons of the specified joystick.
///
/// This function returns the state of all buttons of the specified joystick. Each element in the
/// array is either `glfw.Action.press` or `glfw.Action.release`.
///
/// For backward compatibility with earlier versions that did not have glfw.Joystick.getHats, the
/// button array also includes all hats, each represented as four buttons. The hats are in the same
/// order as returned by glfw.Joystick.getHats and are in the order _up_, _right_, _down_ and
/// _left_. To disable these extra buttons, set the glfw.joystick_hat_buttons init hint before
/// initialization.
///
/// If the specified joystick is not present this function will return null but will not generate an
/// error. This can be used instead of first calling glfw.Joystick.present.
///
/// @return An array of button states, or null if the joystick is not present.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// null is additionally returned in the event of an error.
///
/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick_button
pub inline fn getButtons(self: Joystick) ?[]const u8 {
internal_debug.assertInitialized();
var count: c_int = undefined;
const buttons = c.glfwGetJoystickButtons(@intFromEnum(self.jid), &count);
if (buttons == null) return null;
return buttons[0..@as(u32, @intCast(count))];
}
/// Returns the state of all hats of the specified joystick.
///
/// This function returns the state of all hats of the specified joystick. Each element in the array
/// is one of the following values:
///
/// | Name | Value |
/// |---------------------------|---------------------------------------------|
/// | `glfw.RawHats.centered` | 0 |
/// | `glfw.RawHats.up` | 1 |
/// | `glfw.RawHats.right` | 2 |
/// | `glfw.RawHats.down` | 4 |
/// | `glfw.RawHats.left` | 8 |
/// | `glfw.RawHats.right_up` | `glfw.RawHats.right` \| `glfw.RawHats.up` |
/// | `glfw.RawHats.right_down` | `glfw.RawHats.right` \| `glfw.RawHats.down` |
/// | `glfw.RawHats.left_up` | `glfw.RawHats.left` \| `glfw.RawHats.up` |
/// | `glfw.RawHats.left_down` | `glfw.RawHats.left` \| `glfw.RawHats.down` |
///
/// The diagonal directions are bitwise combinations of the primary (up, right, down and left)
/// directions, since the Zig GLFW wrapper returns a packed struct it is trivial to test for these:
///
/// ```
/// if (hats.up and hats.right) {
/// // up-right!
/// }
/// ```
///
/// If the specified joystick is not present this function will return null but will not generate an
/// error. This can be used instead of first calling glfw.Joystick.present.
///
/// @return An array of hat states, or null if the joystick is not present.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// null is additionally returned in the event of an error.
///
/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected, this function is called
/// again for that joystick or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick_hat
pub inline fn getHats(self: Joystick) ?[]const Hat {
internal_debug.assertInitialized();
var count: c_int = undefined;
const hats = c.glfwGetJoystickHats(@intFromEnum(self.jid), &count);
if (hats == null) return null;
const slice = hats[0..@as(u32, @intCast(count))];
return @as(*const []const Hat, @ptrCast(&slice)).*;
}
/// Returns the name of the specified joystick.
///
/// This function returns the name, encoded as UTF-8, of the specified joystick. The returned string
/// is allocated and freed by GLFW. You should not free it yourself.
///
/// If the specified joystick is not present this function will return null but will not generate an
/// error. This can be used instead of first calling glfw.Joystick.present.
///
/// @return The UTF-8 encoded name of the joystick, or null if the joystick is not present or an
/// error occurred.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// null is additionally returned in the event of an error.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick_name
pub inline fn getName(self: Joystick) ?[:0]const u8 {
internal_debug.assertInitialized();
const name_opt = c.glfwGetJoystickName(@intFromEnum(self.jid));
return if (name_opt) |name|
std.mem.span(@as([*:0]const u8, @ptrCast(name)))
else
null;
}
/// Returns the SDL compatible GUID of the specified joystick.
///
/// This function returns the SDL compatible GUID, as a UTF-8 encoded hexadecimal string, of the
/// specified joystick. The returned string is allocated and freed by GLFW. You should not free it
/// yourself.
///
/// The GUID is what connects a joystick to a gamepad mapping. A connected joystick will always have
/// a GUID even if there is no gamepad mapping assigned to it.
///
/// If the specified joystick is not present this function will return null but will not generate an
/// error. This can be used instead of first calling glfw.Joystick.present.
///
/// The GUID uses the format introduced in SDL 2.0.5. This GUID tries to uniquely identify the make
/// and model of a joystick but does not identify a specific unit, e.g. all wired Xbox 360
/// controllers will have the same GUID on that platform. The GUID for a unit may vary between
/// platforms depending on what hardware information the platform specific APIs provide.
///
/// @return The UTF-8 encoded GUID of the joystick, or null if the joystick is not present.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// null is additionally returned in the event of an error.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: gamepad
pub inline fn getGUID(self: Joystick) ?[:0]const u8 {
internal_debug.assertInitialized();
const guid_opt = c.glfwGetJoystickGUID(@intFromEnum(self.jid));
return if (guid_opt) |guid|
std.mem.span(@as([*:0]const u8, @ptrCast(guid)))
else
null;
}
/// Sets the user pointer of the specified joystick.
///
/// This function sets the user-defined pointer of the specified joystick. The current value is
/// retained until the joystick is disconnected. The initial value is null.
///
/// This function may be called from the joystick callback, even for a joystick that is being disconnected.
///
/// @thread_safety This function may be called from any thread. Access is not synchronized.
///
/// see also: joystick_userptr, glfw.Joystick.getUserPointer
pub inline fn setUserPointer(self: Joystick, comptime T: type, pointer: *T) void {
internal_debug.assertInitialized();
c.glfwSetJoystickUserPointer(@intFromEnum(self.jid), @as(*anyopaque, @ptrCast(pointer)));
}
/// Returns the user pointer of the specified joystick.
///
/// This function returns the current value of the user-defined pointer of the specified joystick.
/// The initial value is null.
///
/// This function may be called from the joystick callback, even for a joystick that is being
/// disconnected.
///
/// @thread_safety This function may be called from any thread. Access is not synchronized.
///
/// see also: joystick_userptr, glfw.Joystick.setUserPointer
pub inline fn getUserPointer(self: Joystick, comptime PointerType: type) ?PointerType {
internal_debug.assertInitialized();
const ptr = c.glfwGetJoystickUserPointer(@intFromEnum(self.jid));
if (ptr) |p| return @as(PointerType, @ptrCast(@alignCast(p)));
return null;
}
/// Describes an event relating to a joystick.
pub const Event = enum(c_int) {
/// The device was connected.
connected = c.GLFW_CONNECTED,
/// The device was disconnected.
disconnected = c.GLFW_DISCONNECTED,
};
/// Sets the joystick configuration callback.
///
/// This function sets the joystick configuration callback, or removes the currently set callback.
/// This is called when a joystick is connected to or disconnected from the system.
///
/// For joystick connection and disconnection events to be delivered on all platforms, you need to
/// call one of the event processing (see events) functions. Joystick disconnection may also be
/// detected and the callback called by joystick functions. The function will then return whatever
/// it returns if the joystick is not present.
///
/// @param[in] callback The new callback, or null to remove the currently set callback.
///
/// @callback_param `jid` The joystick that was connected or disconnected.
/// @callback_param `event` One of `.connected` or `.disconnected`. Future releases may add
/// more events.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: joystick_event
pub inline fn setCallback(comptime callback: ?fn (joystick: Joystick, event: Event) void) void {
internal_debug.assertInitialized();
if (callback) |user_callback| {
const CWrapper = struct {
pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.C) void {
@call(.always_inline, user_callback, .{
Joystick{ .jid = @as(Joystick.Id, @enumFromInt(jid)) },
@as(Event, @enumFromInt(event)),
});
}
};
if (c.glfwSetJoystickCallback(CWrapper.joystickCallbackWrapper) != null) return;
} else {
if (c.glfwSetJoystickCallback(null) != null) return;
}
}
/// Adds the specified SDL_GameControllerDB gamepad mappings.
///
/// This function parses the specified ASCII encoded string and updates the internal list with any
/// gamepad mappings it finds. This string may contain either a single gamepad mapping or many
/// mappings separated by newlines. The parser supports the full format of the `gamecontrollerdb.txt`
/// source file including empty lines and comments.
///
/// See gamepad_mapping for a description of the format.
///
/// If there is already a gamepad mapping for a given GUID in the internal list, it will be
/// replaced by the one passed to this function. If the library is terminated and re-initialized
/// the internal list will revert to the built-in default.
///
/// @param[in] string The string containing the gamepad mappings.
///
/// Possible errors include glfw.ErrorCode.InvalidValue.
/// Returns a boolean indicating success.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: gamepad, glfw.Joystick.isGamepad, glfwGetGamepadName
///
///
/// @ingroup input
pub inline fn updateGamepadMappings(gamepad_mappings: [*:0]const u8) bool {
internal_debug.assertInitialized();
return c.glfwUpdateGamepadMappings(gamepad_mappings) == c.GLFW_TRUE;
}
/// Returns whether the specified joystick has a gamepad mapping.
///
/// This function returns whether the specified joystick is both present and has a gamepad mapping.
///
/// If the specified joystick is present but does not have a gamepad mapping this function will
/// return `false` but will not generate an error. Call glfw.Joystick.present to check if a
/// joystick is present regardless of whether it has a mapping.
///
/// @return `true` if a joystick is both present and has a gamepad mapping, or `false` otherwise.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum.
/// Additionally returns false in the event of an error.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: gamepad, glfw.Joystick.getGamepadState
pub inline fn isGamepad(self: Joystick) bool {
internal_debug.assertInitialized();
const is_gamepad = c.glfwJoystickIsGamepad(@intFromEnum(self.jid));
return is_gamepad == c.GLFW_TRUE;
}
/// Returns the human-readable gamepad name for the specified joystick.
///
/// This function returns the human-readable name of the gamepad from the gamepad mapping assigned
/// to the specified joystick.
///
/// If the specified joystick is not present or does not have a gamepad mapping this function will
/// return null, not an error. Call glfw.Joystick.present to check whether it is
/// present regardless of whether it has a mapping.
///
/// @return The UTF-8 encoded name of the gamepad, or null if the joystick is not present or does
/// not have a mapping.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum.
/// Additionally returns null in the event of an error.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified joystick is disconnected, the gamepad mappings are
/// updated or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: gamepad, glfw.Joystick.isGamepad
pub inline fn getGamepadName(self: Joystick) ?[:0]const u8 {
internal_debug.assertInitialized();
const name_opt = c.glfwGetGamepadName(@intFromEnum(self.jid));
return if (name_opt) |name|
std.mem.span(@as([*:0]const u8, @ptrCast(name)))
else
null;
}
/// Retrieves the state of the joystick remapped as a gamepad.
///
/// This function retrieves the state of the joystick remapped to an Xbox-like gamepad.
///
/// If the specified joystick is not present or does not have a gamepad mapping this function will
/// return `false`. Call glfw.joystickPresent to check whether it is present regardless of whether
/// it has a mapping.
///
/// The Guide button may not be available for input as it is often hooked by the system or the
/// Steam client.
///
/// Not all devices have all the buttons or axes provided by GamepadState. Unavailable buttons
/// and axes will always report `glfw.Action.release` and 0.0 respectively.
///
/// @param[in] jid The joystick (see joysticks) to query.
/// @param[out] state The gamepad input state of the joystick.
/// @return the gamepad input state if successful, or null if no joystick is connected or it has no
/// gamepad mapping.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum.
/// Returns null in the event of an error.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: gamepad, glfw.UpdateGamepadMappings, glfw.Joystick.isGamepad
pub inline fn getGamepadState(self: Joystick) ?GamepadState {
internal_debug.assertInitialized();
var state: GamepadState = undefined;
const success = c.glfwGetGamepadState(@intFromEnum(self.jid), @as(*c.GLFWgamepadstate, @ptrCast(&state)));
return if (success == c.GLFW_TRUE) state else null;
}
test "present" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.present();
}
test "getAxes" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getAxes();
}
test "getButtons" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getButtons();
}
test "getHats" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
if (joystick.getHats()) |hats| {
for (hats) |hat| {
if (hat.down and hat.up) {
// down-up!
}
}
}
}
test "getName" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getName();
}
test "getGUID" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getGUID();
}
test "setUserPointer_syntax" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
// Must be called from joystick callback, we cannot test it.
_ = joystick;
_ = setUserPointer;
}
test "getUserPointer_syntax" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
// Must be called from joystick callback, we cannot test it.
_ = joystick;
_ = getUserPointer;
}
test "setCallback" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
glfw.Joystick.setCallback((struct {
pub fn callback(joystick: Joystick, event: Event) void {
_ = joystick;
_ = event;
}
}).callback);
}
test "updateGamepadMappings_syntax" {
// We don't have a gamepad mapping to test with, just confirm the syntax is good.
_ = updateGamepadMappings;
}
test "isGamepad" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.isGamepad();
}
test "getGamepadName" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getGamepadName();
}
test "getGamepadState" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const joystick = glfw.Joystick{ .jid = .one };
_ = joystick.getGamepadState();
_ = (std.mem.zeroes(GamepadState)).getAxis(.left_x);
_ = (std.mem.zeroes(GamepadState)).getButton(.dpad_up);
}

26
pkg/glfw/LICENSE Normal file
View File

@@ -0,0 +1,26 @@
Copyright (c) 2021 Hexops Contributors (given via the Git commit history).
Copyright (c) 2025 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

599
pkg/glfw/Monitor.zig Normal file
View File

@@ -0,0 +1,599 @@
//! Monitor type and related functions
const std = @import("std");
const mem = std.mem;
const testing = std.testing;
const c = @import("c.zig").c;
const GammaRamp = @import("GammaRamp.zig");
const VideoMode = @import("VideoMode.zig");
const internal_debug = @import("internal_debug.zig");
const Monitor = @This();
handle: *c.GLFWmonitor,
/// A monitor position, in screen coordinates, of the upper left corner of the monitor on the
/// virtual screen.
const Pos = struct {
/// The x coordinate.
x: u32,
/// The y coordinate.
y: u32,
};
/// Returns the position of the monitor's viewport on the virtual screen.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_properties
pub inline fn getPos(self: Monitor) Pos {
internal_debug.assertInitialized();
var xpos: c_int = 0;
var ypos: c_int = 0;
c.glfwGetMonitorPos(self.handle, &xpos, &ypos);
return Pos{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)) };
}
/// The monitor workarea, in screen coordinates.
///
/// This is the position of the upper-left corner of the work area of the monitor, along with the
/// work area size. The work area is defined as the area of the monitor not occluded by the
/// window system task bar where present. If no task bar exists then the work area is the
/// monitor resolution in screen coordinates.
const Workarea = struct {
x: u32,
y: u32,
width: u32,
height: u32,
};
/// Retrieves the work area of the monitor.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
/// A zero value is returned in the event of an error.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_workarea
pub inline fn getWorkarea(self: Monitor) Workarea {
internal_debug.assertInitialized();
var xpos: c_int = 0;
var ypos: c_int = 0;
var width: c_int = 0;
var height: c_int = 0;
c.glfwGetMonitorWorkarea(self.handle, &xpos, &ypos, &width, &height);
return Workarea{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)), .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) };
}
/// The physical size, in millimetres, of the display area of a monitor.
const PhysicalSize = struct {
width_mm: u32,
height_mm: u32,
};
/// Returns the physical size of the monitor.
///
/// Some platforms do not provide accurate monitor size information, either because the monitor
/// [EDID](https://en.wikipedia.org/wiki/Extended_display_identification_data)
/// data is incorrect or because the driver does not report it accurately.
///
/// win32: On Windows 8 and earlier the physical size is calculated from
/// the current resolution and system DPI instead of querying the monitor EDID data
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_properties
pub inline fn getPhysicalSize(self: Monitor) PhysicalSize {
internal_debug.assertInitialized();
var width_mm: c_int = 0;
var height_mm: c_int = 0;
c.glfwGetMonitorPhysicalSize(self.handle, &width_mm, &height_mm);
return PhysicalSize{ .width_mm = @as(u32, @intCast(width_mm)), .height_mm = @as(u32, @intCast(height_mm)) };
}
/// The content scale for a monitor.
///
/// This is the ratio between the current DPI and the platform's default DPI. This is especially
/// important for text and any UI elements. If the pixel dimensions of your UI scaled by this look
/// appropriate on your machine then it should appear at a reasonable size on other machines
/// regardless of their DPI and scaling settings. This relies on the system DPI and scaling
/// settings being somewhat correct.
///
/// The content scale may depend on both the monitor resolution and pixel density and on users
/// settings. It may be very different from the raw DPI calculated from the physical size and
/// current resolution.
const ContentScale = struct {
x_scale: f32,
y_scale: f32,
};
/// Returns the content scale for the monitor.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
/// A zero value is returned in the event of an error.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_scale, glfw.Window.getContentScale
pub inline fn getContentScale(self: Monitor) ContentScale {
internal_debug.assertInitialized();
var x_scale: f32 = 0;
var y_scale: f32 = 0;
c.glfwGetMonitorContentScale(self.handle, &x_scale, &y_scale);
return ContentScale{ .x_scale = @as(f32, @floatCast(x_scale)), .y_scale = @as(f32, @floatCast(y_scale)) };
}
/// Returns the name of the specified monitor.
///
/// This function returns a human-readable name, encoded as UTF-8, of the specified monitor. The
/// name typically reflects the make and model of the monitor and is not guaranteed to be unique
/// among the connected monitors.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the specified monitor is disconnected or the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_properties
pub inline fn getName(self: Monitor) [*:0]const u8 {
internal_debug.assertInitialized();
if (c.glfwGetMonitorName(self.handle)) |name| return @as([*:0]const u8, @ptrCast(name));
// `glfwGetMonitorName` returns `null` only for errors, but the only error is unreachable
// (NotInitialized)
unreachable;
}
/// Sets the user pointer of the specified monitor.
///
/// This function sets the user-defined pointer of the specified monitor. The current value is
/// retained until the monitor is disconnected.
///
/// This function may be called from the monitor callback, even for a monitor that is being
/// disconnected.
///
/// @thread_safety This function may be called from any thread. Access is not synchronized.
///
/// see also: monitor_userptr, glfw.Monitor.getUserPointer
pub inline fn setUserPointer(self: Monitor, comptime T: type, ptr: *T) void {
internal_debug.assertInitialized();
c.glfwSetMonitorUserPointer(self.handle, ptr);
}
/// Returns the user pointer of the specified monitor.
///
/// This function returns the current value of the user-defined pointer of the specified monitor.
///
/// This function may be called from the monitor callback, even for a monitor that is being
/// disconnected.
///
/// @thread_safety This function may be called from any thread. Access is not synchronized.
///
/// see also: monitor_userptr, glfw.Monitor.setUserPointer
pub inline fn getUserPointer(self: Monitor, comptime T: type) ?*T {
internal_debug.assertInitialized();
const ptr = c.glfwGetMonitorUserPointer(self.handle);
if (ptr == null) return null;
return @as(*T, @ptrCast(@alignCast(ptr.?)));
}
/// Returns the available video modes for the specified monitor.
///
/// This function returns an array of all video modes supported by the monitor. The returned slice
/// is sorted in ascending order, first by color bit depth (the sum of all channel depths) and
/// then by resolution area (the product of width and height), then resolution width and finally
/// by refresh rate.
///
/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable.
/// Returns null in the event of an error.
///
/// The returned slice memory is owned by the caller.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_modes, glfw.Monitor.getVideoMode
///
/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and
/// emits glfw.ErrorCode.FeatureUnavailable
///
/// TODO(glfw): rewrite this to not require any allocation.
pub inline fn getVideoModes(self: Monitor, allocator: mem.Allocator) mem.Allocator.Error!?[]VideoMode {
internal_debug.assertInitialized();
var count: c_int = 0;
if (c.glfwGetVideoModes(self.handle, &count)) |modes| {
const slice = try allocator.alloc(VideoMode, @as(u32, @intCast(count)));
var i: u32 = 0;
while (i < count) : (i += 1) {
slice[i] = VideoMode{ .handle = @as([*c]const c.GLFWvidmode, @ptrCast(modes))[i] };
}
return slice;
}
return null;
}
/// Returns the current mode of the specified monitor.
///
/// This function returns the current video mode of the specified monitor. If you have created a
/// full screen window for that monitor, the return value will depend on whether that window is
/// iconified.
///
/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable.
/// Additionally returns null in the event of an error.
///
/// @thread_safety This function must only be called from the main thread.
///
/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented
/// and will thus never be implemented and emits glfw.ErrorCode.FeatureUnavailable
///
/// see also: monitor_modes, glfw.Monitor.getVideoModes
pub inline fn getVideoMode(self: Monitor) ?VideoMode {
internal_debug.assertInitialized();
if (c.glfwGetVideoMode(self.handle)) |mode| return VideoMode{ .handle = mode.* };
return null;
}
/// Generates a gamma ramp and sets it for the specified monitor.
///
/// This function generates an appropriately sized gamma ramp from the specified exponent and then
/// calls glfw.Monitor.setGammaRamp with it. The value must be a finite number greater than zero.
///
/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction,
/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly
/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior.
///
/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint.
///
/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable.
///
/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and
/// emits glfw.ErrorCode.FeatureUnavailable
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_gamma
pub inline fn setGamma(self: Monitor, gamma: f32) void {
internal_debug.assertInitialized();
std.debug.assert(!std.math.isNan(gamma));
std.debug.assert(gamma >= 0);
std.debug.assert(gamma <= std.math.f32_max);
c.glfwSetGamma(self.handle, gamma);
}
/// Returns the current gamma ramp for the specified monitor.
///
/// This function returns the current gamma ramp of the specified monitor.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
/// Additionally returns null in the event of an error.
///
/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented
/// and returns glfw.ErrorCode.FeatureUnavailable.
///
/// The returned gamma ramp is `.owned = true` by GLFW, and is valid until the monitor is
/// disconnected, this function is called again, or `glfw.terminate()` is called.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_gamma
pub inline fn getGammaRamp(self: Monitor) ?GammaRamp {
internal_debug.assertInitialized();
if (c.glfwGetGammaRamp(self.handle)) |ramp| return GammaRamp.fromC(ramp.*);
return null;
}
/// Sets the current gamma ramp for the specified monitor.
///
/// This function sets the current gamma ramp for the specified monitor. The original gamma ramp
/// for that monitor is saved by GLFW the first time this function is called and is restored by
/// `glfw.terminate()`.
///
/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction,
/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly
/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior.
///
/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint.
///
/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable.
///
/// The size of the specified gamma ramp should match the size of the current ramp for that
/// monitor. On win32, the gamma ramp size must be 256.
///
/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented
/// and returns glfw.ErrorCode.FeatureUnavailable.
///
/// @pointer_lifetime The specified gamma ramp is copied before this function returns.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_gamma
pub inline fn setGammaRamp(self: Monitor, ramp: GammaRamp) void {
internal_debug.assertInitialized();
c.glfwSetGammaRamp(self.handle, &ramp.toC());
}
/// Returns the currently connected monitors.
///
/// This function returns a slice of all currently connected monitors. The primary monitor is
/// always first. If no monitors were found, this function returns an empty slice.
///
/// The returned slice memory is owned by the caller. The underlying handles are owned by GLFW, and
/// are valid until the monitor configuration changes or `glfw.terminate` is called.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_monitors, monitor_event, glfw.monitor.getPrimary
pub inline fn getAll(allocator: mem.Allocator) mem.Allocator.Error![]Monitor {
internal_debug.assertInitialized();
var count: c_int = 0;
if (c.glfwGetMonitors(&count)) |monitors| {
const slice = try allocator.alloc(Monitor, @as(u32, @intCast(count)));
var i: u32 = 0;
while (i < count) : (i += 1) {
slice[i] = Monitor{ .handle = @as([*c]const ?*c.GLFWmonitor, @ptrCast(monitors))[i].? };
}
return slice;
}
// `glfwGetMonitors` returning null can be either an error or no monitors, but the only error is
// unreachable (NotInitialized)
return &[_]Monitor{};
}
/// Returns the primary monitor.
///
/// This function returns the primary monitor. This is usually the monitor where elements like
/// the task bar or global menu bar are located.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_monitors, glfw.monitors.getAll
pub inline fn getPrimary() ?Monitor {
internal_debug.assertInitialized();
if (c.glfwGetPrimaryMonitor()) |handle| return Monitor{ .handle = handle };
return null;
}
/// Describes an event relating to a monitor.
pub const Event = enum(c_int) {
/// The device was connected.
connected = c.GLFW_CONNECTED,
/// The device was disconnected.
disconnected = c.GLFW_DISCONNECTED,
};
/// Sets the monitor configuration callback.
///
/// This function sets the monitor configuration callback, or removes the currently set callback.
/// This is called when a monitor is connected to or disconnected from the system. Example:
///
/// ```
/// fn monitorCallback(monitor: glfw.Monitor, event: glfw.Monitor.Event, data: *MyData) void {
/// // data is the pointer you passed into setCallback.
/// // event is one of .connected or .disconnected
/// }
/// ...
/// glfw.Monitor.setCallback(MyData, &myData, monitorCallback)
/// ```
///
/// `event` may be one of .connected or .disconnected. More events may be added in the future.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: monitor_event
pub inline fn setCallback(comptime callback: ?fn (monitor: Monitor, event: Event) void) void {
internal_debug.assertInitialized();
if (callback) |user_callback| {
const CWrapper = struct {
pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.C) void {
@call(.always_inline, user_callback, .{
Monitor{ .handle = monitor.? },
@as(Event, @enumFromInt(event)),
});
}
};
if (c.glfwSetMonitorCallback(CWrapper.monitorCallbackWrapper) != null) return;
} else {
if (c.glfwSetMonitorCallback(null) != null) return;
}
}
test "getAll" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const allocator = testing.allocator;
const monitors = try getAll(allocator);
defer allocator.free(monitors);
}
test "getPrimary" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
_ = getPrimary();
}
test "getPos" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getPos();
}
}
test "getWorkarea" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getWorkarea();
}
}
test "getPhysicalSize" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getPhysicalSize();
}
}
test "getContentScale" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getContentScale();
}
}
test "getName" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getName();
}
}
test "userPointer" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
var p = m.getUserPointer(u32);
try testing.expect(p == null);
var x: u32 = 5;
m.setUserPointer(u32, &x);
p = m.getUserPointer(u32);
try testing.expectEqual(p.?.*, 5);
}
}
test "setCallback" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
setCallback(struct {
fn callback(monitor: Monitor, event: Event) void {
_ = monitor;
_ = event;
}
}.callback);
}
test "getVideoModes" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
const allocator = testing.allocator;
const modes_maybe = try m.getVideoModes(allocator);
if (modes_maybe) |modes| {
defer allocator.free(modes);
}
}
}
test "getVideoMode" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
_ = m.getVideoMode();
}
}
test "set_getGammaRamp" {
const allocator = testing.allocator;
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
const monitor = getPrimary();
if (monitor) |m| {
if (m.getGammaRamp()) |ramp| {
// Set it to the exact same value; if we do otherwise an our tests fail it wouldn't call
// terminate and our made-up gamma ramp would get stuck.
m.setGammaRamp(ramp);
// technically not needed here / noop because GLFW owns this gamma ramp.
defer ramp.deinit(allocator);
}
}
}

50
pkg/glfw/VideoMode.zig Normal file
View File

@@ -0,0 +1,50 @@
//! Monitor video modes and related functions
//!
//! see also: glfw.Monitor.getVideoMode
const std = @import("std");
const c = @import("c.zig").c;
const VideoMode = @This();
handle: c.GLFWvidmode,
/// Returns the width of the video mode, in screen coordinates.
pub inline fn getWidth(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.width));
}
/// Returns the height of the video mode, in screen coordinates.
pub inline fn getHeight(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.height));
}
/// Returns the bit depth of the red channel of the video mode.
pub inline fn getRedBits(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.redBits));
}
/// Returns the bit depth of the green channel of the video mode.
pub inline fn getGreenBits(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.greenBits));
}
/// Returns the bit depth of the blue channel of the video mode.
pub inline fn getBlueBits(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.blueBits));
}
/// Returns the refresh rate of the video mode, in Hz.
pub inline fn getRefreshRate(self: VideoMode) u32 {
return @as(u32, @intCast(self.handle.refreshRate));
}
test "getters" {
const x = std.mem.zeroes(VideoMode);
_ = x.getWidth();
_ = x.getHeight();
_ = x.getRedBits();
_ = x.getGreenBits();
_ = x.getBlueBits();
_ = x.getRefreshRate();
}

3551
pkg/glfw/Window.zig Normal file

File diff suppressed because it is too large Load Diff

13
pkg/glfw/action.zig Normal file
View File

@@ -0,0 +1,13 @@
const c = @import("c.zig").c;
/// Key and button actions
pub const Action = enum(c_int) {
/// The key or mouse button was released.
release = c.GLFW_RELEASE,
/// The key or mouse button was pressed.
press = c.GLFW_PRESS,
/// The key was held down until it repeated.
repeat = c.GLFW_REPEAT,
};

143
pkg/glfw/allocator.zig Normal file
View File

@@ -0,0 +1,143 @@
// TODO: implement custom allocator support
// /*! @brief
// *
// * @sa @ref init_allocator
// * @sa @ref glfwInitAllocator
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// typedef struct GLFWallocator
// {
// GLFWallocatefun allocate;
// GLFWreallocatefun reallocate;
// GLFWdeallocatefun deallocate;
// void* user;
// } GLFWallocator;
// /*! @brief The function pointer type for memory allocation callbacks.
// *
// * This is the function pointer type for memory allocation callbacks. A memory
// * allocation callback function has the following signature:
// * @code
// * void* function_name(size_t size, void* user)
// * @endcode
// *
// * This function must return either a memory block at least `size` bytes long,
// * or `NULL` if allocation failed. Note that not all parts of GLFW handle allocation
// * failures gracefully yet.
// *
// * This function may be called during @ref glfwInit but before the library is
// * flagged as initialized, as well as during @ref glfwTerminate after the
// * library is no longer flagged as initialized.
// *
// * Any memory allocated by this function will be deallocated during library
// * termination or earlier.
// *
// * The size will always be greater than zero. Allocations of size zero are filtered out
// * before reaching the custom allocator.
// *
// * @param[in] size The minimum size, in bytes, of the memory block.
// * @param[in] user The user-defined pointer from the allocator.
// * @return The address of the newly allocated memory block, or `NULL` if an
// * error occurred.
// *
// * @pointer_lifetime The returned memory block must be valid at least until it
// * is deallocated.
// *
// * @reentrancy This function should not call any GLFW function.
// *
// * @thread_safety This function may be called from any thread that calls GLFW functions.
// *
// * @sa @ref init_allocator
// * @sa @ref GLFWallocator
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// typedef void* (* GLFWallocatefun)(size_t size, void* user);
// /*! @brief The function pointer type for memory reallocation callbacks.
// *
// * This is the function pointer type for memory reallocation callbacks.
// * A memory reallocation callback function has the following signature:
// * @code
// * void* function_name(void* block, size_t size, void* user)
// * @endcode
// *
// * This function must return a memory block at least `size` bytes long, or
// * `NULL` if allocation failed. Note that not all parts of GLFW handle allocation
// * failures gracefully yet.
// *
// * This function may be called during @ref glfwInit but before the library is
// * flagged as initialized, as well as during @ref glfwTerminate after the
// * library is no longer flagged as initialized.
// *
// * Any memory allocated by this function will be deallocated during library
// * termination or earlier.
// *
// * The block address will never be `NULL` and the size will always be greater than zero.
// * Reallocations of a block to size zero are converted into deallocations. Reallocations
// * of `NULL` to a non-zero size are converted into regular allocations.
// *
// * @param[in] block The address of the memory block to reallocate.
// * @param[in] size The new minimum size, in bytes, of the memory block.
// * @param[in] user The user-defined pointer from the allocator.
// * @return The address of the newly allocated or resized memory block, or
// * `NULL` if an error occurred.
// *
// * @pointer_lifetime The returned memory block must be valid at least until it
// * is deallocated.
// *
// * @reentrancy This function should not call any GLFW function.
// *
// * @thread_safety This function may be called from any thread that calls GLFW functions.
// *
// * @sa @ref init_allocator
// * @sa @ref GLFWallocator
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// typedef void* (* GLFWreallocatefun)(void* block, size_t size, void* user);
// /*! @brief The function pointer type for memory deallocation callbacks.
// *
// * This is the function pointer type for memory deallocation callbacks.
// * A memory deallocation callback function has the following signature:
// * @code
// * void function_name(void* block, void* user)
// * @endcode
// *
// * This function may deallocate the specified memory block. This memory block
// * will have been allocated with the same allocator.
// *
// * This function may be called during @ref glfwInit but before the library is
// * flagged as initialized, as well as during @ref glfwTerminate after the
// * library is no longer flagged as initialized.
// *
// * The block address will never be `NULL`. Deallocations of `NULL` are filtered out
// * before reaching the custom allocator.
// *
// * @param[in] block The address of the memory block to deallocate.
// * @param[in] user The user-defined pointer from the allocator.
// *
// * @pointer_lifetime The specified memory block will not be accessed by GLFW
// * after this function is called.
// *
// * @reentrancy This function should not call any GLFW function.
// *
// * @thread_safety This function may be called from any thread that calls GLFW functions.
// *
// * @sa @ref init_allocator
// * @sa @ref GLFWallocator
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// typedef void (* GLFWdeallocatefun)(void* block, void* user);

272
pkg/glfw/build.zig Normal file
View File

@@ -0,0 +1,272 @@
const std = @import("std");
const apple_sdk = @import("apple_sdk");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const module = b.addModule("glfw", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
const lib = try buildLib(b, module, .{
.target = target,
.optimize = optimize,
});
const test_exe: ?*std.Build.Step.Compile = if (target.query.isNative()) exe: {
const exe = b.addTest(.{
.name = "test",
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
});
if (target.result.os.tag.isDarwin()) {
try apple_sdk.addPaths(b, exe.root_module);
}
const tests_run = b.addRunArtifact(exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
// Uncomment this if we're debugging tests
b.installArtifact(exe);
break :exe exe;
} else null;
if (b.systemIntegrationOption("glfw3", .{})) {
module.linkSystemLibrary("glfw3", dynamic_link_opts);
if (test_exe) |exe| exe.linkSystemLibrary2("glfw3", dynamic_link_opts);
} else {
module.linkLibrary(lib);
b.installArtifact(lib);
if (test_exe) |exe| exe.linkLibrary(lib);
}
}
fn buildLib(
b: *std.Build,
module: *std.Build.Module,
options: anytype,
) !*std.Build.Step.Compile {
const target = options.target;
const optimize = options.optimize;
const use_x11 = b.option(
bool,
"x11",
"Build with X11. Only useful on Linux",
) orelse true;
const use_wl = b.option(
bool,
"wayland",
"Build with Wayland. Only useful on Linux",
) orelse true;
const use_opengl = b.option(
bool,
"opengl",
"Build with OpenGL; deprecated on MacOS",
) orelse false;
const use_gles = b.option(
bool,
"gles",
"Build with GLES; not supported on MacOS",
) orelse false;
const use_metal = b.option(
bool,
"metal",
"Build with Metal; only supported on MacOS",
) orelse true;
const lib = b.addStaticLibrary(.{
.name = "glfw",
.target = target,
.optimize = optimize,
});
lib.linkLibC();
const upstream = b.lazyDependency("glfw", .{}) orelse return lib;
lib.addIncludePath(upstream.path("include"));
module.addIncludePath(upstream.path("include"));
lib.installHeadersDirectory(upstream.path("include/GLFW"), "GLFW", .{});
switch (target.result.os.tag) {
.windows => {
lib.linkSystemLibrary("gdi32");
lib.linkSystemLibrary("user32");
lib.linkSystemLibrary("shell32");
if (use_opengl) {
lib.linkSystemLibrary("opengl32");
}
if (use_gles) {
lib.linkSystemLibrary("GLESv3");
}
const flags = [_][]const u8{"-D_GLFW_WIN32"};
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &base_sources,
.flags = &flags,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &windows_sources,
.flags = &flags,
});
},
.macos => {
try apple_sdk.addPaths(b, lib.root_module);
try apple_sdk.addPaths(b, module);
// Transitive dependencies, explicit linkage of these works around
// ziglang/zig#17130
lib.linkFramework("CFNetwork");
lib.linkFramework("ApplicationServices");
lib.linkFramework("ColorSync");
lib.linkFramework("CoreText");
lib.linkFramework("ImageIO");
// Direct dependencies
lib.linkSystemLibrary("objc");
lib.linkFramework("IOKit");
lib.linkFramework("CoreFoundation");
lib.linkFramework("AppKit");
lib.linkFramework("CoreServices");
lib.linkFramework("CoreGraphics");
lib.linkFramework("Foundation");
if (use_metal) {
lib.linkFramework("Metal");
}
if (use_opengl) {
lib.linkFramework("OpenGL");
}
const flags = [_][]const u8{"-D_GLFW_COCOA"};
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &base_sources,
.flags = &flags,
});
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = &macos_sources,
.flags = &flags,
});
},
// everything that isn't windows or mac is linux :P
else => {
var sources = std.BoundedArray([]const u8, 64).init(0) catch unreachable;
var flags = std.BoundedArray([]const u8, 16).init(0) catch unreachable;
sources.appendSlice(&base_sources) catch unreachable;
sources.appendSlice(&linux_sources) catch unreachable;
if (use_x11) {
lib.linkSystemLibrary2("X11", dynamic_link_opts);
lib.linkSystemLibrary2("xkbcommon", dynamic_link_opts);
sources.appendSlice(&linux_x11_sources) catch unreachable;
flags.append("-D_GLFW_X11") catch unreachable;
}
if (use_wl) {
lib.linkSystemLibrary2("wayland-client", dynamic_link_opts);
lib.root_module.addCMacro("WL_MARSHAL_FLAG_DESTROY", "1");
lib.addIncludePath(b.path("wayland-headers"));
sources.appendSlice(&linux_wl_sources) catch unreachable;
flags.append("-D_GLFW_WAYLAND") catch unreachable;
flags.append("-Wno-implicit-function-declaration") catch unreachable;
}
lib.addCSourceFiles(.{
.root = upstream.path(""),
.files = sources.slice(),
.flags = flags.slice(),
});
},
}
return lib;
}
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
const base_sources = [_][]const u8{
"src/context.c",
"src/egl_context.c",
"src/init.c",
"src/input.c",
"src/monitor.c",
"src/null_init.c",
"src/null_joystick.c",
"src/null_monitor.c",
"src/null_window.c",
"src/osmesa_context.c",
"src/platform.c",
"src/vulkan.c",
"src/window.c",
};
const linux_sources = [_][]const u8{
"src/linux_joystick.c",
"src/posix_module.c",
"src/posix_poll.c",
"src/posix_thread.c",
"src/posix_time.c",
"src/xkb_unicode.c",
};
const linux_wl_sources = [_][]const u8{
"src/wl_init.c",
"src/wl_monitor.c",
"src/wl_window.c",
};
const linux_x11_sources = [_][]const u8{
"src/glx_context.c",
"src/x11_init.c",
"src/x11_monitor.c",
"src/x11_window.c",
};
const windows_sources = [_][]const u8{
"src/wgl_context.c",
"src/win32_init.c",
"src/win32_joystick.c",
"src/win32_module.c",
"src/win32_monitor.c",
"src/win32_thread.c",
"src/win32_time.c",
"src/win32_window.c",
};
const macos_sources = [_][]const u8{
// C sources
"src/cocoa_time.c",
"src/posix_module.c",
"src/posix_thread.c",
// ObjC sources
"src/cocoa_init.m",
"src/cocoa_joystick.m",
"src/cocoa_monitor.m",
"src/cocoa_window.m",
"src/nsgl_context.m",
};

15
pkg/glfw/build.zig.zon Normal file
View File

@@ -0,0 +1,15 @@
.{
.name = .glfw,
.version = "3.4.0",
.fingerprint = 0x3bbe0a5c667e2c62,
.paths = .{""},
.dependencies = .{
.glfw = .{
.url = "https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip",
.hash = "N-V-__8AADTkRwBjUvVwTLOnV96QhN0J5Nyg7YzvnISe-Eax",
.lazy = true,
},
.apple_sdk = .{ .path = "../apple-sdk" },
},
}

6
pkg/glfw/c.zig Normal file
View File

@@ -0,0 +1,6 @@
pub const c = @cImport({
// Must be uncommented for vulkan.zig to work
// @cDefine("GLFW_INCLUDE_VULKAN", "1");
@cDefine("GLFW_INCLUDE_NONE", "1");
@cInclude("GLFW/glfw3.h");
});

71
pkg/glfw/clipboard.zig Normal file
View File

@@ -0,0 +1,71 @@
const std = @import("std");
const c = @import("c.zig").c;
const internal_debug = @import("internal_debug.zig");
/// Sets the clipboard to the specified string.
///
/// This function sets the system clipboard to the specified, UTF-8 encoded string.
///
/// @param[in] string A UTF-8 encoded string.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @pointer_lifetime The specified string is copied before this function returns.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: clipboard, glfwGetClipboardString
pub inline fn setClipboardString(value: [*:0]const u8) void {
internal_debug.assertInitialized();
c.glfwSetClipboardString(null, value);
}
/// Returns the contents of the clipboard as a string.
///
/// This function returns the contents of the system clipboard, if it contains or is convertible to
/// a UTF-8 encoded string. If the clipboard is empty or if its contents cannot be converted,
/// glfw.ErrorCode.FormatUnavailable is returned.
///
/// @return The contents of the clipboard as a UTF-8 encoded string.
///
/// Possible errors include glfw.ErrorCode.FormatUnavailable and glfw.ErrorCode.PlatformError.
/// null is returned in the event of an error.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the next call to glfw.getClipboardString or glfw.setClipboardString
/// or until the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: clipboard, glfwSetClipboardString
pub inline fn getClipboardString() ?[:0]const u8 {
internal_debug.assertInitialized();
if (c.glfwGetClipboardString(null)) |c_str| return std.mem.span(@as([*:0]const u8, @ptrCast(c_str)));
return null;
}
test "setClipboardString" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
glfw.setClipboardString("hello mach");
}
test "getClipboardString" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
_ = glfw.getClipboardString();
}

338
pkg/glfw/errors.zig Normal file
View File

@@ -0,0 +1,338 @@
//! Errors
const testing = @import("std").testing;
const mem = @import("std").mem;
const c = @import("c.zig").c;
/// Errors that GLFW can produce.
pub const ErrorCode = error{
/// GLFW has not been initialized.
///
/// This occurs if a GLFW function was called that must not be called unless the library is
/// initialized.
NotInitialized,
/// No context is current for this thread.
///
/// This occurs if a GLFW function was called that needs and operates on the current OpenGL or
/// OpenGL ES context but no context is current on the calling thread. One such function is
/// glfw.SwapInterval.
NoCurrentContext,
/// One of the arguments to the function was an invalid enum value.
///
/// One of the arguments to the function was an invalid enum value, for example requesting
/// glfw.red_bits with glfw.getWindowAttrib.
InvalidEnum,
/// One of the arguments to the function was an invalid value.
///
/// One of the arguments to the function was an invalid value, for example requesting a
/// non-existent OpenGL or OpenGL ES version like 2.7.
///
/// Requesting a valid but unavailable OpenGL or OpenGL ES version will instead result in a
/// glfw.ErrorCode.VersionUnavailable error.
InvalidValue,
/// A memory allocation failed.
OutOfMemory,
/// GLFW could not find support for the requested API on the system.
///
/// The installed graphics driver does not support the requested API, or does not support it
/// via the chosen context creation API. Below are a few examples.
///
/// Some pre-installed Windows graphics drivers do not support OpenGL. AMD only supports
/// OpenGL ES via EGL, while Nvidia and Intel only support it via a WGL or GLX extension. macOS
/// does not provide OpenGL ES at all. The Mesa EGL, OpenGL and OpenGL ES libraries do not
/// interface with the Nvidia binary driver. Older graphics drivers do not support Vulkan.
APIUnavailable,
/// The requested OpenGL or OpenGL ES version (including any requested context or framebuffer
/// hints) is not available on this machine.
///
/// The machine does not support your requirements. If your application is sufficiently
/// flexible, downgrade your requirements and try again. Otherwise, inform the user that their
/// machine does not match your requirements.
///
/// Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 comes out
/// before the 4.x series gets that far, also fail with this error and not glfw.ErrorCode.InvalidValue,
/// because GLFW cannot know what future versions will exist.
VersionUnavailable,
/// A platform-specific error occurred that does not match any of the more specific categories.
///
/// A bug or configuration error in GLFW, the underlying operating system or its drivers, or a
/// lack of required resources. Report the issue to our [issue tracker](https://github.com/glfw/glfw/issues).
PlatformError,
/// The requested format is not supported or available.
///
/// If emitted during window creation, the requested pixel format is not supported.
///
/// If emitted when querying the clipboard, the contents of the clipboard could not be
/// converted to the requested format.
///
/// If emitted during window creation, one or more hard constraints did not match any of the
/// available pixel formats. If your application is sufficiently flexible, downgrade your
/// requirements and try again. Otherwise, inform the user that their machine does not match
/// your requirements.
///
/// If emitted when querying the clipboard, ignore the error or report it to the user, as
/// appropriate.
FormatUnavailable,
/// The specified window does not have an OpenGL or OpenGL ES context.
///
/// A window that does not have an OpenGL or OpenGL ES context was passed to a function that
/// requires it to have one.
NoWindowContext,
/// The specified cursor shape is not available.
///
/// The specified standard cursor shape is not available, either because the
/// current platform cursor theme does not provide it or because it is not
/// available on the platform.
///
/// analysis: Platform or system settings limitation. Pick another standard cursor shape or
/// create a custom cursor.
CursorUnavailable,
/// The requested feature is not provided by the platform.
///
/// The requested feature is not provided by the platform, so GLFW is unable to
/// implement it. The documentation for each function notes if it could emit
/// this error.
///
/// analysis: Platform or platform version limitation. The error can be ignored
/// unless the feature is critical to the application.
///
/// A function call that emits this error has no effect other than the error and
/// updating any existing out parameters.
///
FeatureUnavailable,
/// The requested feature is not implemented for the platform.
///
/// The requested feature has not yet been implemented in GLFW for this platform.
///
/// analysis: An incomplete implementation of GLFW for this platform, hopefully
/// fixed in a future release. The error can be ignored unless the feature is
/// critical to the application.
///
/// A function call that emits this error has no effect other than the error and
/// updating any existing out parameters.
///
FeatureUnimplemented,
/// Platform unavailable or no matching platform was found.
///
/// If emitted during initialization, no matching platform was found. If glfw.InitHint.platform
/// is set to `.any_platform`, GLFW could not detect any of the platforms supported by this
/// library binary, except for the Null platform. If set to a specific platform, it is either
/// not supported by this library binary or GLFW was not able to detect it.
///
/// If emitted by a native access function, GLFW was initialized for a different platform
/// than the function is for.
///
/// analysis: Failure to detect any platform usually only happens on non-macOS Unix
/// systems, either when no window system is running or the program was run from
/// a terminal that does not have the necessary environment variables. Fall back to
/// a different platform if possible or notify the user that no usable platform was
/// detected.
///
/// Failure to detect a specific platform may have the same cause as above or be because
/// support for that platform was not compiled in. Call glfw.platformSupported to
/// check whether a specific platform is supported by a library binary.
///
PlatformUnavailable,
};
/// An error produced by GLFW and the description associated with it.
pub const Error = struct {
error_code: ErrorCode,
description: [:0]const u8,
};
fn convertError(e: c_int) ErrorCode!void {
return switch (e) {
c.GLFW_NO_ERROR => {},
c.GLFW_NOT_INITIALIZED => ErrorCode.NotInitialized,
c.GLFW_NO_CURRENT_CONTEXT => ErrorCode.NoCurrentContext,
c.GLFW_INVALID_ENUM => ErrorCode.InvalidEnum,
c.GLFW_INVALID_VALUE => ErrorCode.InvalidValue,
c.GLFW_OUT_OF_MEMORY => ErrorCode.OutOfMemory,
c.GLFW_API_UNAVAILABLE => ErrorCode.APIUnavailable,
c.GLFW_VERSION_UNAVAILABLE => ErrorCode.VersionUnavailable,
c.GLFW_PLATFORM_ERROR => ErrorCode.PlatformError,
c.GLFW_FORMAT_UNAVAILABLE => ErrorCode.FormatUnavailable,
c.GLFW_NO_WINDOW_CONTEXT => ErrorCode.NoWindowContext,
c.GLFW_CURSOR_UNAVAILABLE => ErrorCode.CursorUnavailable,
c.GLFW_FEATURE_UNAVAILABLE => ErrorCode.FeatureUnavailable,
c.GLFW_FEATURE_UNIMPLEMENTED => ErrorCode.FeatureUnimplemented,
c.GLFW_PLATFORM_UNAVAILABLE => ErrorCode.PlatformUnavailable,
else => unreachable,
};
}
/// Clears the last error and the error description pointer for the calling thread. Does nothing if
/// no error has occurred since the last call.
///
/// @remark This function may be called before @ref glfwInit.
///
/// @thread_safety This function may be called from any thread.
pub inline fn clearError() void {
_ = c.glfwGetError(null);
}
/// Returns and clears the last error for the calling thread.
///
/// This function returns and clears the error code of the last error that occurred on the calling
/// thread, along with a UTF-8 encoded human-readable description of it. If no error has occurred
/// since the last call, it returns GLFW_NO_ERROR (zero) and the description pointer is set to
/// `NULL`.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is guaranteed to be valid only until the next error occurs or the library is
/// terminated.
///
/// @remark This function may be called before @ref glfwInit.
///
/// @thread_safety This function may be called from any thread.
pub inline fn getError() ?Error {
var desc: [*c]const u8 = null;
convertError(c.glfwGetError(&desc)) catch |error_code| {
return .{
.error_code = error_code,
.description = mem.sliceTo(desc, 0),
};
};
return null;
}
pub inline fn mustGetError() Error {
return getError() orelse {
@panic("glfw: mustGetError called but no error is present");
};
}
/// Returns and clears the last error for the calling thread.
///
/// This function returns and clears the error code of the last error that occurred on the calling
/// thread. If no error has occurred since the last call, it returns GLFW_NO_ERROR (zero).
///
/// @return The last error code for the calling thread, or @ref GLFW_NO_ERROR (zero).
///
/// @remark This function may be called before @ref glfwInit.
///
/// @thread_safety This function may be called from any thread.
pub inline fn getErrorCode() ErrorCode!void {
return convertError(c.glfwGetError(null));
}
/// Returns and clears the last error code for the calling thread. If no error is present, this
/// function panics.
pub inline fn mustGetErrorCode() ErrorCode {
try getErrorCode();
@panic("glfw: mustGetErrorCode called but no error is present");
}
/// Returns and clears the last error description for the calling thread.
///
/// This function returns a UTF-8 encoded human-readable description of the last error that occured
/// on the calling thread. If no error has occurred since the last call, it returns null.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is guaranteed to be valid only until the next error occurs or the library is
/// terminated.
///
/// @remark This function may be called before @ref glfwInit.
///
/// @thread_safety This function may be called from any thread.
pub inline fn getErrorString() ?[:0]const u8 {
var desc: [*c]const u8 = null;
const error_code = c.glfwGetError(&desc);
if (error_code != c.GLFW_NO_ERROR) {
return mem.sliceTo(desc, 0);
}
return null;
}
/// Returns and clears the last error description for the calling thread. If no error is present,
/// this function panics.
pub inline fn mustGetErrorString() [:0]const u8 {
return getErrorString() orelse {
@panic("glfw: mustGetErrorString called but no error is present");
};
}
/// Sets the error callback.
///
/// This function sets the error callback, which is called with an error code
/// and a human-readable description each time a GLFW error occurs.
///
/// The error code is set before the callback is called. Calling @ref
/// glfwGetError from the error callback will return the same value as the error
/// code argument.
///
/// The error callback is called on the thread where the error occurred. If you
/// are using GLFW from multiple threads, your error callback needs to be
/// written accordingly.
///
/// Because the description string may have been generated specifically for that
/// error, it is not guaranteed to be valid after the callback has returned. If
/// you wish to use it after the callback returns, you need to make a copy.
///
/// Once set, the error callback remains set even after the library has been
/// terminated.
///
/// @param[in] callback The new callback, or `NULL` to remove the currently set
/// callback.
///
/// @callback_param `error_code` An error code. Future releases may add more error codes.
/// @callback_param `description` A UTF-8 encoded string describing the error.
///
/// @errors None.
///
/// @remark This function may be called before @ref glfwInit.
///
/// @thread_safety This function must only be called from the main thread.
pub fn setErrorCallback(comptime callback: ?fn (error_code: ErrorCode, description: [:0]const u8) void) void {
if (callback) |user_callback| {
const CWrapper = struct {
pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.C) void {
convertError(err_int) catch |error_code| {
user_callback(error_code, mem.sliceTo(c_description, 0));
};
}
};
_ = c.glfwSetErrorCallback(CWrapper.errorCallbackWrapper);
return;
}
_ = c.glfwSetErrorCallback(null);
}
test "set error callback" {
const TestStruct = struct {
pub fn callback(_: ErrorCode, _: [:0]const u8) void {}
};
setErrorCallback(TestStruct.callback);
}
test "error string" {
try testing.expect(getErrorString() == null);
}
test "error code" {
try getErrorCode();
}
test "error code and string" {
try testing.expect(getError() == null);
}
test "clear error" {
clearError();
}

16
pkg/glfw/gamepad_axis.zig Normal file
View File

@@ -0,0 +1,16 @@
const c = @import("c.zig").c;
/// Gamepad axes.
///
/// See glfw.getGamepadState for how these are used.
pub const GamepadAxis = enum(c_int) {
left_x = c.GLFW_GAMEPAD_AXIS_LEFT_X,
left_y = c.GLFW_GAMEPAD_AXIS_LEFT_Y,
right_x = c.GLFW_GAMEPAD_AXIS_RIGHT_X,
right_y = c.GLFW_GAMEPAD_AXIS_RIGHT_Y,
left_trigger = c.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER,
right_trigger = c.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER,
};
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const last = GamepadAxis.right_trigger;

View File

@@ -0,0 +1,37 @@
const c = @import("c.zig").c;
/// Gamepad buttons.
///
/// See glfw.getGamepadState for how these are used.
pub const GamepadButton = enum(c_int) {
a = c.GLFW_GAMEPAD_BUTTON_A,
b = c.GLFW_GAMEPAD_BUTTON_B,
x = c.GLFW_GAMEPAD_BUTTON_X,
y = c.GLFW_GAMEPAD_BUTTON_Y,
left_bumper = c.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER,
right_bumper = c.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER,
back = c.GLFW_GAMEPAD_BUTTON_BACK,
start = c.GLFW_GAMEPAD_BUTTON_START,
guide = c.GLFW_GAMEPAD_BUTTON_GUIDE,
left_thumb = c.GLFW_GAMEPAD_BUTTON_LEFT_THUMB,
right_thumb = c.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB,
dpad_up = c.GLFW_GAMEPAD_BUTTON_DPAD_UP,
dpad_right = c.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT,
dpad_down = c.GLFW_GAMEPAD_BUTTON_DPAD_DOWN,
dpad_left = c.GLFW_GAMEPAD_BUTTON_DPAD_LEFT,
};
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const last = GamepadButton.dpad_left;
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const cross = GamepadButton.a;
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const circle = GamepadButton.b;
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const square = GamepadButton.x;
/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden.
pub const triangle = GamepadButton.y;

100
pkg/glfw/hat.zig Normal file
View File

@@ -0,0 +1,100 @@
const c = @import("c.zig").c;
// must be in sync with GLFW C constants in hat state group, search for "@defgroup hat_state Joystick hat states"
/// A bitmask of all Joystick hat states
///
/// See glfw.Joystick.getHats for how these are used.
pub const Hat = packed struct(u8) {
up: bool = false,
right: bool = false,
down: bool = false,
left: bool = false,
_padding: u4 = 0,
pub inline fn centered(self: Hat) bool {
return self.up == false and self.right == false and self.down == false and self.left == false;
}
inline fn verifyIntType(comptime IntType: type) void {
comptime {
switch (@import("shims.zig").typeInfo(IntType)) {
.int => {},
else => @compileError("Int was not of int type"),
}
}
}
pub inline fn toInt(self: Hat, comptime IntType: type) IntType {
verifyIntType(IntType);
return @as(IntType, @intCast(@as(u8, @bitCast(self))));
}
pub inline fn fromInt(flags: anytype) Hat {
verifyIntType(@TypeOf(flags));
return @as(Hat, @bitCast(@as(u8, @intCast(flags))));
}
};
/// Holds all GLFW hat values in their raw form.
pub const RawHat = struct {
pub const centered = c.GLFW_HAT_CENTERED;
pub const up = c.GLFW_HAT_UP;
pub const right = c.GLFW_HAT_RIGHT;
pub const down = c.GLFW_HAT_DOWN;
pub const left = c.GLFW_HAT_LEFT;
pub const right_up = right | up;
pub const right_down = right | down;
pub const left_up = left | up;
pub const left_down = left | down;
};
test "from int, single" {
const std = @import("std");
try std.testing.expectEqual(Hat{
.up = true,
.right = false,
.down = false,
.left = false,
._padding = 0,
}, Hat.fromInt(RawHat.up));
}
test "from int, multi" {
const std = @import("std");
try std.testing.expectEqual(Hat{
.up = true,
.right = false,
.down = true,
.left = true,
._padding = 0,
}, Hat.fromInt(RawHat.up | RawHat.down | RawHat.left));
}
test "to int, single" {
const std = @import("std");
var v = Hat{
.up = true,
.right = false,
.down = false,
.left = false,
._padding = 0,
};
try std.testing.expectEqual(v.toInt(c_int), RawHat.up);
}
test "to int, multi" {
const std = @import("std");
var v = Hat{
.up = true,
.right = false,
.down = true,
.left = true,
._padding = 0,
};
try std.testing.expectEqual(v.toInt(c_int), RawHat.up | RawHat.down | RawHat.left);
}

View File

@@ -0,0 +1,14 @@
const std = @import("std");
const builtin = @import("builtin");
const is_debug = builtin.mode == .Debug;
var glfw_initialized = if (is_debug) false else @as(void, {});
pub inline fn toggleInitialized() void {
if (is_debug) glfw_initialized = !glfw_initialized;
}
pub inline fn assertInitialized() void {
if (is_debug) std.debug.assert(glfw_initialized);
}
pub inline fn assumeInitialized() void {
if (is_debug) glfw_initialized = true;
}

266
pkg/glfw/key.zig Normal file
View File

@@ -0,0 +1,266 @@
//! Keyboard key IDs.
//!
//! See glfw.setKeyCallback for how these are used.
//!
//! These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), but re-arranged to
//! map to 7-bit ASCII for printable keys (function keys are put in the 256+ range).
//!
//! The naming of the key codes follow these rules:
//!
//! - The US keyboard layout is used
//! - Names of printable alphanumeric characters are used (e.g. "a", "r", "three", etc.)
//! - For non-alphanumeric characters, Unicode:ish names are used (e.g. "comma", "left_bracket",
//! etc.). Note that some names do not correspond to the Unicode standard (usually for brevity)
//! - Keys that lack a clear US mapping are named "world_x"
//! - For non-printable keys, custom names are used (e.g. "F4", "backspace", etc.)
const std = @import("std");
const cc = @import("c.zig").c;
const internal_debug = @import("internal_debug.zig");
/// enum containing all glfw keys
pub const Key = enum(c_int) {
/// The unknown key
unknown = cc.GLFW_KEY_UNKNOWN,
/// Printable keys
space = cc.GLFW_KEY_SPACE,
apostrophe = cc.GLFW_KEY_APOSTROPHE,
comma = cc.GLFW_KEY_COMMA,
minus = cc.GLFW_KEY_MINUS,
period = cc.GLFW_KEY_PERIOD,
slash = cc.GLFW_KEY_SLASH,
zero = cc.GLFW_KEY_0,
one = cc.GLFW_KEY_1,
two = cc.GLFW_KEY_2,
three = cc.GLFW_KEY_3,
four = cc.GLFW_KEY_4,
five = cc.GLFW_KEY_5,
six = cc.GLFW_KEY_6,
seven = cc.GLFW_KEY_7,
eight = cc.GLFW_KEY_8,
nine = cc.GLFW_KEY_9,
semicolon = cc.GLFW_KEY_SEMICOLON,
equal = cc.GLFW_KEY_EQUAL,
a = cc.GLFW_KEY_A,
b = cc.GLFW_KEY_B,
c = cc.GLFW_KEY_C,
d = cc.GLFW_KEY_D,
e = cc.GLFW_KEY_E,
f = cc.GLFW_KEY_F,
g = cc.GLFW_KEY_G,
h = cc.GLFW_KEY_H,
i = cc.GLFW_KEY_I,
j = cc.GLFW_KEY_J,
k = cc.GLFW_KEY_K,
l = cc.GLFW_KEY_L,
m = cc.GLFW_KEY_M,
n = cc.GLFW_KEY_N,
o = cc.GLFW_KEY_O,
p = cc.GLFW_KEY_P,
q = cc.GLFW_KEY_Q,
r = cc.GLFW_KEY_R,
s = cc.GLFW_KEY_S,
t = cc.GLFW_KEY_T,
u = cc.GLFW_KEY_U,
v = cc.GLFW_KEY_V,
w = cc.GLFW_KEY_W,
x = cc.GLFW_KEY_X,
y = cc.GLFW_KEY_Y,
z = cc.GLFW_KEY_Z,
left_bracket = cc.GLFW_KEY_LEFT_BRACKET,
backslash = cc.GLFW_KEY_BACKSLASH,
right_bracket = cc.GLFW_KEY_RIGHT_BRACKET,
grave_accent = cc.GLFW_KEY_GRAVE_ACCENT,
world_1 = cc.GLFW_KEY_WORLD_1, // non-US #1
world_2 = cc.GLFW_KEY_WORLD_2, // non-US #2
// Function keys
escape = cc.GLFW_KEY_ESCAPE,
enter = cc.GLFW_KEY_ENTER,
tab = cc.GLFW_KEY_TAB,
backspace = cc.GLFW_KEY_BACKSPACE,
insert = cc.GLFW_KEY_INSERT,
delete = cc.GLFW_KEY_DELETE,
right = cc.GLFW_KEY_RIGHT,
left = cc.GLFW_KEY_LEFT,
down = cc.GLFW_KEY_DOWN,
up = cc.GLFW_KEY_UP,
page_up = cc.GLFW_KEY_PAGE_UP,
page_down = cc.GLFW_KEY_PAGE_DOWN,
home = cc.GLFW_KEY_HOME,
end = cc.GLFW_KEY_END,
caps_lock = cc.GLFW_KEY_CAPS_LOCK,
scroll_lock = cc.GLFW_KEY_SCROLL_LOCK,
num_lock = cc.GLFW_KEY_NUM_LOCK,
print_screen = cc.GLFW_KEY_PRINT_SCREEN,
pause = cc.GLFW_KEY_PAUSE,
F1 = cc.GLFW_KEY_F1,
F2 = cc.GLFW_KEY_F2,
F3 = cc.GLFW_KEY_F3,
F4 = cc.GLFW_KEY_F4,
F5 = cc.GLFW_KEY_F5,
F6 = cc.GLFW_KEY_F6,
F7 = cc.GLFW_KEY_F7,
F8 = cc.GLFW_KEY_F8,
F9 = cc.GLFW_KEY_F9,
F10 = cc.GLFW_KEY_F10,
F11 = cc.GLFW_KEY_F11,
F12 = cc.GLFW_KEY_F12,
F13 = cc.GLFW_KEY_F13,
F14 = cc.GLFW_KEY_F14,
F15 = cc.GLFW_KEY_F15,
F16 = cc.GLFW_KEY_F16,
F17 = cc.GLFW_KEY_F17,
F18 = cc.GLFW_KEY_F18,
F19 = cc.GLFW_KEY_F19,
F20 = cc.GLFW_KEY_F20,
F21 = cc.GLFW_KEY_F21,
F22 = cc.GLFW_KEY_F22,
F23 = cc.GLFW_KEY_F23,
F24 = cc.GLFW_KEY_F24,
F25 = cc.GLFW_KEY_F25,
kp_0 = cc.GLFW_KEY_KP_0,
kp_1 = cc.GLFW_KEY_KP_1,
kp_2 = cc.GLFW_KEY_KP_2,
kp_3 = cc.GLFW_KEY_KP_3,
kp_4 = cc.GLFW_KEY_KP_4,
kp_5 = cc.GLFW_KEY_KP_5,
kp_6 = cc.GLFW_KEY_KP_6,
kp_7 = cc.GLFW_KEY_KP_7,
kp_8 = cc.GLFW_KEY_KP_8,
kp_9 = cc.GLFW_KEY_KP_9,
kp_decimal = cc.GLFW_KEY_KP_DECIMAL,
kp_divide = cc.GLFW_KEY_KP_DIVIDE,
kp_multiply = cc.GLFW_KEY_KP_MULTIPLY,
kp_subtract = cc.GLFW_KEY_KP_SUBTRACT,
kp_add = cc.GLFW_KEY_KP_ADD,
kp_enter = cc.GLFW_KEY_KP_ENTER,
kp_equal = cc.GLFW_KEY_KP_EQUAL,
left_shift = cc.GLFW_KEY_LEFT_SHIFT,
left_control = cc.GLFW_KEY_LEFT_CONTROL,
left_alt = cc.GLFW_KEY_LEFT_ALT,
left_super = cc.GLFW_KEY_LEFT_SUPER,
right_shift = cc.GLFW_KEY_RIGHT_SHIFT,
right_control = cc.GLFW_KEY_RIGHT_CONTROL,
right_alt = cc.GLFW_KEY_RIGHT_ALT,
right_super = cc.GLFW_KEY_RIGHT_SUPER,
menu = cc.GLFW_KEY_MENU,
pub inline fn last() Key {
return @as(Key, @enumFromInt(cc.GLFW_KEY_LAST));
}
/// Returns the layout-specific name of the specified printable key.
///
/// This function returns the name of the specified printable key, encoded as UTF-8. This is
/// typically the character that key would produce without any modifier keys, intended for
/// displaying key bindings to the user. For dead keys, it is typically the diacritic it would add
/// to a character.
///
/// __Do not use this function__ for text input (see input_char). You will break text input for many
/// languages even if it happens to work for yours.
///
/// If the key is `glfw.key.unknown`, the scancode is used to identify the key, otherwise the
/// scancode is ignored. If you specify a non-printable key, or `glfw.key.unknown` and a scancode
/// that maps to a non-printable key, this function returns null but does not emit an error.
///
/// This behavior allows you to always pass in the arguments in the key callback (see input_key)
/// without modification.
///
/// The printable keys are:
///
/// - `glfw.Key.apostrophe`
/// - `glfw.Key.comma`
/// - `glfw.Key.minus`
/// - `glfw.Key.period`
/// - `glfw.Key.slash`
/// - `glfw.Key.semicolon`
/// - `glfw.Key.equal`
/// - `glfw.Key.left_bracket`
/// - `glfw.Key.right_bracket`
/// - `glfw.Key.backslash`
/// - `glfw.Key.world_1`
/// - `glfw.Key.world_2`
/// - `glfw.Key.0` to `glfw.key.9`
/// - `glfw.Key.a` to `glfw.key.z`
/// - `glfw.Key.kp_0` to `glfw.key.kp_9`
/// - `glfw.Key.kp_decimal`
/// - `glfw.Key.kp_divide`
/// - `glfw.Key.kp_multiply`
/// - `glfw.Key.kp_subtract`
/// - `glfw.Key.kp_add`
/// - `glfw.Key.kp_equal`
///
/// Names for printable keys depend on keyboard layout, while names for non-printable keys are the
/// same across layouts but depend on the application language and should be localized along with
/// other user interface text.
///
/// @param[in] key The key to query, or `glfw.key.unknown`.
/// @param[in] scancode The scancode of the key to query.
/// @return The UTF-8 encoded, layout-specific name of the key, or null.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
/// Also returns null in the event of an error.
///
/// The contents of the returned string may change when a keyboard layout change event is received.
///
/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it
/// yourself. It is valid until the library is terminated.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: input_key_name
pub inline fn getName(self: Key, scancode: i32) ?[:0]const u8 {
internal_debug.assertInitialized();
const name_opt = cc.glfwGetKeyName(@intFromEnum(self), @as(c_int, @intCast(scancode)));
return if (name_opt) |name|
std.mem.span(@as([*:0]const u8, @ptrCast(name)))
else
null;
}
/// Returns the platform-specific scancode of the specified key.
///
/// This function returns the platform-specific scancode of the specified key.
///
/// If the key is `glfw.key.UNKNOWN` or does not exist on the keyboard this method will return `-1`.
///
/// @param[in] key Any named key (see keys).
/// @return The platform-specific scancode for the key.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError.
/// Additionally returns -1 in the event of an error.
///
/// @thread_safety This function may be called from any thread.
pub inline fn getScancode(self: Key) i32 {
internal_debug.assertInitialized();
return cc.glfwGetKeyScancode(@intFromEnum(self));
}
};
test "getName" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
_ = glfw.Key.a.getName(0);
}
test "getScancode" {
const glfw = @import("main.zig");
defer glfw.clearError(); // clear any error we generate
if (!glfw.init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
std.process.exit(1);
}
defer glfw.terminate();
_ = glfw.Key.a.getScancode();
}

586
pkg/glfw/main.zig Normal file
View File

@@ -0,0 +1,586 @@
const std = @import("std");
const testing = std.testing;
const c = @import("c.zig").c;
const key = @import("key.zig");
const errors = @import("errors.zig");
/// Possible value for various window hints, etc.
pub const dont_care = c.GLFW_DONT_CARE;
pub const getError = errors.getError;
pub const mustGetError = errors.mustGetError;
pub const getErrorCode = errors.getErrorCode;
pub const mustGetErrorCode = errors.mustGetErrorCode;
pub const getErrorString = errors.getErrorString;
pub const mustGetErrorString = errors.mustGetErrorString;
pub const setErrorCallback = errors.setErrorCallback;
pub const clearError = errors.clearError;
pub const ErrorCode = errors.ErrorCode;
pub const Error = errors.Error;
pub const Action = @import("action.zig").Action;
pub const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis;
pub const GamepadButton = @import("gamepad_button.zig").GamepadButton;
pub const gamepad_axis = @import("gamepad_axis.zig");
pub const gamepad_button = @import("gamepad_button.zig");
pub const GammaRamp = @import("GammaRamp.zig");
pub const Image = @import("Image.zig");
pub const Joystick = @import("Joystick.zig");
pub const Monitor = @import("Monitor.zig");
pub const mouse_button = @import("mouse_button.zig");
pub const MouseButton = mouse_button.MouseButton;
pub const version = @import("version.zig");
pub const VideoMode = @import("VideoMode.zig");
pub const Window = @import("Window.zig");
pub const Cursor = @import("Cursor.zig");
pub const Native = @import("native.zig").Native;
pub const BackendOptions = @import("native.zig").BackendOptions;
pub const Key = key.Key;
pub const setClipboardString = @import("clipboard.zig").setClipboardString;
pub const getClipboardString = @import("clipboard.zig").getClipboardString;
pub const makeContextCurrent = @import("opengl.zig").makeContextCurrent;
pub const getCurrentContext = @import("opengl.zig").getCurrentContext;
pub const swapInterval = @import("opengl.zig").swapInterval;
pub const extensionSupported = @import("opengl.zig").extensionSupported;
pub const GLProc = @import("opengl.zig").GLProc;
pub const getProcAddress = @import("opengl.zig").getProcAddress;
pub const getTime = @import("time.zig").getTime;
pub const setTime = @import("time.zig").setTime;
pub const getTimerValue = @import("time.zig").getTimerValue;
pub const getTimerFrequency = @import("time.zig").getTimerFrequency;
pub const Hat = @import("hat.zig").Hat;
pub const RawHat = @import("hat.zig").RawHat;
pub const Mods = @import("mod.zig").Mods;
pub const RawMods = @import("mod.zig").RawMods;
const internal_debug = @import("internal_debug.zig");
/// If GLFW was already initialized in your program, e.g. you are embedding Zig code into an existing
/// program that has already called glfwInit via the C API for you - then you need to tell mach/glfw
/// that it has in fact been initialized already, otherwise when you call other methods mach/glfw
/// would panic thinking glfw.init has not been called yet.
pub fn assumeInitialized() void {
internal_debug.assumeInitialized();
}
/// Initializes the GLFW library.
///
/// This function initializes the GLFW library. Before most GLFW functions can be used, GLFW must
/// be initialized, and before an application terminates GLFW should be terminated in order to free
/// any resources allocated during or after initialization.
///
/// If this function fails, it calls glfw.Terminate before returning. If it succeeds, you should
/// call glfw.Terminate before the application exits.
///
/// Additional calls to this function after successful initialization but before termination will
/// return immediately with no error.
///
/// The glfw.InitHint.platform init hint controls which platforms are considered during
/// initialization. This also depends on which platforms the library was compiled to support.
///
/// macos: This function will change the current directory of the application to the
/// `Contents/Resources` subdirectory of the application's bundle, if present. This can be disabled
/// with `glfw.InitHint.cocoa_chdir_resources`.
///
/// macos: This function will create the main menu and dock icon for the application. If GLFW finds
/// a `MainMenu.nib` it is loaded and assumed to contain a menu bar. Otherwise a minimal menu bar is
/// created manually with common commands like Hide, Quit and About. The About entry opens a minimal
/// about dialog with information from the application's bundle. The menu bar and dock icon can be
/// disabled entirely with `glfw.InitHint.cocoa_menubar`.
///
/// x11: This function will set the `LC_CTYPE` category of the application locale according to the
/// current environment if that category is still "C". This is because the "C" locale breaks
/// Unicode text input.
///
/// Possible errors include glfw.ErrorCode.PlatformUnavailable, glfw.ErrorCode.PlatformError.
/// Returns a bool indicating success.
///
/// @thread_safety This function must only be called from the main thread.
pub inline fn init(hints: InitHints) bool {
internal_debug.toggleInitialized();
internal_debug.assertInitialized();
errdefer {
internal_debug.assertInitialized();
internal_debug.toggleInitialized();
}
inline for (comptime std.meta.fieldNames(InitHints)) |field_name| {
const init_hint = @field(InitHint, field_name);
const init_value = @field(hints, field_name);
if (@TypeOf(init_value) == PlatformType) {
initHint(init_hint, @intFromEnum(init_value));
} else {
initHint(init_hint, init_value);
}
}
return c.glfwInit() == c.GLFW_TRUE;
}
// TODO: implement custom allocator support
//
// /*! @brief Sets the init allocator to the desired value.
// *
// * To use the default allocator, call this function with a `NULL` argument.
// *
// * If you specify an allocator struct, every member must be a valid function
// * pointer. If any member is `NULL`, this function emits @ref
// * GLFW_INVALID_VALUE and the init allocator is unchanged.
// *
// * @param[in] allocator The allocator to use at the next initialization, or
// * `NULL` to use the default one.
// *
// * @errors Possible errors include @ref GLFW_INVALID_VALUE.
// *
// * @pointer_lifetime The specified allocator is copied before this function
// * returns.
// *
// * @thread_safety This function must only be called from the main thread.
// *
// * @sa @ref init_allocator
// * @sa @ref glfwInit
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// GLFWAPI void glfwInitAllocator(const GLFWallocator* allocator);
/// Terminates the GLFW library.
///
/// This function destroys all remaining windows and cursors, restores any modified gamma ramps
/// and frees any other allocated resources. Once this function is called, you must again call
/// glfw.init successfully before you will be able to use most GLFW functions.
///
/// If GLFW has been successfully initialized, this function should be called before the
/// application exits. If initialization fails, there is no need to call this function, as it is
/// called by glfw.init before it returns failure.
///
/// This function has no effect if GLFW is not initialized.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// warning: The contexts of any remaining windows must not be current on any other thread when
/// this function is called.
///
/// reentrancy: This function must not be called from a callback.
///
/// thread_safety: This function must only be called from the main thread.
pub inline fn terminate() void {
internal_debug.assertInitialized();
internal_debug.toggleInitialized();
c.glfwTerminate();
}
/// Initialization hints for passing into glfw.init
pub const InitHints = struct {
/// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier
/// versions of GLFW that did not have glfwGetJoystickHats.
joystick_hat_buttons: bool = true,
/// macOS specific init hint. Ignored on other platforms.
///
/// Specifies whether to set the current directory to the application to the Contents/Resources
/// subdirectory of the application's bundle, if present.
cocoa_chdir_resources: bool = true,
/// macOS specific init hint. Ignored on other platforms.
///
/// specifies whether to create a basic menu bar, either from a nib or manually, when the first
/// window is created, which is when AppKit is initialized.
cocoa_menubar: bool = true,
/// Platform selection init hint.
///
/// Possible values are `PlatformType` enums.
platform: PlatformType = .any,
};
/// Initialization hints for passing into glfw.initHint
const InitHint = enum(c_int) {
/// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier
/// versions of GLFW that did not have glfwGetJoystickHats.
///
/// Possible values are `true` and `false`.
joystick_hat_buttons = c.GLFW_JOYSTICK_HAT_BUTTONS,
/// ANGLE rendering backend init hint.
///
/// Possible values are `AnglePlatformType` enums.
angle_platform_type = c.GLFW_ANGLE_PLATFORM_TYPE,
/// Platform selection init hint.
///
/// Possible values are `PlatformType` enums.
platform = c.GLFW_PLATFORM,
/// macOS specific init hint. Ignored on other platforms.
///
/// Specifies whether to set the current directory to the application to the Contents/Resources
/// subdirectory of the application's bundle, if present.
///
/// Possible values are `true` and `false`.
cocoa_chdir_resources = c.GLFW_COCOA_CHDIR_RESOURCES,
/// macOS specific init hint. Ignored on other platforms.
///
/// specifies whether to create a basic menu bar, either from a nib or manually, when the first
/// window is created, which is when AppKit is initialized.
///
/// Possible values are `true` and `false`.
cocoa_menubar = c.GLFW_COCOA_MENUBAR,
/// X11 specific init hint.
x11_xcb_vulkan_surface = c.GLFW_X11_XCB_VULKAN_SURFACE,
/// Wayland specific init hint.
///
/// Possible values are `WaylandLibdecorInitHint` enums.
wayland_libdecor = c.GLFW_WAYLAND_LIBDECOR,
};
/// Angle platform type hints for glfw.InitHint.angle_platform_type
pub const AnglePlatformType = enum(c_int) {
none = c.GLFW_ANGLE_PLATFORM_TYPE_NONE,
opengl = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGL,
opengles = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGLES,
d3d9 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D9,
d3d11 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D11,
vulkan = c.GLFW_ANGLE_PLATFORM_TYPE_VULKAN,
metal = c.GLFW_ANGLE_PLATFORM_TYPE_METAL,
};
/// Wayland libdecor hints for glfw.InitHint.wayland_libdecor
///
/// libdecor is important for GNOME, since GNOME does not implement server side decorations on
/// wayland. libdecor is loaded dynamically at runtime, so in general enabling it is always
/// safe to do. It is enabled by default.
pub const WaylandLibdecorInitHint = enum(c_int) {
wayland_prefer_libdecor = c.GLFW_WAYLAND_PREFER_LIBDECOR,
wayland_disable_libdecor = c.GLFW_WAYLAND_DISABLE_LIBDECOR,
};
/// Platform type hints for glfw.InitHint.platform
pub const PlatformType = enum(c_int) {
/// Enables automatic platform detection.
/// Will default to X11 on wayland.
any = c.GLFW_ANY_PLATFORM,
win32 = c.GLFW_PLATFORM_WIN32,
cocoa = c.GLFW_PLATFORM_COCOA,
wayland = c.GLFW_PLATFORM_WAYLAND,
x11 = c.GLFW_PLATFORM_X11,
null = c.GLFW_PLATFORM_NULL,
};
/// Sets the specified init hint to the desired value.
///
/// This function sets hints for the next initialization of GLFW.
///
/// The values you set hints to are never reset by GLFW, but they only take effect during
/// initialization. Once GLFW has been initialized, any values you set will be ignored until the
/// library is terminated and initialized again.
///
/// Some hints are platform specific. These may be set on any platform but they will only affect
/// their specific platform. Other platforms will ignore them. Setting these hints requires no
/// platform specific headers or functions.
///
/// @param hint: The init hint to set.
/// @param value: The new value of the init hint.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.InvalidValue.
///
/// @remarks This function may be called before glfw.init.
///
/// @thread_safety This function must only be called from the main thread.
fn initHint(hint: InitHint, value: anytype) void {
switch (@import("shims.zig").typeInfo(@TypeOf(value))) {
.int, .comptime_int => {
c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(value)));
},
.bool => c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(@intFromBool(value)))),
else => @compileError("expected a int or bool, got " ++ @typeName(@TypeOf(value))),
}
}
/// Returns a string describing the compile-time configuration.
///
/// This function returns the compile-time generated version string of the GLFW library binary. It
/// describes the version, platform, compiler and any platform or operating system specific
/// compile-time options. It should not be confused with the OpenGL or OpenGL ES version string,
/// queried with `glGetString`.
///
/// __Do not use the version string__ to parse the GLFW library version. Use the glfw.version
/// constants instead.
///
/// __Do not use the version string__ to parse what platforms are supported. The
/// `glfw.platformSupported` function lets you query platform support.
///
/// returns: The ASCII encoded GLFW version string.
///
/// remark: This function may be called before @ref glfw.Init.
///
/// pointer_lifetime: The returned string is static and compile-time generated.
///
/// thread_safety: This function may be called from any thread.
pub inline fn getVersionString() [:0]const u8 {
return std.mem.span(@as([*:0]const u8, @ptrCast(c.glfwGetVersionString())));
}
/// Returns the currently selected platform.
///
/// This function returns the platform that was selected during initialization. The returned value
/// will be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`,
/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`.
///
/// thread_safety: This function may be called from any thread.
pub fn getPlatform() PlatformType {
internal_debug.assertInitialized();
return @as(PlatformType, @enumFromInt(c.glfwGetPlatform()));
}
/// Returns whether the library includes support for the specified platform.
///
/// This function returns whether the library was compiled with support for the specified platform.
/// The platform must be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`,
/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`.
///
/// remark: This function may be called before glfw.Init.
///
/// thread_safety: This function may be called from any thread.
pub fn platformSupported(platform: PlatformType) bool {
internal_debug.assertInitialized();
return c.glfwPlatformSupported(@intFromEnum(platform)) == c.GLFW_TRUE;
}
/// Processes all pending events.
///
/// This function processes only those events that are already in the event queue and then returns
/// immediately. Processing events will cause the window and input callbacks associated with those
/// events to be called.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout
pub inline fn pollEvents() void {
internal_debug.assertInitialized();
c.glfwPollEvents();
}
/// Waits until events are queued and processes them.
///
/// This function puts the calling thread to sleep until at least one event is available in the
/// event queue. Once one or more events are available, it behaves exactly like glfw.pollEvents,
/// i.e. the events in the queue are processed and the function then returns immediately.
/// Processing events will cause the window and input callbacks associated with those events to be
/// called.
///
/// Since not all events are associated with callbacks, this function may return without a callback
/// having been called even if you are monitoring all callbacks.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.pollEvents, glfw.waitEventsTimeout
pub inline fn waitEvents() void {
internal_debug.assertInitialized();
c.glfwWaitEvents();
}
/// Waits with timeout until events are queued and processes them.
///
/// This function puts the calling thread to sleep until at least one event is available in the
/// event queue, or until the specified timeout is reached. If one or more events are available, it
/// behaves exactly like glfw.pollEvents, i.e. the events in the queue are processed and the
/// function then returns immediately. Processing events will cause the window and input callbacks
/// associated with those events to be called.
///
/// The timeout value must be a positive finite number.
///
/// Since not all events are associated with callbacks, this function may return without a callback
/// having been called even if you are monitoring all callbacks.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// @param[in] timeout The maximum amount of time, in seconds, to wait.
///
/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.pollEvents, glfw.waitEvents
pub inline fn waitEventsTimeout(timeout: f64) void {
internal_debug.assertInitialized();
std.debug.assert(!std.math.isNan(timeout));
std.debug.assert(timeout >= 0);
std.debug.assert(timeout <= std.math.floatMax(f64));
c.glfwWaitEventsTimeout(timeout);
}
/// Posts an empty event to the event queue.
///
/// This function posts an empty event from the current thread to the event queue, causing
/// glfw.waitEvents or glfw.waitEventsTimeout to return.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout
pub inline fn postEmptyEvent() void {
internal_debug.assertInitialized();
c.glfwPostEmptyEvent();
}
/// Returns whether raw mouse motion is supported.
///
/// This function returns whether raw mouse motion is supported on the current system. This status
/// does not change after GLFW has been initialized so you only need to check this once. If you
/// attempt to enable raw motion on a system that does not support it, glfw.ErrorCode.PlatformError
/// will be emitted.
///
/// Raw mouse motion is closer to the actual motion of the mouse across a surface. It is not
/// affected by the scaling and acceleration applied to the motion of the desktop cursor. That
/// processing is suitable for a cursor while raw motion is better for controlling for example a 3D
/// camera. Because of this, raw mouse motion is only provided when the cursor is disabled.
///
/// @return `true` if raw mouse motion is supported on the current machine, or `false` otherwise.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: raw_mouse_motion, glfw.setInputMode
pub inline fn rawMouseMotionSupported() bool {
internal_debug.assertInitialized();
return c.glfwRawMouseMotionSupported() == c.GLFW_TRUE;
}
pub fn basicTest() !void {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
const start = std.time.milliTimestamp();
while (std.time.milliTimestamp() < start + 1000 and !window.shouldClose()) {
c.glfwPollEvents();
}
}
test {
std.testing.refAllDeclsRecursive(@This());
}
test "getVersionString" {
std.debug.print("\nGLFW version v{}.{}.{}\n", .{ version.major, version.minor, version.revision });
std.debug.print("\nstring: {s}\n", .{getVersionString()});
}
test "init" {
_ = init(.{ .cocoa_chdir_resources = true });
if (getErrorString()) |err| {
std.log.err("failed to initialize GLFW: {?s}", .{err});
std.process.exit(1);
}
defer terminate();
}
test "pollEvents" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
pollEvents();
}
test "waitEventsTimeout" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
waitEventsTimeout(0.25);
}
test "postEmptyEvent_and_waitEvents" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
postEmptyEvent();
waitEvents();
}
test "rawMouseMotionSupported" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
_ = rawMouseMotionSupported();
}
test "basic" {
try basicTest();
}

167
pkg/glfw/mod.zig Normal file
View File

@@ -0,0 +1,167 @@
//! Modifier key flags
//!
//! See glfw.setKeyCallback for how these are used.
const c = @import("c.zig").c;
// must be in sync with GLFW C constants in modifier group, search for "@defgroup mods Modifier key flags"
/// A bitmask of all key modifiers
pub const Mods = packed struct(u8) {
shift: bool = false,
control: bool = false,
alt: bool = false,
super: bool = false,
caps_lock: bool = false,
num_lock: bool = false,
_padding: u2 = 0,
inline fn verifyIntType(comptime IntType: type) void {
comptime {
switch (@import("shims.zig").typeInfo(IntType)) {
.int => {},
else => @compileError("Int was not of int type"),
}
}
}
pub inline fn toInt(self: Mods, comptime IntType: type) IntType {
verifyIntType(IntType);
return @as(IntType, @intCast(@as(u8, @bitCast(self))));
}
pub inline fn fromInt(flags: anytype) Mods {
verifyIntType(@TypeOf(flags));
return @as(Mods, @bitCast(@as(u8, @intCast(flags))));
}
};
/// Holds all GLFW mod values in their raw form.
pub const RawMods = struct {
/// If this bit is set one or more Shift keys were held down.
pub const shift = c.GLFW_MOD_SHIFT;
/// If this bit is set one or more Control keys were held down.
pub const control = c.GLFW_MOD_CONTROL;
/// If this bit is set one or more Alt keys were held down.
pub const alt = c.GLFW_MOD_ALT;
/// If this bit is set one or more Super keys were held down.
pub const super = c.GLFW_MOD_SUPER;
/// If this bit is set the Caps Lock key is enabled and the glfw.lock_key_mods input mode is set.
pub const caps_lock = c.GLFW_MOD_CAPS_LOCK;
/// If this bit is set the Num Lock key is enabled and the glfw.lock_key_mods input mode is set.
pub const num_lock = c.GLFW_MOD_NUM_LOCK;
};
test "shift int to bitmask" {
const std = @import("std");
const int_mod = RawMods.shift;
const mod = Mods.fromInt(int_mod);
try std.testing.expect(mod.shift == true);
try std.testing.expect(mod.control == false);
try std.testing.expect(mod.alt == false);
try std.testing.expect(mod.super == false);
try std.testing.expect(mod.caps_lock == false);
try std.testing.expect(mod.num_lock == false);
}
test "shift int and alt to bitmask" {
const std = @import("std");
const int_mod = RawMods.shift | RawMods.alt;
const mod = Mods.fromInt(int_mod);
try std.testing.expect(mod.shift == true);
try std.testing.expect(mod.control == false);
try std.testing.expect(mod.alt == true);
try std.testing.expect(mod.super == false);
try std.testing.expect(mod.caps_lock == false);
try std.testing.expect(mod.num_lock == false);
}
test "super int to bitmask" {
const std = @import("std");
const int_mod = RawMods.super;
const mod = Mods.fromInt(int_mod);
try std.testing.expect(mod.shift == false);
try std.testing.expect(mod.control == false);
try std.testing.expect(mod.alt == false);
try std.testing.expect(mod.super == true);
try std.testing.expect(mod.caps_lock == false);
try std.testing.expect(mod.num_lock == false);
}
test "num lock int to bitmask" {
const std = @import("std");
const int_mod = RawMods.num_lock;
const mod = Mods.fromInt(int_mod);
try std.testing.expect(mod.shift == false);
try std.testing.expect(mod.control == false);
try std.testing.expect(mod.alt == false);
try std.testing.expect(mod.super == false);
try std.testing.expect(mod.caps_lock == false);
try std.testing.expect(mod.num_lock == true);
}
test "all int to bitmask" {
const std = @import("std");
const int_mod = RawMods.shift | RawMods.control |
RawMods.alt | RawMods.super |
RawMods.caps_lock | RawMods.num_lock;
const mod = Mods.fromInt(int_mod);
try std.testing.expect(mod.shift == true);
try std.testing.expect(mod.control == true);
try std.testing.expect(mod.alt == true);
try std.testing.expect(mod.super == true);
try std.testing.expect(mod.caps_lock == true);
try std.testing.expect(mod.num_lock == true);
}
test "shift bitmask to int" {
const std = @import("std");
const mod = Mods{ .shift = true };
const int_mod = mod.toInt(c_int);
try std.testing.expectEqual(int_mod, RawMods.shift);
}
test "shift and alt bitmask to int" {
const std = @import("std");
const mod = Mods{ .shift = true, .alt = true };
const int_mod = mod.toInt(c_int);
try std.testing.expectEqual(int_mod, RawMods.shift | RawMods.alt);
}
test "all bitmask to int" {
const std = @import("std");
const mod = Mods{
.shift = true,
.control = true,
.alt = true,
.super = true,
.caps_lock = true,
.num_lock = true,
};
const int_mod = mod.toInt(c_int);
const expected = RawMods.shift | RawMods.control |
RawMods.alt | RawMods.super |
RawMods.caps_lock | RawMods.num_lock;
try std.testing.expectEqual(int_mod, expected);
}

Some files were not shown because too many files have changed in this diff Show More