mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
Compare commits
183 Commits
push-lvtrm
...
7d5be8e960
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7d5be8e960 | ||
![]() |
d05ec81b86 | ||
![]() |
f016b79f22 | ||
![]() |
52f5ab1a36 | ||
![]() |
8d11c08db3 | ||
![]() |
90c0fc2590 | ||
![]() |
e909e28876 | ||
![]() |
cf0390bab5 | ||
![]() |
4614e5fdad | ||
![]() |
ce94bb9f6a | ||
![]() |
ac104a3dfc | ||
![]() |
16e47e7586 | ||
![]() |
e8217aa007 | ||
![]() |
9aa1698e5a | ||
![]() |
3664ee9f87 | ||
![]() |
a72995590b | ||
![]() |
2bf0d3f4c7 | ||
![]() |
4af290d5f0 | ||
![]() |
ef7857f9be | ||
![]() |
7dcf2c9b62 | ||
![]() |
d316449ebf | ||
![]() |
650028fa9f | ||
![]() |
5ef6412823 | ||
![]() |
0b58830882 | ||
![]() |
a41ec17b61 | ||
![]() |
c535d0a664 | ||
![]() |
2009ea511d | ||
![]() |
d8578a9ee2 | ||
![]() |
0d30f859bd | ||
![]() |
650095e7e9 | ||
![]() |
6319464cfb | ||
![]() |
fc6266133f | ||
![]() |
a51a956bdb | ||
![]() |
c94805f0aa | ||
![]() |
937d17cc35 | ||
![]() |
0bc90b2a20 | ||
![]() |
75e3835a9e | ||
![]() |
d1e01ec5c3 | ||
![]() |
b0d9b0dee0 | ||
![]() |
e6b019b197 | ||
![]() |
5761f66f35 | ||
![]() |
a5eef1d227 | ||
![]() |
85e642097a | ||
![]() |
bed350f0be | ||
![]() |
460fcc1344 | ||
![]() |
f91e6f1764 | ||
![]() |
f802d33652 | ||
![]() |
2701932475 | ||
![]() |
6cfd89e248 | ||
![]() |
9962e523a8 | ||
![]() |
04956f3dc1 | ||
![]() |
f1ea30dcf1 | ||
![]() |
d3cadf2495 | ||
![]() |
7106d71a42 | ||
![]() |
f54f2dc7f3 | ||
![]() |
5013d028a3 | ||
![]() |
adfc93047c | ||
![]() |
a3f4997fbc | ||
![]() |
56d3fd872e | ||
![]() |
58e85bf133 | ||
![]() |
19a27383f8 | ||
![]() |
8fa065512f | ||
![]() |
6530107e3b | ||
![]() |
87056a2600 | ||
![]() |
6a128189e3 | ||
![]() |
2490171304 | ||
![]() |
31c96d906a | ||
![]() |
64d8492836 | ||
![]() |
c1ab41afac | ||
![]() |
f047db6a3b | ||
![]() |
cd8455c24b | ||
![]() |
4d6269a859 | ||
![]() |
3fb17dc802 | ||
![]() |
6cf636b1ad | ||
![]() |
673afd193b | ||
![]() |
ff61cad1e2 | ||
![]() |
520eaec61c | ||
![]() |
e676eae640 | ||
![]() |
830194d436 | ||
![]() |
6f630a27be | ||
![]() |
ae48f323d7 | ||
![]() |
a90bf58080 | ||
![]() |
6a78f9c0c0 | ||
![]() |
466fdfffe6 | ||
![]() |
58e7400ea5 | ||
![]() |
0afadeea5f | ||
![]() |
e5ad6603f4 | ||
![]() |
7cc0728fe5 | ||
![]() |
17f7f204e1 | ||
![]() |
63cd424678 | ||
![]() |
5c464e855d | ||
![]() |
9c725187e1 | ||
![]() |
4630369f87 | ||
![]() |
5b0801cbc9 | ||
![]() |
3320a081b4 | ||
![]() |
9a56e77937 | ||
![]() |
a471bac782 | ||
![]() |
14a3765916 | ||
![]() |
ca06b95f65 | ||
![]() |
d659bdcfdd | ||
![]() |
754bb4011a | ||
![]() |
11d845ce17 | ||
![]() |
c629ea674c | ||
![]() |
f8d69e5baf | ||
![]() |
c396c25898 | ||
![]() |
e98e868265 | ||
![]() |
52a25e9c69 | ||
![]() |
8aa0b4c92a | ||
![]() |
8a14f21325 | ||
![]() |
5c03ff8165 | ||
![]() |
400576f0b0 | ||
![]() |
c9199f2ba2 | ||
![]() |
48120f8b6c | ||
![]() |
27ed58252d | ||
![]() |
c26323d697 | ||
![]() |
13425b4881 | ||
![]() |
9ff716642e | ||
![]() |
c57a84a6de | ||
![]() |
42b1ff70d1 | ||
![]() |
95bc181c98 | ||
![]() |
a18332828a | ||
![]() |
00e4a90699 | ||
![]() |
7622d2662d | ||
![]() |
e1d4c37996 | ||
![]() |
1b8dd234b0 | ||
![]() |
43e010bf47 | ||
![]() |
062d596c0a | ||
![]() |
a3be474d28 | ||
![]() |
b347585e27 | ||
![]() |
1aa59cf63d | ||
![]() |
bd4e9b96bf | ||
![]() |
59fd366264 | ||
![]() |
78f05ec96c | ||
![]() |
0d536d447c | ||
![]() |
4f4c06967a | ||
![]() |
f6f2a85256 | ||
![]() |
c181fc4fbf | ||
![]() |
d854ecd374 | ||
![]() |
652f6f1deb | ||
![]() |
c014dd79f6 | ||
![]() |
292efec669 | ||
![]() |
0c722b0e3d | ||
![]() |
54f8dff308 | ||
![]() |
60e077b651 | ||
![]() |
50fe12e85c | ||
![]() |
f4abecefe4 | ||
![]() |
db60e981d1 | ||
![]() |
3859f50b88 | ||
![]() |
4fdf0b687e | ||
![]() |
85cba70c2e | ||
![]() |
5bff354e96 | ||
![]() |
6d9cac5ffc | ||
![]() |
6248030426 | ||
![]() |
6708229a7e | ||
![]() |
91f973afdb | ||
![]() |
14f5a879a9 | ||
![]() |
eaa81be051 | ||
![]() |
83b573aed7 | ||
![]() |
c78fb0f895 | ||
![]() |
5441578f08 | ||
![]() |
c142473405 | ||
![]() |
fc1307e939 | ||
![]() |
5836dc4ce6 | ||
![]() |
93c2400bf4 | ||
![]() |
f4009721a1 | ||
![]() |
a479c9b2af | ||
![]() |
35102ddb5a | ||
![]() |
298f11166d | ||
![]() |
7d60c7c75b | ||
![]() |
056ccc9818 | ||
![]() |
3ef6de4ffa | ||
![]() |
1ce56a12fa | ||
![]() |
f736ee8865 | ||
![]() |
40105e1c7e | ||
![]() |
c110c0f76d | ||
![]() |
c675896595 | ||
![]() |
53c2f915d8 | ||
![]() |
795c745491 | ||
![]() |
534aa508d6 | ||
![]() |
f7994e6412 | ||
![]() |
108260100c | ||
![]() |
db2984de6e | ||
![]() |
487b1d72ab |
4
.github/workflows/nix.yml
vendored
4
.github/workflows/nix.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
|
4
.github/workflows/release-pr.yml
vendored
4
.github/workflows/release-pr.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
8
.github/workflows/release-tag.yml
vendored
8
.github/workflows/release-tag.yml
vendored
@@ -83,13 +83,13 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -311,7 +311,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
10
.github/workflows/release-tip.yml
vendored
10
.github/workflows/release-tip.yml
vendored
@@ -107,12 +107,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -401,7 +401,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -581,7 +581,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
168
.github/workflows/test.yml
vendored
168
.github/workflows/test.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
- build-bench
|
||||
- build-dist
|
||||
- build-flatpak
|
||||
- build-freebsd
|
||||
- build-linux
|
||||
- build-linux-libghostty
|
||||
- build-nix
|
||||
@@ -20,7 +21,6 @@ jobs:
|
||||
- build-macos
|
||||
- build-macos-matrix
|
||||
- build-windows
|
||||
- flatpak-check-zig-cache
|
||||
- test
|
||||
- test-gtk
|
||||
- test-gtk-ng
|
||||
@@ -70,14 +70,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -101,14 +101,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -137,14 +137,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -166,14 +166,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -199,14 +199,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -243,14 +243,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -378,7 +378,7 @@ jobs:
|
||||
mkdir dist
|
||||
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
@@ -474,14 +474,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -519,14 +519,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -568,14 +568,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -616,14 +616,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -674,12 +674,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -702,12 +702,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -729,12 +729,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -756,12 +756,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -783,12 +783,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -810,12 +810,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -844,12 +844,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -871,12 +871,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -906,14 +906,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -954,33 +954,6 @@ jobs:
|
||||
build-args: |
|
||||
DISTRO_VERSION=13
|
||||
|
||||
flatpak-check-zig-cache:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
runs-on: namespace-profile-ghostty-xsm
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
useDaemon: false # sometimes fails on short jobs
|
||||
- name: Check Flatpak Zig Dependencies
|
||||
run: nix develop -c ./flatpak/build-support/check-zig-cache.sh
|
||||
|
||||
flatpak:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
name: "Flatpak"
|
||||
@@ -996,7 +969,7 @@ jobs:
|
||||
- arch: aarch64
|
||||
runner: namespace-profile-ghostty-md-arm64
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
needs: [flatpak-check-zig-cache, test]
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@10a3c29f0162516f0f68006be14c92f34bd4fa6c # v6.5
|
||||
@@ -1010,6 +983,7 @@ jobs:
|
||||
valgrind:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
runs-on: namespace-profile-ghostty-lg
|
||||
timeout-minutes: 30
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
@@ -1019,14 +993,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -1042,3 +1016,57 @@ jobs:
|
||||
- name: valgrind
|
||||
run: |
|
||||
nix develop -c zig build test-valgrind
|
||||
|
||||
build-freebsd:
|
||||
name: Build on FreeBSD
|
||||
needs: test
|
||||
runs-on: namespace-profile-mitchellh-sm-systemd
|
||||
strategy:
|
||||
matrix:
|
||||
release:
|
||||
- "14.3"
|
||||
# - "15.0" # disable until fixed: https://github.com/vmactions/freebsd-vm/issues/108
|
||||
steps:
|
||||
- name: Checkout Ghostty
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Start SSH
|
||||
run: |
|
||||
sudo systemctl start ssh
|
||||
|
||||
- name: Set up FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@05856381fab64eeee9b038a0818f6cec649ca17a # v1.2.3
|
||||
with:
|
||||
release: ${{ matrix.release }}
|
||||
copyback: false
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
devel/blueprint-compiler \
|
||||
devel/gettext \
|
||||
devel/git \
|
||||
devel/pkgconf \
|
||||
graphics/wayland \
|
||||
lang/zig \
|
||||
security/ca_root_nss \
|
||||
textproc/hs-pandoc \
|
||||
x11-fonts/jetbrains-mono \
|
||||
x11-toolkits/libadwaita \
|
||||
x11-toolkits/gtk40 \
|
||||
x11-toolkits/gtk4-layer-shell
|
||||
|
||||
run: |
|
||||
zig env
|
||||
|
||||
- name: Run tests
|
||||
shell: freebsd {0}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
zig build test
|
||||
|
||||
- name: Build GTK-NG app runtime
|
||||
shell: freebsd {0}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
zig build
|
||||
./zig-out/bin/ghostty +version
|
||||
|
6
.github/workflows/update-colorschemes.yml
vendored
6
.github/workflows/update-colorschemes.yml
vendored
@@ -22,14 +22,14 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -50,8 +50,6 @@ jobs:
|
||||
if ! git diff --exit-code build.zig.zon; then
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh --update
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh
|
||||
nix develop -c ./flatpak/build-support/check-zig-cache.sh --update
|
||||
nix develop -c ./flatpak/build-support/check-zig-cache.sh
|
||||
fi
|
||||
|
||||
# Verify the build still works. We choose an arbitrary build type
|
||||
|
@@ -169,6 +169,7 @@
|
||||
/po/es_BO.UTF-8.po @ghostty-org/es_BO
|
||||
/po/es_AR.UTF-8.po @ghostty-org/es_AR
|
||||
/po/fr_FR.UTF-8.po @ghostty-org/fr_FR
|
||||
/po/hu_HU.UTF-8.po @ghostty-org/hu_HU
|
||||
/po/id_ID.UTF-8.po @ghostty-org/id_ID
|
||||
/po/ja_JP.UTF-8.po @ghostty-org/ja_JP
|
||||
/po/mk_MK.UTF-8.po @ghostty-org/mk_MK
|
||||
|
265
CONTRIBUTING.md
265
CONTRIBUTING.md
@@ -1,9 +1,9 @@
|
||||
# Ghostty Development Process
|
||||
# Contributing to Ghostty
|
||||
|
||||
This document describes the development process for Ghostty. It is intended for
|
||||
anyone considering opening an **issue** or **pull request**. If in doubt,
|
||||
please open a [discussion](https://github.com/ghostty-org/ghostty/discussions);
|
||||
we can always convert that to an issue later.
|
||||
This document describes the process of contributing to Ghostty. It is intended
|
||||
for anyone considering opening an **issue**, **discussion** or **pull request**.
|
||||
For people who are interested in developing Ghostty and technical details behind
|
||||
it, please check out our ["Developing Ghostty"](HACKING.md) document as well.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@@ -49,13 +49,16 @@ Please be respectful to maintainers and disclose AI assistance.
|
||||
|
||||
## Quick Guide
|
||||
|
||||
**I'd like to contribute!**
|
||||
### I'd like to contribute!
|
||||
|
||||
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".
|
||||
[All issues are actionable](#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!**
|
||||
["contributor friendly"]: https://github.com/ghostty-org/ghostty/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22contributor%20friendly%22
|
||||
|
||||
### 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.
|
||||
@@ -64,25 +67,39 @@ 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!**
|
||||
### I have a bug! / Something isn't working!
|
||||
|
||||
1. Search the issue tracker and discussions for similar issues.
|
||||
2. If you don't have steps to reproduce, open a discussion.
|
||||
3. If you have steps to reproduce, open an issue.
|
||||
1. Search the issue tracker and discussions for similar issues. Tip: also
|
||||
search for [closed issues] and [discussions] — your issue might have already
|
||||
been fixed!
|
||||
2. If your issue hasn't been reported already, open an ["Issue Triage" discussion]
|
||||
and make sure to fill in the template **completely**. They are vital for
|
||||
maintainers to figure out important details about your setup. Because of
|
||||
this, please make sure that you _only_ use the "Issue Triage" category for
|
||||
reporting bugs — thank you!
|
||||
|
||||
**I have an idea for a feature!**
|
||||
[closed issues]: https://github.com/ghostty-org/ghostty/issues?q=is%3Aissue%20state%3Aclosed
|
||||
[discussions]: https://github.com/ghostty-org/ghostty/discussions?discussions_q=is%3Aclosed
|
||||
["Issue Triage" discussion]: https://github.com/ghostty-org/ghostty/discussions/new?category=issue-triage
|
||||
|
||||
1. Open a discussion.
|
||||
### I have an idea for a feature!
|
||||
|
||||
**I've implemented a feature!**
|
||||
Open a discussion in the ["Feature Requests, Ideas" category](https://github.com/ghostty-org/ghostty/discussions/new?category=feature-requests-ideas).
|
||||
|
||||
1. If there is an issue for the feature, open a pull request.
|
||||
### I've implemented a feature!
|
||||
|
||||
1. If there is an issue for the feature, open a pull request straight away.
|
||||
2. If there is no issue, open a discussion and link to your branch.
|
||||
3. If you want to live dangerously, open a pull request and hope for the best.
|
||||
3. If you want to live dangerously, open a pull request and
|
||||
[hope for the best](#pull-requests-implement-an-issue).
|
||||
|
||||
**I have a question!**
|
||||
### I have a question!
|
||||
|
||||
1. Open a discussion or use Discord.
|
||||
Open an [Q&A discussion], or join our [Discord Server] and ask away in the
|
||||
`#help` channel.
|
||||
|
||||
[Q&A discussion]: https://github.com/ghostty-org/ghostty/discussions/new?category=q-a
|
||||
[Discord Server]: https://discord.gg/ghostty
|
||||
|
||||
## General Patterns
|
||||
|
||||
@@ -120,209 +137,3 @@ pull request will be accepted with a high degree of certainty.
|
||||
> **Pull requests are NOT a place to discuss feature design.** Please do
|
||||
> 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.
|
||||
|
||||
## Checking for Memory Leaks
|
||||
|
||||
While Zig does an amazing job of finding and preventing memory leaks,
|
||||
Ghostty uses many third-party libraries that are written in C. Improper usage
|
||||
of those libraries or bugs in those libraries can cause memory leaks that
|
||||
Zig cannot detect by itself.
|
||||
|
||||
### On Linux
|
||||
|
||||
On Linux the recommended tool to check for memory leaks is Valgrind. The
|
||||
recommended way to run Valgrind is via `zig build`:
|
||||
|
||||
```sh
|
||||
zig build run-valgrind
|
||||
```
|
||||
|
||||
This builds a Ghostty executable with Valgrind support and runs Valgrind
|
||||
with the proper flags to ensure we're suppressing known false positives.
|
||||
|
||||
You can combine the same build args with `run-valgrind` that you can with
|
||||
`run`, such as specifying additional configurations after a trailing `--`.
|
||||
|
||||
## 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
|
||||
and developing Ghostty against multiple different Linux desktop environments.
|
||||
|
||||
Running these requires a working Nix installation, either Nix on your
|
||||
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
|
||||
requirements for macOS are detailed below.
|
||||
|
||||
VMs should only be run on your local desktop and then powered off when not in
|
||||
use, which will discard any changes to the VM.
|
||||
|
||||
The VM definitions provide minimal software "out of the box" but additional
|
||||
software can be installed by using standard Nix mechanisms like `nix run nixpkgs#<package>`.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Check out the Ghostty source and change to the directory.
|
||||
2. Run `nix run .#<vmtype>`. `<vmtype>` can be any of the VMs defined in the
|
||||
`nix/vm` directory (without the `.nix` suffix) excluding any file prefixed
|
||||
with `common` or `create`.
|
||||
3. The VM will build and then launch. Depending on the speed of your system, this
|
||||
can take a while, but eventually you should get a new VM window.
|
||||
4. The Ghostty source directory should be mounted to `/tmp/shared` in the VM. Depending
|
||||
on what UID and GID of the user that you launched the VM as, `/tmp/shared` _may_ be
|
||||
writable by the VM user, so be careful!
|
||||
|
||||
### macOS
|
||||
|
||||
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
|
||||
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
|
||||
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
|
||||
blog post for more information about the Linux builder and how to tune the performance.
|
||||
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
|
||||
above to launch a VM.
|
||||
|
||||
### Custom VMs
|
||||
|
||||
To easily create a custom VM without modifying the Ghostty source, create a new
|
||||
directory, then create a file called `flake.nix` with the following text in the
|
||||
new directory.
|
||||
|
||||
```
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
ghostty.url = "github:ghostty-org/ghostty";
|
||||
};
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
ghostty,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations.custom-vm = ghostty.create-gnome-vm {
|
||||
nixpkgs = nixpkgs;
|
||||
system = "x86_64-linux";
|
||||
overlay = ghostty.overlays.releasefast;
|
||||
# module = ./configuration.nix # also works
|
||||
module = {pkgs, ...}: {
|
||||
environment.systemPackages = [
|
||||
pkgs.btop
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The custom VM can then be run with a command like this:
|
||||
|
||||
```
|
||||
nix run .#nixosConfigurations.custom-vm.config.system.build.vm
|
||||
```
|
||||
|
||||
A file named `ghostty.qcow2` will be created that is used to persist any changes
|
||||
made in the VM. To "reset" the VM to default delete the file and it will be
|
||||
recreated the next time you run the VM.
|
||||
|
||||
### Contributing new VM definitions
|
||||
|
||||
#### VM Acceptance Criteria
|
||||
|
||||
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
|
||||
|
||||
1. They should be different enough from existing VM definitions that they represent a distinct
|
||||
user (and developer) experience.
|
||||
2. There's a significant Ghostty user population that uses a similar environment.
|
||||
3. The VMs can be built using only packages from the current stable NixOS release.
|
||||
|
||||
#### VM Definition Criteria
|
||||
|
||||
1. VMs should be as minimal as possible so that they build and launch quickly.
|
||||
Additional software can be added at runtime with a command like `nix run nixpkgs#<package name>`.
|
||||
2. VMs should not expose any services to the network, or run any remote access
|
||||
software like SSH daemons, VNC or RDP.
|
||||
3. VMs should auto-login using the "ghostty" user.
|
||||
|
329
HACKING.md
Normal file
329
HACKING.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# Developing Ghostty
|
||||
|
||||
This document describes the technical details behind Ghostty's development.
|
||||
If you'd like to open any pull requests or would like to implement new features
|
||||
into Ghostty, please make sure to read our ["Contributing to Ghostty"](CONTRIBUTING.md)
|
||||
document first.
|
||||
|
||||
To start development on Ghostty, you need to build Ghostty from a Git checkout,
|
||||
which is very similar in process to [building Ghostty from a source tarball](http://ghostty.org/docs/install/build). One key difference is that obviously
|
||||
you need to clone the Git repository instead of unpacking the source tarball:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ghostty-org/ghostty
|
||||
cd ghostty
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Ghostty may require [extra dependencies](#extra-dependencies)
|
||||
> when building from a Git checkout compared to a source tarball.
|
||||
> Tip versions may also require a different version of Zig or other toolchains
|
||||
> (e.g. the Xcode SDK on macOS) compared to stable versions — make sure to
|
||||
> follow the steps closely!
|
||||
|
||||
When you're developing Ghostty, it's very likely that you will want to build a
|
||||
_debug_ build to diagnose issues more easily. This is already the default for
|
||||
Zig builds, so simply run `zig build` **without any `-Doptimize` flags**.
|
||||
|
||||
There are many more build steps than just `zig build`, some of which are listed
|
||||
here:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `zig build run` | Runs Ghostty |
|
||||
| `zig build run-valgrind` | Runs Ghostty under Valgrind to [check for memory leaks](#checking-for-memory-leaks) |
|
||||
| `zig build test` | Runs unit tests (accepts `-Dtest-filter=<filter>` to only run tests whose name matches the filter) |
|
||||
| `zig build update-translations` | Updates Ghostty's translation strings (see the [Contributor's Guide on Localizing Ghostty](po/README_CONTRIBUTORS.md)) |
|
||||
| `zig build dist` | Builds a source tarball |
|
||||
| `zig build distcheck` | Installs and validates a source tarball |
|
||||
|
||||
## Extra Dependencies
|
||||
|
||||
Building Ghostty from a Git checkout on Linux requires some additional
|
||||
dependencies:
|
||||
|
||||
- `blueprint-compiler` (version 0.16.0 or newer)
|
||||
|
||||
macOS users don't require any additional dependencies.
|
||||
|
||||
## Xcode Version and SDKs
|
||||
|
||||
Building the Ghostty macOS app requires that Xcode, the macOS SDK,
|
||||
and the iOS SDK are all installed.
|
||||
|
||||
A common issue is that the incorrect version of Xcode is either
|
||||
installed or selected. Use the `xcode-select` command to
|
||||
ensure that the correct version of Xcode is selected:
|
||||
|
||||
```shell-session
|
||||
sudo xcode-select --switch /Applications/Xcode-beta.app
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> Main branch development of Ghostty is preparing for the next major
|
||||
> macOS release, Tahoe (macOS 26). Therefore, the main branch requires
|
||||
> **Xcode 26 and the macOS 26 SDK**.
|
||||
>
|
||||
> You do not need to be running on macOS 26 to build Ghostty, you can
|
||||
> still use Xcode 26 beta on macOS 15 stable.
|
||||
|
||||
## Linting
|
||||
|
||||
### Prettier
|
||||
|
||||
Ghostty's docs and resources (not including Zig code) are linted using
|
||||
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
|
||||
check will fail builds with improper formatting. Therefore, if you are
|
||||
modifying anything Prettier will lint, you may want to install it locally and
|
||||
run this from the repo root before you commit:
|
||||
|
||||
```
|
||||
prettier --write .
|
||||
```
|
||||
|
||||
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
Nix users can use the following command to format with Prettier:
|
||||
|
||||
```
|
||||
nix develop -c prettier --write .
|
||||
```
|
||||
|
||||
### Alejandra
|
||||
|
||||
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
|
||||
will fail builds with improper formatting.
|
||||
|
||||
Nix users can use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
nix develop -c alejandra .
|
||||
```
|
||||
|
||||
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
alejandra .
|
||||
```
|
||||
|
||||
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
### Updating the Zig Cache Fixed-Output Derivation Hash
|
||||
|
||||
The Nix package depends on a [fixed-output
|
||||
derivation](https://nix.dev/manual/nix/stable/language/advanced-attributes.html#adv-attr-outputHash)
|
||||
that manages the Zig package cache. This allows the package to be built in the
|
||||
Nix sandbox.
|
||||
|
||||
Occasionally (usually when `build.zig.zon` is updated), the hash that
|
||||
identifies the cache will need to be updated. There are jobs that monitor the
|
||||
hash in CI, and builds will fail if it drifts.
|
||||
|
||||
To update it, you can run the following in the repository root:
|
||||
|
||||
```
|
||||
./nix/build-support/check-zig-cache-hash.sh --update
|
||||
```
|
||||
|
||||
This will write out the `nix/zigCacheHash.nix` file with the updated hash
|
||||
that can then be committed and pushed to fix the builds.
|
||||
|
||||
## Including and Updating Translations
|
||||
|
||||
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
|
||||
|
||||
## Checking for Memory Leaks
|
||||
|
||||
While Zig does an amazing job of finding and preventing memory leaks,
|
||||
Ghostty uses many third-party libraries that are written in C. Improper usage
|
||||
of those libraries or bugs in those libraries can cause memory leaks that
|
||||
Zig cannot detect by itself.
|
||||
|
||||
### On Linux
|
||||
|
||||
On Linux the recommended tool to check for memory leaks is Valgrind. The
|
||||
recommended way to run Valgrind is via `zig build`:
|
||||
|
||||
```sh
|
||||
zig build run-valgrind
|
||||
```
|
||||
|
||||
This builds a Ghostty executable with Valgrind support and runs Valgrind
|
||||
with the proper flags to ensure we're suppressing known false positives.
|
||||
|
||||
You can combine the same build args with `run-valgrind` that you can with
|
||||
`run`, such as specifying additional configurations after a trailing `--`.
|
||||
|
||||
## 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
|
||||
and developing Ghostty against multiple different Linux desktop environments.
|
||||
|
||||
Running these requires a working Nix installation, either Nix on your
|
||||
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
|
||||
requirements for macOS are detailed below.
|
||||
|
||||
VMs should only be run on your local desktop and then powered off when not in
|
||||
use, which will discard any changes to the VM.
|
||||
|
||||
The VM definitions provide minimal software "out of the box" but additional
|
||||
software can be installed by using standard Nix mechanisms like `nix run nixpkgs#<package>`.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Check out the Ghostty source and change to the directory.
|
||||
2. Run `nix run .#<vmtype>`. `<vmtype>` can be any of the VMs defined in the
|
||||
`nix/vm` directory (without the `.nix` suffix) excluding any file prefixed
|
||||
with `common` or `create`.
|
||||
3. The VM will build and then launch. Depending on the speed of your system, this
|
||||
can take a while, but eventually you should get a new VM window.
|
||||
4. The Ghostty source directory should be mounted to `/tmp/shared` in the VM. Depending
|
||||
on what UID and GID of the user that you launched the VM as, `/tmp/shared` _may_ be
|
||||
writable by the VM user, so be careful!
|
||||
|
||||
### macOS
|
||||
|
||||
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
|
||||
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
|
||||
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
|
||||
blog post for more information about the Linux builder and how to tune the performance.
|
||||
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
|
||||
above to launch a VM.
|
||||
|
||||
### Custom VMs
|
||||
|
||||
To easily create a custom VM without modifying the Ghostty source, create a new
|
||||
directory, then create a file called `flake.nix` with the following text in the
|
||||
new directory.
|
||||
|
||||
```
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
ghostty.url = "github:ghostty-org/ghostty";
|
||||
};
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
ghostty,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations.custom-vm = ghostty.create-gnome-vm {
|
||||
nixpkgs = nixpkgs;
|
||||
system = "x86_64-linux";
|
||||
overlay = ghostty.overlays.releasefast;
|
||||
# module = ./configuration.nix # also works
|
||||
module = {pkgs, ...}: {
|
||||
environment.systemPackages = [
|
||||
pkgs.btop
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The custom VM can then be run with a command like this:
|
||||
|
||||
```
|
||||
nix run .#nixosConfigurations.custom-vm.config.system.build.vm
|
||||
```
|
||||
|
||||
A file named `ghostty.qcow2` will be created that is used to persist any changes
|
||||
made in the VM. To "reset" the VM to default delete the file and it will be
|
||||
recreated the next time you run the VM.
|
||||
|
||||
### Contributing new VM definitions
|
||||
|
||||
#### VM Acceptance Criteria
|
||||
|
||||
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
|
||||
|
||||
1. They should be different enough from existing VM definitions that they represent a distinct
|
||||
user (and developer) experience.
|
||||
2. There's a significant Ghostty user population that uses a similar environment.
|
||||
3. The VMs can be built using only packages from the current stable NixOS release.
|
||||
|
||||
#### VM Definition Criteria
|
||||
|
||||
1. VMs should be as minimal as possible so that they build and launch quickly.
|
||||
Additional software can be added at runtime with a command like `nix run nixpkgs#<package name>`.
|
||||
2. VMs should not expose any services to the network, or run any remote access
|
||||
software like SSH daemons, VNC or RDP.
|
||||
3. VMs should auto-login using the "ghostty" user.
|
128
README.md
128
README.md
@@ -13,7 +13,9 @@
|
||||
·
|
||||
<a href="https://ghostty.org/docs">Documentation</a>
|
||||
·
|
||||
<a href="#developing-ghostty">Developing</a>
|
||||
<a href="CONTRIBUTING.md">Contributing</a>
|
||||
·
|
||||
<a href="HACKING.md">Developing</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
@@ -49,6 +51,14 @@ See the [download page](https://ghostty.org/download) on the Ghostty website.
|
||||
|
||||
See the [documentation](https://ghostty.org/docs) on the Ghostty website.
|
||||
|
||||
## Contributing and Developing
|
||||
|
||||
If you have any ideas, issues, etc. regarding Ghostty, or would like to
|
||||
contribute to Ghostty through pull requests, please check out our
|
||||
["Contributing to Ghostty"](CONTRIBUTING.md) document. Those who would like
|
||||
to get involved with Ghostty's development as well should also read the
|
||||
["Developing Ghostty"](HACKING.md) document for more technical details.
|
||||
|
||||
## Roadmap and Status
|
||||
|
||||
The high-level ambitious plan for the project, in order:
|
||||
@@ -184,119 +194,3 @@ SENTRY_DSN=https://e914ee84fd895c4fe324afa3e53dac76@o4507352570920960.ingest.us.
|
||||
> stack memory of each thread at the time of the crash. This information
|
||||
> is used to rebuild the stack trace but can also contain sensitive data
|
||||
> depending when the crash occurred.
|
||||
|
||||
## Developing Ghostty
|
||||
|
||||
See the documentation on the Ghostty website for
|
||||
[building Ghostty from a source tarball](http://ghostty.org/docs/install/build).
|
||||
Building Ghostty from a Git checkout is very similar, except you want to
|
||||
omit the `-Doptimize` flag to build a debug build, and you may require
|
||||
additional dependencies since the source tarball includes some processed
|
||||
files that are not in the Git repository.
|
||||
|
||||
Other useful commands:
|
||||
|
||||
- `zig build test` for running unit tests.
|
||||
- `zig build test -Dtest-filter=<filter>` for running a specific subset of those unit tests
|
||||
- `zig build run -Dconformance=<name>` runs a conformance test case from
|
||||
the `conformance` directory. The `name` is the name of the file. This runs
|
||||
in the current running terminal emulator so if you want to check the
|
||||
behavior of this project, you must run this command in Ghostty.
|
||||
|
||||
### Extra Dependencies
|
||||
|
||||
Building Ghostty from a Git checkout on Linux requires some additional
|
||||
dependencies:
|
||||
|
||||
- `blueprint-compiler`
|
||||
|
||||
macOS users don't require any additional dependencies.
|
||||
|
||||
> [!NOTE]
|
||||
> This only applies to building from a _Git checkout_. This section does
|
||||
> not apply if you're building from a released _source tarball_. For
|
||||
> source tarballs, see the
|
||||
> [website](http://ghostty.org/docs/install/build).
|
||||
|
||||
### Xcode Version and SDKs
|
||||
|
||||
Building the Ghostty macOS app requires that Xcode, the macOS SDK,
|
||||
and the iOS SDK are all installed.
|
||||
|
||||
A common issue is that the incorrect version of Xcode is either
|
||||
installed or selected. Use the `xcode-select` command to
|
||||
ensure that the correct version of Xcode is selected:
|
||||
|
||||
```shell-session
|
||||
sudo xcode-select --switch /Applications/Xcode-beta.app
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> Main branch development of Ghostty is preparing for the next major
|
||||
> macOS release, Tahoe (macOS 26). Therefore, the main branch requires
|
||||
> **Xcode 26 and the macOS 26 SDK**.
|
||||
>
|
||||
> You do not need to be running on macOS 26 to build Ghostty, you can
|
||||
> still use Xcode 26 beta on macOS 15 stable.
|
||||
|
||||
### Linting
|
||||
|
||||
#### Prettier
|
||||
|
||||
Ghostty's docs and resources (not including Zig code) are linted using
|
||||
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
|
||||
check will fail builds with improper formatting. Therefore, if you are
|
||||
modifying anything Prettier will lint, you may want to install it locally and
|
||||
run this from the repo root before you commit:
|
||||
|
||||
```
|
||||
prettier --write .
|
||||
```
|
||||
|
||||
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
Nix users can use the following command to format with Prettier:
|
||||
|
||||
```
|
||||
nix develop -c prettier --write .
|
||||
```
|
||||
|
||||
#### Alejandra
|
||||
|
||||
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
|
||||
will fail builds with improper formatting.
|
||||
|
||||
Nix users can use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
nix develop -c alejandra .
|
||||
```
|
||||
|
||||
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
alejandra .
|
||||
```
|
||||
|
||||
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
#### Updating the Zig Cache Fixed-Output Derivation Hash
|
||||
|
||||
The Nix package depends on a [fixed-output
|
||||
derivation](https://nix.dev/manual/nix/stable/language/advanced-attributes.html#adv-attr-outputHash)
|
||||
that manages the Zig package cache. This allows the package to be built in the
|
||||
Nix sandbox.
|
||||
|
||||
Occasionally (usually when `build.zig.zon` is updated), the hash that
|
||||
identifies the cache will need to be updated. There are jobs that monitor the
|
||||
hash in CI, and builds will fail if it drifts.
|
||||
|
||||
To update it, you can run the following in the repository root:
|
||||
|
||||
```
|
||||
./nix/build-support/check-zig-cache-hash.sh --update
|
||||
```
|
||||
|
||||
This will write out the `nix/zigCacheHash.nix` file with the updated hash
|
||||
that can then be committed and pushed to fix the builds.
|
||||
|
@@ -20,8 +20,8 @@
|
||||
},
|
||||
.z2d = .{
|
||||
// vancluever/z2d
|
||||
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz",
|
||||
.hash = "z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5",
|
||||
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
.hash = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l",
|
||||
.lazy = true,
|
||||
},
|
||||
.zig_objc = .{
|
||||
@@ -112,8 +112,8 @@
|
||||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||
.hash = "N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls",
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
.hash = "N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
12
build.zig.zon.json
generated
12
build.zig.zon.json
generated
@@ -49,10 +49,10 @@
|
||||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||
},
|
||||
"N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls": {
|
||||
"N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||
"hash": "sha256-PySWF/9IAK4DZCkd5FRpiaIl6et2Qm6t8IKCTzh/Xa0="
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
"hash": "sha256-NlUXcBOmaA8W+7RXuXcn9TIhm964dXO2Op4QCQxhDyc="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
@@ -129,10 +129,10 @@
|
||||
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
|
||||
},
|
||||
"z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5": {
|
||||
"z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l": {
|
||||
"name": "z2d",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz",
|
||||
"hash": "sha256-6ZqgrO/bcjgnuQcfq89VYptUUodsErM8Fz6nwBZhTJs="
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
"hash": "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c="
|
||||
},
|
||||
"zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": {
|
||||
"name": "zf",
|
||||
|
13
build.zig.zon.nix
generated
13
build.zig.zon.nix
generated
@@ -49,6 +49,7 @@
|
||||
inherit name rev hash;
|
||||
url = url_without_query;
|
||||
deepClone = false;
|
||||
fetchSubmodules = false;
|
||||
};
|
||||
|
||||
fetchZigArtifact = {
|
||||
@@ -162,11 +163,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls";
|
||||
name = "N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz";
|
||||
hash = "sha256-PySWF/9IAK4DZCkd5FRpiaIl6et2Qm6t8IKCTzh/Xa0=";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz";
|
||||
hash = "sha256-NlUXcBOmaA8W+7RXuXcn9TIhm964dXO2Op4QCQxhDyc=";
|
||||
};
|
||||
}
|
||||
{
|
||||
@@ -290,11 +291,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5";
|
||||
name = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l";
|
||||
path = fetchZigArtifact {
|
||||
name = "z2d";
|
||||
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz";
|
||||
hash = "sha256-6ZqgrO/bcjgnuQcfq89VYptUUodsErM8Fz6nwBZhTJs=";
|
||||
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz";
|
||||
hash = "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
4
build.zig.zon.txt
generated
4
build.zig.zon.txt
generated
@@ -28,8 +28,8 @@ https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21a
|
||||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz
|
||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz
|
||||
|
25
flake.lock
generated
25
flake.lock
generated
@@ -47,6 +47,19 @@
|
||||
"url": "https://channels.nixos.org/nixos-25.05/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1755972213,
|
||||
"narHash": "sha256-VYK7aDAv8H1enXn1ECRHmGbeY6RqLnNwUJkOwloIsko=",
|
||||
"rev": "73e96df7cff5783f45e21342a75a1540c4eddce4",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixos/unstable-small/nixos-25.11pre850642.73e96df7cff5/nixexprs.tar.xz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://channels.nixos.org/nixos-unstable-small/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
@@ -102,22 +115,20 @@
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742104771,
|
||||
"narHash": "sha256-LhidlyEA9MP8jGe1rEnyjGFCzLLgCdDpYeWggibayr0=",
|
||||
"lastModified": 1756000480,
|
||||
"narHash": "sha256-fR5pdcjO0II5MNdCzqvyokyuFkmff7/FyBAjUS6sMfA=",
|
||||
"owner": "jcollie",
|
||||
"repo": "zon2nix",
|
||||
"rev": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
|
||||
"rev": "d9dc9ef1ab9ae45b5c9d80c6a747cc9968ee0c60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jcollie",
|
||||
"repo": "zon2nix",
|
||||
"rev": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
|
||||
"rev": "d9dc9ef1ab9ae45b5c9d80c6a747cc9968ee0c60",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
@@ -24,9 +24,12 @@
|
||||
};
|
||||
|
||||
zon2nix = {
|
||||
url = "github:jcollie/zon2nix?rev=56c159be489cc6c0e73c3930bd908ddc6fe89613";
|
||||
url = "github:jcollie/zon2nix?rev=d9dc9ef1ab9ae45b5c9d80c6a747cc9968ee0c60";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
# Don't override nixpkgs until Zig 0.15 is available in the Nix branch
|
||||
# we are using for "normal" builds.
|
||||
#
|
||||
# nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script checks if the flatpak/zig-packages.json 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:
|
||||
#
|
||||
# - flatpak/zig-packages.json
|
||||
#
|
||||
# All of these are auto-generated and should not be edited manually.
|
||||
|
||||
# Nothing in this script should fail.
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
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 " ./flatpak/build-support/check-zig-cache.sh --update"
|
||||
echo " git add flatpak/zig-packages.json"
|
||||
echo " git commit -m \"flatpak: update zig-packages.json\""
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Turn Nix's base64 hashes into regular hexadecimal form
|
||||
decode_hash() {
|
||||
input=$1
|
||||
input=${input#sha256-}
|
||||
echo "$input" | base64 -d | od -vAn -t x1 | tr -d ' \n'
|
||||
}
|
||||
|
||||
ROOT="$(realpath "$(dirname "$0")/../../")"
|
||||
ZIG_PACKAGES_JSON="$ROOT/flatpak/zig-packages.json"
|
||||
BUILD_ZIG_ZON_JSON="$ROOT/build.zig.zon.json"
|
||||
|
||||
if [ ! -f "${BUILD_ZIG_ZON_JSON}" ]; then
|
||||
echo -e "\nERROR: build.zig.zon2json-lock missing."
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "${ZIG_PACKAGES_JSON}" ]; then
|
||||
OLD_HASH=$(sha512sum "${ZIG_PACKAGES_JSON}" | awk '{print $1}')
|
||||
fi
|
||||
|
||||
while read -r url sha256 dest; do
|
||||
src_type=archive
|
||||
sha256=$(decode_hash "$sha256")
|
||||
git_commit=
|
||||
if [[ "$url" =~ ^git\+* ]]; then
|
||||
src_type=git
|
||||
sha256=
|
||||
url=${url#git+}
|
||||
git_commit=${url##*#}
|
||||
url=${url%%/\?ref*}
|
||||
url=${url%%#*}
|
||||
fi
|
||||
|
||||
jq \
|
||||
-nec \
|
||||
--arg type "$src_type" \
|
||||
--arg url "$url" \
|
||||
--arg git_commit "$git_commit" \
|
||||
--arg dest "$dest" \
|
||||
--arg sha256 "$sha256" \
|
||||
'{
|
||||
type: $type,
|
||||
url: $url,
|
||||
commit: $git_commit,
|
||||
dest: $dest,
|
||||
sha256: $sha256,
|
||||
} | with_entries(select(.value != ""))'
|
||||
done < <(jq -rc 'to_entries[] | [.value.url, .value.hash, "vendor/p/\(.key)"] | @tsv' "$BUILD_ZIG_ZON_JSON") |
|
||||
jq -s '.' >"$WORK_DIR/zig-packages.json"
|
||||
|
||||
NEW_HASH=$(sha512sum "$WORK_DIR/zig-packages.json" | awk '{print $1}')
|
||||
|
||||
if [ "${OLD_HASH}" == "${NEW_HASH}" ]; then
|
||||
echo -e "\nOK: flatpak/zig-packages.json unchanged."
|
||||
exit 0
|
||||
elif [ "${1:-}" != "--update" ]; then
|
||||
echo -e "\nERROR: flatpak/zig-packages.json needs to be updated."
|
||||
echo ""
|
||||
echo " * Old hash: ${OLD_HASH}"
|
||||
echo " * New hash: ${NEW_HASH}"
|
||||
help
|
||||
exit 1
|
||||
else
|
||||
mv "$WORK_DIR/zig-packages.json" "$ZIG_PACKAGES_JSON"
|
||||
echo -e "\nOK: flatpak/zig-packages.json updated."
|
||||
exit 0
|
||||
fi
|
@@ -61,9 +61,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/8b639f0c2605557bd23ba1b940842c67bbfd4ed0.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAAlgXwSghpDmXBXZM4Rpd80WKOXVWTrcL0ucVmls",
|
||||
"sha256": "3f249617ff4800ae0364291de4546989a225e9eb76426eadf082824f387f5dad"
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME",
|
||||
"sha256": "3655177013a6680f16fbb457b97727f532219bdeb87573b63a9e10090c610f27"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
@@ -157,9 +157,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.1.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.7.1-j5P_HhFQDQC6T5srG235r_nQpCrNwI15Gu2zqVrtUxN5",
|
||||
"sha256": "e99aa0acefdb723827b9071fabcf55629b5452876c12b33c173ea7c016614c9b"
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l",
|
||||
"sha256": "b56acb944394e3fd193a5ce018e5c1d3c7d6eeca9fc8015fdeb96fd3097d6ff7"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
@@ -419,6 +419,7 @@ typedef struct {
|
||||
ghostty_env_var_s* env_vars;
|
||||
size_t env_var_count;
|
||||
const char* initial_input;
|
||||
bool wait_after_command;
|
||||
} ghostty_surface_config_s;
|
||||
|
||||
typedef struct {
|
||||
@@ -450,6 +451,28 @@ typedef struct {
|
||||
ghostty_config_color_s colors[256];
|
||||
} ghostty_config_palette_s;
|
||||
|
||||
// config.QuickTerminalSize
|
||||
typedef enum {
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_NONE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS,
|
||||
} ghostty_quick_terminal_size_tag_e;
|
||||
|
||||
typedef union {
|
||||
float percentage;
|
||||
uint32_t pixels;
|
||||
} ghostty_quick_terminal_size_value_u;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_tag_e tag;
|
||||
ghostty_quick_terminal_size_value_u value;
|
||||
} ghostty_quick_terminal_size_s;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_s primary;
|
||||
ghostty_quick_terminal_size_s secondary;
|
||||
} ghostty_config_quick_terminal_size_s;
|
||||
|
||||
// apprt.Target.Key
|
||||
typedef enum {
|
||||
GHOSTTY_TARGET_APP,
|
||||
@@ -680,6 +703,12 @@ typedef struct {
|
||||
uintptr_t len;
|
||||
} ghostty_action_open_url_s;
|
||||
|
||||
// apprt.action.CloseTabMode
|
||||
typedef enum {
|
||||
GHOSTTY_ACTION_CLOSE_TAB_MODE_THIS,
|
||||
GHOSTTY_ACTION_CLOSE_TAB_MODE_OTHER,
|
||||
} ghostty_action_close_tab_mode_e;
|
||||
|
||||
// apprt.surface.Message.ChildExited
|
||||
typedef struct {
|
||||
uint32_t exit_code;
|
||||
@@ -693,15 +722,15 @@ typedef enum {
|
||||
GHOSTTY_PROGRESS_STATE_ERROR,
|
||||
GHOSTTY_PROGRESS_STATE_INDETERMINATE,
|
||||
GHOSTTY_PROGRESS_STATE_PAUSE,
|
||||
} ghostty_terminal_osc_command_progressreport_state_e;
|
||||
} ghostty_action_progress_report_state_e;
|
||||
|
||||
// terminal.osc.Command.ProgressReport.C
|
||||
typedef struct {
|
||||
ghostty_terminal_osc_command_progressreport_state_e state;
|
||||
ghostty_action_progress_report_state_e state;
|
||||
// -1 if no progress was reported, otherwise 0-100 indicating percent
|
||||
// completeness.
|
||||
int8_t progress;
|
||||
} ghostty_terminal_osc_command_progressreport_s;
|
||||
} ghostty_action_progress_report_s;
|
||||
|
||||
// apprt.Action.Key
|
||||
typedef enum {
|
||||
@@ -786,8 +815,9 @@ typedef union {
|
||||
ghostty_action_reload_config_s reload_config;
|
||||
ghostty_action_config_change_s config_change;
|
||||
ghostty_action_open_url_s open_url;
|
||||
ghostty_action_close_tab_mode_e close_tab_mode;
|
||||
ghostty_surface_message_childexited_s child_exited;
|
||||
ghostty_terminal_osc_command_progressreport_s progress_report;
|
||||
ghostty_action_progress_report_s progress_report;
|
||||
} ghostty_action_u;
|
||||
|
||||
typedef struct {
|
||||
@@ -934,7 +964,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t,
|
||||
double,
|
||||
ghostty_input_scroll_mods_t);
|
||||
void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*, double*, double*);
|
||||
void ghostty_surface_request_close(ghostty_surface_t);
|
||||
void ghostty_surface_split(ghostty_surface_t, ghostty_action_split_direction_e);
|
||||
void ghostty_surface_split_focus(ghostty_surface_t,
|
||||
|
@@ -104,6 +104,7 @@
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */; };
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */; };
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
||||
@@ -126,6 +127,7 @@
|
||||
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */; };
|
||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */; };
|
||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3C2B37804400D21823 /* CodableBridge.swift */; };
|
||||
A5D689BE2E654D98002E2346 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; };
|
||||
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; };
|
||||
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; };
|
||||
@@ -252,6 +254,7 @@
|
||||
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>"; };
|
||||
A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSize.swift; sourceTree = "<group>"; };
|
||||
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
||||
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
||||
@@ -637,6 +640,7 @@
|
||||
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */,
|
||||
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
||||
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */,
|
||||
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
|
||||
);
|
||||
path = QuickTerminal;
|
||||
@@ -939,6 +943,10 @@
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,
|
||||
@@ -980,6 +988,7 @@
|
||||
A5333E232B5A219A008AEFF7 /* SurfaceView.swift in Sources */,
|
||||
A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */,
|
||||
A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
||||
A5D689BE2E654D98002E2346 /* Ghostty.Action.swift in Sources */,
|
||||
A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */,
|
||||
A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */,
|
||||
A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */,
|
||||
|
@@ -6,8 +6,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
|
||||
"version" : "2.6.4"
|
||||
"revision" : "df074165274afaa39539c05d57b0832620775b11",
|
||||
"version" : "2.7.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@@ -394,35 +394,69 @@ class AppDelegate: NSObject,
|
||||
// Ghostty will validate as well but we can avoid creating an entirely new
|
||||
// surface by doing our own validation here. We can also show a useful error
|
||||
// this way.
|
||||
|
||||
|
||||
var isDirectory = ObjCBool(true)
|
||||
guard FileManager.default.fileExists(atPath: filename, isDirectory: &isDirectory) else { return false }
|
||||
|
||||
|
||||
// Set to true if confirmation is required before starting up the
|
||||
// new terminal.
|
||||
var requiresConfirm: Bool = false
|
||||
|
||||
// Initialize the surface config which will be used to create the tab or window for the opened file.
|
||||
var config = Ghostty.SurfaceConfiguration()
|
||||
|
||||
|
||||
if (isDirectory.boolValue) {
|
||||
// When opening a directory, check the configuration to decide
|
||||
// whether to open in a new tab or new window.
|
||||
config.workingDirectory = filename
|
||||
} else {
|
||||
// Unconditionally require confirmation in the file execution case.
|
||||
// In the future I have ideas about making this more fine-grained if
|
||||
// we can not inherit of unsandboxed state. For now, we need to confirm
|
||||
// because there is a sandbox escape possible if a sandboxed application
|
||||
// somehow is tricked into `open`-ing a non-sandboxed application.
|
||||
requiresConfirm = true
|
||||
|
||||
// When opening a file, we want to execute the file. To do this, we
|
||||
// don't override the command directly, because it won't load the
|
||||
// profile/rc files for the shell, which is super important on macOS
|
||||
// due to things like Homebrew. Instead, we set the command to
|
||||
// `<filename>; exit` which is what Terminal and iTerm2 do.
|
||||
config.initialInput = "\(filename); exit\n"
|
||||
|
||||
|
||||
// For commands executed directly, we want to ensure we wait after exit
|
||||
// because in most cases scripts don't block on exit and we don't want
|
||||
// the window to just flash closed once complete.
|
||||
config.waitAfterCommand = true
|
||||
|
||||
// Set the parent directory to our working directory so that relative
|
||||
// paths in scripts work.
|
||||
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
||||
}
|
||||
|
||||
if requiresConfirm {
|
||||
// Confirmation required. We use an app-wide NSAlert for now. In the future we
|
||||
// may want to show this as a sheet on the focused window (especially if we're
|
||||
// opening a tab). I'm not sure.
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Allow Ghostty to execute \"\(filename)\"?"
|
||||
alert.addButton(withTitle: "Allow")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.alertStyle = .warning
|
||||
switch (alert.runModal()) {
|
||||
case .alertFirstButtonReturn:
|
||||
break
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch ghostty.config.macosDockDropBehavior {
|
||||
case .new_tab: _ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||
case .new_window: _ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -957,18 +991,10 @@ class AppDelegate: NSObject,
|
||||
|
||||
@IBAction func newWindow(_ sender: Any?) {
|
||||
_ = TerminalController.newWindow(ghostty)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBAction func newTab(_ sender: Any?) {
|
||||
_ = TerminalController.newTab(ghostty)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBAction func closeAllWindows(_ sender: Any?) {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
@@ -22,7 +22,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
private var previousActiveSpace: CGSSpace? = nil
|
||||
|
||||
/// The window frame saved when the quick terminal's surface tree becomes empty.
|
||||
///
|
||||
///
|
||||
/// This preserves the user's window size and position when all terminal surfaces
|
||||
/// are closed (e.g., via the `exit` command). When a new surface is created,
|
||||
/// the window will be restored to this frame, preventing SwiftUI from resetting
|
||||
@@ -34,6 +34,9 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||
private var derivedConfig: DerivedConfig
|
||||
|
||||
/// Tracks if we're currently handling a manual resize to prevent recursion
|
||||
private var isHandlingResize: Bool = false
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
position: QuickTerminalPosition = .top,
|
||||
@@ -76,6 +79,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
selector: #selector(onNewTab),
|
||||
name: Ghostty.Notification.ghosttyNewTab,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(windowDidResize(_:)),
|
||||
name: NSWindow.didResizeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -109,14 +117,29 @@ class QuickTerminalController: BaseTerminalController {
|
||||
syncAppearance()
|
||||
|
||||
// Setup our initial size based on our configured position
|
||||
position.setLoaded(window)
|
||||
position.setLoaded(window, size: derivedConfig.quickTerminalSize)
|
||||
|
||||
// Upon first adding this Window to its host view, older SwiftUI
|
||||
// seems to have a "hiccup" and corrupts the frameRect,
|
||||
// sometimes setting the size to zero, sometimes corrupting it.
|
||||
// We pass the actual window's frame as "initial" frame directly
|
||||
// to the window, so it can use that instead of the frameworks
|
||||
// "interpretation"
|
||||
if let qtWindow = window as? QuickTerminalWindow {
|
||||
qtWindow.initialFrame = window.frame
|
||||
}
|
||||
|
||||
// Setup our content
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
ghostty: self.ghostty,
|
||||
viewModel: self,
|
||||
delegate: self
|
||||
))
|
||||
|
||||
// Clear out our frame at this point, the fixup from above is complete.
|
||||
if let qtWindow = window as? QuickTerminalWindow {
|
||||
qtWindow.initialFrame = nil
|
||||
}
|
||||
|
||||
// Animate the window in
|
||||
animateIn()
|
||||
@@ -194,11 +217,28 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
}
|
||||
|
||||
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
|
||||
// We use the actual screen the window is on for this, since it should
|
||||
// be on the proper screen.
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return frameSize }
|
||||
return position.restrictFrameSize(frameSize, on: screen)
|
||||
override func windowDidResize(_ notification: Notification) {
|
||||
guard let window = notification.object as? NSWindow,
|
||||
window == self.window,
|
||||
visible,
|
||||
!isHandlingResize else { return }
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
// Prevent recursive loops
|
||||
isHandlingResize = true
|
||||
defer { isHandlingResize = false }
|
||||
|
||||
switch position {
|
||||
case .top, .bottom, .center:
|
||||
// For centered positions (top, bottom, center), we need to recenter the window
|
||||
// when it's manually resized to maintain proper positioning
|
||||
let newOrigin = position.centeredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
case .left, .right:
|
||||
// For side positions, we may need to adjust vertical centering
|
||||
let newOrigin = position.verticallyCenteredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Base Controller Overrides
|
||||
@@ -318,15 +358,17 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) {
|
||||
guard let screen = derivedConfig.quickTerminalScreen.screen else { return }
|
||||
|
||||
// Grab our last closed frame to use, and clear our state since we're animating in.
|
||||
let lastClosedFrame = self.lastClosedFrame
|
||||
self.lastClosedFrame = nil
|
||||
|
||||
// Restore our previous frame if we have one
|
||||
if let lastClosedFrame {
|
||||
window.setFrame(lastClosedFrame, display: false)
|
||||
self.lastClosedFrame = nil
|
||||
}
|
||||
|
||||
// Move our window off screen to the top
|
||||
position.setInitial(in: window, on: screen)
|
||||
// Move our window off screen to the initial animation position.
|
||||
position.setInitial(
|
||||
in: window,
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: lastClosedFrame)
|
||||
|
||||
// We need to set our window level to a high value. In testing, only
|
||||
// popUpMenu and above do what we want. This gets it above the menu bar
|
||||
@@ -357,7 +399,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setFinal(in: window.animator(), on: screen)
|
||||
position.setFinal(
|
||||
in: window.animator(),
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: lastClosedFrame)
|
||||
}, completionHandler: {
|
||||
// There is a very minor delay here so waiting at least an event loop tick
|
||||
// keeps us safe from the view not being on the window.
|
||||
@@ -435,11 +481,19 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
|
||||
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
|
||||
// If we are in fullscreen, then we exit fullscreen. We do this immediately so
|
||||
// we have th correct window.frame for the save state below.
|
||||
if let fullscreenStyle, fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
}
|
||||
|
||||
// Save the current window frame before animating out. This preserves
|
||||
// the user's preferred window size and position for when the quick
|
||||
// terminal is reactivated with a new surface. Without this, SwiftUI
|
||||
// would reset the window to its minimum content size.
|
||||
lastClosedFrame = window.frame
|
||||
if window.frame.width > 0 && window.frame.height > 0 {
|
||||
lastClosedFrame = window.frame
|
||||
}
|
||||
|
||||
// If we hid the dock then we unhide it.
|
||||
hiddenDock = nil
|
||||
@@ -455,11 +509,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
// We always animate out to whatever screen the window is actually on.
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
// If we are in fullscreen, then we exit fullscreen.
|
||||
if let fullscreenStyle, fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
}
|
||||
|
||||
// If we have a previously active application, restore focus to it. We
|
||||
// do this BEFORE the animation below because when the animation completes
|
||||
// macOS will bring forward another window.
|
||||
@@ -481,7 +530,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setInitial(in: window.animator(), on: screen)
|
||||
position.setInitial(
|
||||
in: window.animator(),
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: window.frame)
|
||||
}, completionHandler: {
|
||||
// This causes the window to be removed from the screen list and macOS
|
||||
// handles what should be focused next.
|
||||
@@ -612,6 +665,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
let quickTerminalAnimationDuration: Double
|
||||
let quickTerminalAutoHide: Bool
|
||||
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
|
||||
let quickTerminalSize: QuickTerminalSize
|
||||
let backgroundOpacity: Double
|
||||
|
||||
init() {
|
||||
@@ -619,6 +673,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = 0.2
|
||||
self.quickTerminalAutoHide = true
|
||||
self.quickTerminalSpaceBehavior = .move
|
||||
self.quickTerminalSize = QuickTerminalSize()
|
||||
self.backgroundOpacity = 1.0
|
||||
}
|
||||
|
||||
@@ -627,6 +682,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
|
||||
self.quickTerminalSize = config.quickTerminalSize
|
||||
self.backgroundOpacity = config.backgroundOpacity
|
||||
}
|
||||
}
|
||||
|
@@ -7,95 +7,86 @@ enum QuickTerminalPosition : String {
|
||||
case right
|
||||
case center
|
||||
|
||||
/// Set the loaded state for a window.
|
||||
func setLoaded(_ window: NSWindow) {
|
||||
/// Set the loaded state for a window. This should only be called when the window is first loaded,
|
||||
/// usually in `windowDidLoad` or in a similar callback. This is the initial state.
|
||||
func setLoaded(_ window: NSWindow, size: QuickTerminalSize) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: screen.frame.height / 4)
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 4,
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 2,
|
||||
height: screen.frame.height / 3)
|
||||
), display: false)
|
||||
}
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: size.calculate(position: self, screenDimensions: screen.visibleFrame.size)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always start invisible
|
||||
/// Set the initial state for a window NOT yet into position (either before animating in or
|
||||
/// after animating out).
|
||||
func setInitial(
|
||||
in window: NSWindow,
|
||||
on screen: NSScreen,
|
||||
terminalSize: QuickTerminalSize,
|
||||
closedFrame: NSRect? = nil
|
||||
) {
|
||||
// Invisible
|
||||
window.alphaValue = 0
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: initialOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: closedFrame?.size ?? configuredFrameSize(
|
||||
on: screen,
|
||||
terminalSize: terminalSize)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the final state for a window in this position.
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen) {
|
||||
func setFinal(
|
||||
in window: NSWindow,
|
||||
on screen: NSScreen,
|
||||
terminalSize: QuickTerminalSize,
|
||||
closedFrame: NSRect? = nil
|
||||
) {
|
||||
// We always end visible
|
||||
window.alphaValue = 1
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: finalOrigin(for: window, on: screen),
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
size: closedFrame?.size ?? configuredFrameSize(
|
||||
on: screen,
|
||||
terminalSize: terminalSize)
|
||||
), display: true)
|
||||
}
|
||||
|
||||
/// Restrict the frame size during resizing.
|
||||
func restrictFrameSize(_ size: NSSize, on screen: NSScreen) -> NSSize {
|
||||
var finalSize = size
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
finalSize.width = screen.frame.width
|
||||
|
||||
case .left, .right:
|
||||
finalSize.height = screen.visibleFrame.height
|
||||
|
||||
case .center:
|
||||
finalSize.width = screen.frame.width / 2
|
||||
finalSize.height = screen.frame.height / 3
|
||||
}
|
||||
|
||||
return finalSize
|
||||
/// Get the configured frame size for initial positioning and animations.
|
||||
func configuredFrameSize(on screen: NSScreen, terminalSize: QuickTerminalSize) -> NSSize {
|
||||
let dimensions = terminalSize.calculate(position: self, screenDimensions: screen.visibleFrame.size)
|
||||
return NSSize(width: dimensions.width, height: dimensions.height)
|
||||
}
|
||||
|
||||
/// The initial point origin for this position.
|
||||
func initialOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.maxY)
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.maxY)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: -window.frame.height)
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: -window.frame.height)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: 0)
|
||||
return .init(
|
||||
x: screen.visibleFrame.minX-window.frame.width,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.frame.maxX, y: 0)
|
||||
return .init(
|
||||
x: screen.visibleFrame.maxX,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.height - window.frame.width)
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: screen.visibleFrame.height - window.frame.width)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +94,27 @@ enum QuickTerminalPosition : String {
|
||||
func finalOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(x: screen.frame.minX, y: screen.visibleFrame.maxY - window.frame.height)
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.maxY - window.frame.height)
|
||||
|
||||
case .bottom:
|
||||
return .init(x: screen.frame.minX, y: screen.frame.minY)
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.minY)
|
||||
|
||||
case .left:
|
||||
return .init(x: screen.frame.minX, y: window.frame.origin.y)
|
||||
return .init(
|
||||
x: screen.visibleFrame.minX,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
|
||||
case .right:
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: window.frame.origin.y)
|
||||
return .init(
|
||||
x: screen.visibleFrame.maxX - window.frame.width,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
|
||||
case .center:
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,4 +135,52 @@ enum QuickTerminalPosition : String {
|
||||
case .right: self == .top || self == .bottom
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the centered origin for a window, keeping it properly positioned after manual resizing
|
||||
func centeredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .top:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .bottom:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .center:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .left, .right:
|
||||
// For left/right positions, only adjust horizontal centering if needed
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the vertically centered origin for side-positioned windows
|
||||
func verticallyCenteredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .left:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .right:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .top, .bottom, .center:
|
||||
// These positions don't need vertical recentering during resize
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
84
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
84
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
import GhosttyKit
|
||||
|
||||
/// Represents the Ghostty `quick-terminal-size` configuration. See the documentation for
|
||||
/// that for more details on exactly how it works. Some of those docs will be reproduced in various comments
|
||||
/// in this file but that is the best source of truth for it.
|
||||
///
|
||||
/// The size determines the size of the quick terminal along the primary and secondary axis. The primary and
|
||||
/// secondary axis is defined by the `quick-terminal-position`.
|
||||
struct QuickTerminalSize {
|
||||
let primary: Size?
|
||||
let secondary: Size?
|
||||
|
||||
init(primary: Size? = nil, secondary: Size? = nil) {
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
}
|
||||
|
||||
init(from cStruct: ghostty_config_quick_terminal_size_s) {
|
||||
self.primary = Size(from: cStruct.primary)
|
||||
self.secondary = Size(from: cStruct.secondary)
|
||||
}
|
||||
|
||||
enum Size {
|
||||
case percentage(Float)
|
||||
case pixels(UInt32)
|
||||
|
||||
init?(from cStruct: ghostty_quick_terminal_size_s) {
|
||||
switch cStruct.tag {
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_NONE:
|
||||
return nil
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE:
|
||||
self = .percentage(cStruct.value.percentage)
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS:
|
||||
self = .pixels(cStruct.value.pixels)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPixels(parentDimension: CGFloat) -> CGFloat {
|
||||
switch self {
|
||||
case .percentage(let value):
|
||||
return parentDimension * CGFloat(value) / 100.0
|
||||
case .pixels(let value):
|
||||
return CGFloat(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This is an almost direct port of th Zig function QuickTerminalSize.calculate
|
||||
func calculate(position: QuickTerminalPosition, screenDimensions: CGSize) -> CGSize {
|
||||
let dims = CGSize(width: screenDimensions.width, height: screenDimensions.height)
|
||||
|
||||
switch position {
|
||||
case .left, .right:
|
||||
return CGSize(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? dims.height
|
||||
)
|
||||
|
||||
case .top, .bottom:
|
||||
return CGSize(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? dims.width,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
|
||||
case .center:
|
||||
if dims.width >= dims.height {
|
||||
// Landscape
|
||||
return CGSize(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 800,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
} else {
|
||||
// Portrait
|
||||
return CGSize(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 800
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,4 +29,19 @@ class QuickTerminalWindow: NSPanel {
|
||||
// We don't want to activate the owning app when quick terminal is triggered.
|
||||
self.styleMask.insert(.nonactivatingPanel)
|
||||
}
|
||||
|
||||
/// This is set to the frame prior to setting `contentView`. This is purely a hack to workaround
|
||||
/// bugs in older macOS versions (Ventura): https://github.com/ghostty-org/ghostty/pull/8026
|
||||
var initialFrame: NSRect? = nil
|
||||
|
||||
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
|
||||
// Upon first adding this Window to its host view, older SwiftUI
|
||||
// seems to have a "hiccup" and corrupts the frameRect,
|
||||
// sometimes setting the size to zero, sometimes corrupting it.
|
||||
// If we find we have cached the "initial" frame, use that instead
|
||||
// the propagated one through the framework
|
||||
//
|
||||
// https://github.com/ghostty-org/ghostty/pull/8026
|
||||
super.setFrame(initialFrame ?? frameRect, display: flag)
|
||||
}
|
||||
}
|
||||
|
@@ -290,8 +290,12 @@ class BaseTerminalController: NSWindowController,
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.alertStyle = .warning
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
let alertWindow = alert.window
|
||||
self.alert = nil
|
||||
if response == .alertFirstButtonReturn {
|
||||
// This is important so that we avoid losing focus when Stage
|
||||
// Manager is used (#8336)
|
||||
alertWindow.orderOut(nil)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
@@ -95,6 +95,11 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
selector: #selector(onCloseTab),
|
||||
name: .ghosttyCloseTab,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(onCloseOtherTabs),
|
||||
name: .ghosttyCloseOtherTabs,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(onResetWindowSize),
|
||||
@@ -196,7 +201,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
if let parent {
|
||||
if parent.styleMask.contains(.fullScreen) {
|
||||
parent.toggleFullScreen(nil)
|
||||
// If our previous window was fullscreen then we want our new window to
|
||||
// be fullscreen. This behavior actually doesn't match the native tabbing
|
||||
// behavior of macOS apps where new windows create tabs when in native
|
||||
// fullscreen but this is how we've always done it. This matches iTerm2
|
||||
// behavior.
|
||||
c.toggleFullscreen(mode: .native)
|
||||
} else if ghostty.config.windowFullscreen {
|
||||
switch (ghostty.config.windowFullscreenMode) {
|
||||
case .native:
|
||||
@@ -226,6 +236,10 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
}
|
||||
|
||||
c.showWindow(self)
|
||||
|
||||
// All new_window actions force our app to be active, so that the new
|
||||
// window is focused and visible.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
// Setup our undo
|
||||
@@ -332,6 +346,10 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
controller.showWindow(self)
|
||||
window.makeKeyAndOrderFront(self)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
// It takes an event loop cycle until the macOS tabGroup state becomes
|
||||
@@ -421,8 +439,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
continue
|
||||
}
|
||||
|
||||
let action = "goto_tab:\(tab)"
|
||||
if let equiv = ghostty.config.keyboardShortcut(for: action) {
|
||||
if let equiv = ghostty.config.keyboardShortcut(for: "goto_tab:\(tab)") {
|
||||
window.keyEquivalent = "\(equiv)"
|
||||
} else {
|
||||
window.keyEquivalent = ""
|
||||
@@ -546,7 +563,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
closeWindow(nil)
|
||||
}
|
||||
|
||||
private func closeTabImmediately() {
|
||||
private func closeTabImmediately(registerRedo: Bool = true) {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup,
|
||||
tabGroup.windows.count > 1 else {
|
||||
@@ -563,19 +580,69 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
expiresAfter: undoExpiration
|
||||
) { ghostty in
|
||||
let newController = TerminalController(ghostty, with: undoState)
|
||||
|
||||
// Register redo action
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration
|
||||
) { target in
|
||||
target.closeTabImmediately()
|
||||
|
||||
if registerRedo {
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration
|
||||
) { target in
|
||||
target.closeTabImmediately()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.close()
|
||||
}
|
||||
|
||||
private func closeOtherTabsImmediately() {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup else { return }
|
||||
guard tabGroup.windows.count > 1 else { return }
|
||||
|
||||
// Start an undo grouping
|
||||
if let undoManager {
|
||||
undoManager.beginUndoGrouping()
|
||||
}
|
||||
defer {
|
||||
undoManager?.endUndoGrouping()
|
||||
}
|
||||
|
||||
// Iterate through all tabs except the current one.
|
||||
for window in tabGroup.windows where window != self.window {
|
||||
// We ignore any non-terminal tabs. They don't currently exist and we can't
|
||||
// properly undo them anyways so I'd rather ignore them and get a bug report
|
||||
// later if and when we introduce non-terminal tabs.
|
||||
if let controller = window.windowController as? TerminalController {
|
||||
// We must not register a redo, because it messes with our own redo
|
||||
// that we register later.
|
||||
controller.closeTabImmediately(registerRedo: false)
|
||||
}
|
||||
}
|
||||
|
||||
if let undoManager {
|
||||
undoManager.setActionName("Close Other Tabs")
|
||||
|
||||
// We need to register an undo that refocuses this window. Otherwise, the
|
||||
// undo operation above for each tab will steal focus.
|
||||
undoManager.registerUndo(
|
||||
withTarget: self,
|
||||
expiresAfter: undoExpiration
|
||||
) { target in
|
||||
DispatchQueue.main.async {
|
||||
target.window?.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
// Register redo action
|
||||
undoManager.registerUndo(
|
||||
withTarget: target,
|
||||
expiresAfter: target.undoExpiration
|
||||
) { target in
|
||||
target.closeOtherTabsImmediately()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the current window (including any other tabs) immediately and without
|
||||
/// confirmation. This will setup proper undo state so the action can be undone.
|
||||
@@ -739,6 +806,9 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
alert.alertStyle = .warning
|
||||
alert.beginSheetModal(for: alertWindow, completionHandler: { response in
|
||||
if (response == .alertFirstButtonReturn) {
|
||||
// This is important so that we avoid losing focus when Stage
|
||||
// Manager is used (#8336)
|
||||
alert.window.orderOut(nil)
|
||||
closeAllWindowsImmediately()
|
||||
}
|
||||
})
|
||||
@@ -1007,6 +1077,38 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func closeOtherTabs(_ sender: Any?) {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup else { return }
|
||||
|
||||
// If we only have one window then we have no other tabs to close
|
||||
guard tabGroup.windows.count > 1 else { return }
|
||||
|
||||
// Check if we have to confirm close.
|
||||
guard tabGroup.windows.contains(where: { window in
|
||||
// Ignore ourself
|
||||
if window == self.window { return false }
|
||||
|
||||
// Ignore non-terminals
|
||||
guard let controller = window.windowController as? TerminalController else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if any surfaces require confirmation
|
||||
return controller.surfaceTree.contains(where: { $0.needsConfirmQuit })
|
||||
}) else {
|
||||
self.closeOtherTabsImmediately()
|
||||
return
|
||||
}
|
||||
|
||||
confirmClose(
|
||||
messageText: "Close Other Tabs?",
|
||||
informativeText: "At least one other tab still has a running process. If you close the tab the process will be killed."
|
||||
) {
|
||||
self.closeOtherTabsImmediately()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func returnToDefaultSize(_ sender: Any?) {
|
||||
guard let defaultSize else { return }
|
||||
window?.setFrame(defaultSize, display: true)
|
||||
@@ -1190,6 +1292,12 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
closeTab(self)
|
||||
}
|
||||
|
||||
@objc private func onCloseOtherTabs(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard surfaceTree.contains(target) else { return }
|
||||
closeOtherTabs(self)
|
||||
}
|
||||
|
||||
@objc private func onCloseWindow(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard surfaceTree.contains(target) else { return }
|
||||
|
@@ -70,4 +70,39 @@ extension Ghostty.Action {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProgressReport {
|
||||
enum State {
|
||||
case remove
|
||||
case set
|
||||
case error
|
||||
case indeterminate
|
||||
case pause
|
||||
|
||||
init(_ c: ghostty_action_progress_report_state_e) {
|
||||
switch c {
|
||||
case GHOSTTY_PROGRESS_STATE_REMOVE:
|
||||
self = .remove
|
||||
case GHOSTTY_PROGRESS_STATE_SET:
|
||||
self = .set
|
||||
case GHOSTTY_PROGRESS_STATE_ERROR:
|
||||
self = .error
|
||||
case GHOSTTY_PROGRESS_STATE_INDETERMINATE:
|
||||
self = .indeterminate
|
||||
case GHOSTTY_PROGRESS_STATE_PAUSE:
|
||||
self = .pause
|
||||
default:
|
||||
self = .remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state: State
|
||||
let progress: UInt8?
|
||||
|
||||
init(c: ghostty_action_progress_report_s) {
|
||||
self.state = State(c.state)
|
||||
self.progress = c.progress >= 0 ? UInt8(c.progress) : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -455,7 +455,7 @@ extension Ghostty {
|
||||
newSplit(app, target: target, direction: action.action.new_split)
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_TAB:
|
||||
closeTab(app, target: target)
|
||||
closeTab(app, target: target, mode: action.action.close_tab_mode)
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_WINDOW:
|
||||
closeWindow(app, target: target)
|
||||
@@ -543,6 +543,9 @@ extension Ghostty {
|
||||
|
||||
case GHOSTTY_ACTION_KEY_SEQUENCE:
|
||||
keySequence(app, target: target, v: action.action.key_sequence)
|
||||
|
||||
case GHOSTTY_ACTION_PROGRESS_REPORT:
|
||||
progressReport(app, target: target, v: action.action.progress_report)
|
||||
|
||||
case GHOSTTY_ACTION_CONFIG_CHANGE:
|
||||
configChange(app, target: target, v: action.action.config_change)
|
||||
@@ -778,20 +781,34 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s, mode: ghostty_action_close_tab_mode_e) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("close tab does nothing with an app target")
|
||||
Ghostty.logger.warning("close tabs 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: .ghosttyCloseTab,
|
||||
object: surfaceView
|
||||
)
|
||||
switch (mode) {
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_THIS:
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyCloseTab,
|
||||
object: surfaceView
|
||||
)
|
||||
return
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_OTHER:
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyCloseOtherTabs,
|
||||
object: surfaceView
|
||||
)
|
||||
return
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
@@ -1509,6 +1526,33 @@ extension Ghostty {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func progressReport(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_progress_report_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("progress report 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 }
|
||||
|
||||
let progressReport = Ghostty.Action.ProgressReport(c: v)
|
||||
DispatchQueue.main.async {
|
||||
if progressReport.state == .remove {
|
||||
surfaceView.progressReport = nil
|
||||
} else {
|
||||
surfaceView.progressReport = progressReport
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func configReload(
|
||||
_ app: ghostty_app_t,
|
||||
|
@@ -504,6 +504,14 @@ extension Ghostty {
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||
}
|
||||
|
||||
var quickTerminalSize: QuickTerminalSize {
|
||||
guard let config = self.config else { return QuickTerminalSize() }
|
||||
var v = ghostty_config_quick_terminal_size_s()
|
||||
let key = "quick-terminal-size"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return QuickTerminalSize() }
|
||||
return QuickTerminalSize(from: v)
|
||||
}
|
||||
#endif
|
||||
|
||||
var resizeOverlay: ResizeOverlay {
|
||||
|
@@ -329,6 +329,9 @@ extension Notification.Name {
|
||||
/// Close tab
|
||||
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
|
||||
|
||||
/// Close other tabs
|
||||
static let ghosttyCloseOtherTabs = Notification.Name("com.mitchellh.ghostty.closeOtherTabs")
|
||||
|
||||
/// Close window
|
||||
static let ghosttyCloseWindow = Notification.Name("com.mitchellh.ghostty.closeWindow")
|
||||
|
||||
|
@@ -113,6 +113,11 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
.ghosttySurfaceView(surfaceView)
|
||||
|
||||
// Progress report overlay
|
||||
if let progressReport = surfaceView.progressReport {
|
||||
ProgressReportOverlay(report: progressReport)
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
// If we are in the middle of a key sequence, then we show a visual element. We only
|
||||
@@ -267,6 +272,49 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
// Progress report overlay that shows a progress bar at the top of the terminal
|
||||
struct ProgressReportOverlay: View {
|
||||
let report: Action.ProgressReport
|
||||
|
||||
@ViewBuilder
|
||||
private var progressBar: some View {
|
||||
if let progress = report.progress {
|
||||
// Determinate progress bar
|
||||
ProgressView(value: Double(progress), total: 100)
|
||||
.progressViewStyle(.linear)
|
||||
.tint(report.state == .error ? .red : report.state == .pause ? .orange : nil)
|
||||
.animation(.easeInOut(duration: 0.2), value: progress)
|
||||
} else {
|
||||
// Indeterminate states
|
||||
switch report.state {
|
||||
case .indeterminate:
|
||||
ProgressView()
|
||||
.progressViewStyle(.linear)
|
||||
case .error:
|
||||
ProgressView()
|
||||
.progressViewStyle(.linear)
|
||||
.tint(.red)
|
||||
case .pause:
|
||||
Rectangle().fill(Color.orange)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
progressBar
|
||||
.scaleEffect(x: 1, y: 0.5, anchor: .center)
|
||||
.frame(height: 2)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the resize overlay that shows on top of a surface to show the current
|
||||
// size during a resize operation.
|
||||
struct SurfaceResizeOverlay: View {
|
||||
@@ -424,6 +472,9 @@ extension Ghostty {
|
||||
|
||||
/// Extra input to send as stdin
|
||||
var initialInput: String? = nil
|
||||
|
||||
/// Wait after the command
|
||||
var waitAfterCommand: Bool = false
|
||||
|
||||
init() {}
|
||||
|
||||
@@ -475,6 +526,9 @@ extension Ghostty {
|
||||
|
||||
// Zero is our default value that means to inherit the font size.
|
||||
config.font_size = fontSize ?? 0
|
||||
|
||||
// Set wait after command
|
||||
config.wait_after_command = waitAfterCommand
|
||||
|
||||
// Use withCString to ensure strings remain valid for the duration of the closure
|
||||
return try workingDirectory.withCString { cWorkingDir in
|
||||
|
@@ -41,6 +41,23 @@ extension Ghostty {
|
||||
|
||||
// The hovered URL string
|
||||
@Published var hoverUrl: String? = nil
|
||||
|
||||
// The progress report (if any)
|
||||
@Published var progressReport: Action.ProgressReport? = nil {
|
||||
didSet {
|
||||
// Cancel any existing timer
|
||||
progressReportTimer?.invalidate()
|
||||
progressReportTimer = nil
|
||||
|
||||
// If we have a new progress report, start a timer to remove it after 15 seconds
|
||||
if progressReport != nil {
|
||||
progressReportTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
|
||||
self?.progressReport = nil
|
||||
self?.progressReportTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The currently active key sequence. The sequence is not active if this is empty.
|
||||
@Published var keySequence: [KeyboardShortcut] = []
|
||||
@@ -142,6 +159,9 @@ extension Ghostty {
|
||||
|
||||
// A timer to fallback to ghost emoji if no title is set within the grace period
|
||||
private var titleFallbackTimer: Timer?
|
||||
|
||||
// Timer to remove progress report after 15 seconds
|
||||
private var progressReportTimer: 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
|
||||
@@ -348,6 +368,9 @@ extension Ghostty {
|
||||
// Remove any notifications associated with this surface
|
||||
let identifiers = Array(self.notificationIdentifiers)
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
// Cancel progress report timer
|
||||
progressReportTimer?.invalidate()
|
||||
}
|
||||
|
||||
func focusDidChange(_ focused: Bool) {
|
||||
@@ -1241,7 +1264,7 @@ extension Ghostty {
|
||||
|
||||
var key_ev = event.ghosttyKeyEvent(action, translationMods: translationEvent?.modifierFlags)
|
||||
key_ev.composing = composing
|
||||
|
||||
|
||||
// For text, we only encode UTF8 if we don't have a single control
|
||||
// character. Control characters are encoded by Ghostty itself.
|
||||
// Without this, `ctrl+enter` does the wrong thing.
|
||||
@@ -1660,8 +1683,10 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
}
|
||||
|
||||
// Ghostty will tell us where it thinks an IME keyboard should render.
|
||||
var x: Double = 0;
|
||||
var y: Double = 0;
|
||||
var x: Double = 0
|
||||
var y: Double = 0
|
||||
var width: Double = cellSize.width
|
||||
var height: Double = cellSize.height
|
||||
|
||||
// QuickLook never gives us a matching range to our selection so if we detect
|
||||
// this then we return the top-left selection point rather than the cursor point.
|
||||
@@ -1679,15 +1704,19 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
// Free our text
|
||||
ghostty_surface_free_text(surface, &text)
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
}
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
}
|
||||
|
||||
// Ghostty coordinates are in top-left (0, 0) so we have to convert to
|
||||
// bottom-left since that is what UIKit expects
|
||||
let viewRect = NSMakeRect(x, frame.size.height - y, 0, 0)
|
||||
let viewRect = NSMakeRect(
|
||||
x,
|
||||
frame.size.height - y,
|
||||
max(width, cellSize.width),
|
||||
max(height, cellSize.height))
|
||||
|
||||
// Convert the point to the window coordinates
|
||||
let winRect = self.convert(viewRect, to: nil)
|
||||
|
@@ -30,6 +30,9 @@ extension Ghostty {
|
||||
|
||||
// The hovered URL
|
||||
@Published var hoverUrl: String? = nil
|
||||
|
||||
// The progress report (if any)
|
||||
@Published var progressReport: Action.ProgressReport? = nil
|
||||
|
||||
// The time this surface last became focused. This is a ContinuousClock.Instant
|
||||
// on supported platforms.
|
||||
|
@@ -407,8 +407,14 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||
self.styleMask = window.styleMask
|
||||
self.toolbar = window.toolbar
|
||||
self.toolbarStyle = window.toolbarStyle
|
||||
self.titlebarAccessoryViewControllers = window.titlebarAccessoryViewControllers
|
||||
self.dock = window.screen?.hasDock ?? false
|
||||
|
||||
self.titlebarAccessoryViewControllers = if (window.hasTitleBar) {
|
||||
// Accessing titlebarAccessoryViewControllers without a titlebar triggers a crash.
|
||||
window.titlebarAccessoryViewControllers
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
|
||||
if let cgWindowId = window.cgWindowId {
|
||||
// We hide the menu only if this window is not on any fullscreen
|
||||
|
@@ -9,6 +9,7 @@
|
||||
# - build.zig.zon.nix
|
||||
# - build.zig.zon.txt
|
||||
# - build.zig.zon.json
|
||||
# - flatpak/zig-packages.json
|
||||
#
|
||||
# All of these are auto-generated and should not be edited manually.
|
||||
|
||||
@@ -34,8 +35,8 @@ help() {
|
||||
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 " git add build.zig.zon.nix build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json"
|
||||
echo " git commit -m \"nix: update build.zig.zon.nix build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json\""
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -44,6 +45,7 @@ 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"
|
||||
ZIG_PACKAGES_JSON="$ROOT/flatpak/zig-packages.json"
|
||||
|
||||
if [ -f "${BUILD_ZIG_ZON_NIX}" ]; then
|
||||
OLD_HASH_NIX=$(sha512sum "${BUILD_ZIG_ZON_NIX}" | awk '{print $1}')
|
||||
@@ -69,27 +71,40 @@ elif [ "$1" != "--update" ]; then
|
||||
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"
|
||||
if [ -f "${ZIG_PACKAGES_JSON}" ]; then
|
||||
OLD_HASH_FLATPAK=$(sha512sum "${ZIG_PACKAGES_JSON}" | awk '{print $1}')
|
||||
elif [ "$1" != "--update" ]; then
|
||||
echo -e "\nERROR: flatpak/zig-packages.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" --flatpak "$WORK_DIR/zig-packages.json"
|
||||
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
|
||||
prettier --write "$WORK_DIR/build.zig.zon.json"
|
||||
prettier --log-level warn --write "$WORK_DIR/build.zig.zon.json"
|
||||
prettier --log-level warn --write "$WORK_DIR/zig-packages.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}')
|
||||
NEW_HASH_FLATPAK=$(sha512sum "$WORK_DIR/zig-packages.json" | awk '{print $1}')
|
||||
|
||||
if [ "${OLD_HASH_NIX}" == "${NEW_HASH_NIX}" ] && [ "${OLD_HASH_TXT}" == "${NEW_HASH_TXT}" ] && [ "${OLD_HASH_JSON}" == "${NEW_HASH_JSON}" ]; then
|
||||
if [ "${OLD_HASH_NIX}" == "${NEW_HASH_NIX}" ] && [ "${OLD_HASH_TXT}" == "${NEW_HASH_TXT}" ] && [ "${OLD_HASH_JSON}" == "${NEW_HASH_JSON}" ] && [ "${OLD_HASH_FLATPAK}" == "${NEW_HASH_FLATPAK}" ]; 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."
|
||||
echo -e "OK: flatpak/zig-packages.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}"
|
||||
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}"
|
||||
echo " * Old flatpak/zig-packages.json hash: ${OLD_HASH_FLATPAK}"
|
||||
echo " * New flatpak/zig-packages.json hash: ${NEW_HASH_FLATPAK}"
|
||||
help
|
||||
exit 1
|
||||
else
|
||||
@@ -99,6 +114,8 @@ else
|
||||
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."
|
||||
mv "$WORK_DIR/zig-packages.json" "$ZIG_PACKAGES_JSON"
|
||||
echo -e "OK: flatpak/zig-packages.json updated."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
@@ -2,14 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Damyan Bogoev <damyan.bogoev@gmail.com>, 2025.
|
||||
# reo101 <pavel.atanasov2001@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-05-19 11:34+0300\n"
|
||||
"Last-Translator: Damyan Bogoev <damyan.bogoev@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-22 14:52+0300\n"
|
||||
"Last-Translator: reo101 <pavel.atanasov2001@gmail.com>\n"
|
||||
"Language-Team: Bulgarian <dict@ludost.net>\n"
|
||||
"Language: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -208,12 +209,12 @@ msgstr "Позволи"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Запомни избора за това разделяне"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "За да покажеш това съобщение отново, презареди конфигурацията"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +279,15 @@ msgstr "Копирано в клипборда"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Клипбордът е изчистен"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Командата завърши успешно"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Командата завърши неуспешно"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,14 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Francesc Arpi <francesc.arpi@gmail.com>, 2025.
|
||||
# Kristofer Soler <31729650+KristoferSoler@users.noreply.github.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-20 08:07+0100\n"
|
||||
"Last-Translator: Francesc Arpi <francesc.arpi@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-24 19:22+0200\n"
|
||||
"Last-Translator: Kristofer Soler <31729650+KristoferSoler@users.noreply.github.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -87,7 +88,7 @@ msgstr "Divideix a la dreta"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Executa una ordre…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -160,7 +161,7 @@ msgstr "Obre la configuració"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Paleta de comandes"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -208,12 +209,12 @@ msgstr "Permet"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Recorda l’opció per a aquest panell dividit"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Recarrega la configuració per tornar a mostrar aquest missatge"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +279,15 @@ msgstr "Copiat al porta-retalls"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Porta-retalls netejat"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Comanda completada amb èxit"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Comanda fallida"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -298,7 +299,7 @@ msgstr "Mostra les pestanyes obertes"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Nova divisió"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -9,7 +9,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-06 14:57+0100\n"
|
||||
"PO-Revision-Date: 2025-08-25 19:38+0100\n"
|
||||
"Last-Translator: Robin <r@rpfaeffle.com>\n"
|
||||
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
|
||||
"Language: de\n"
|
||||
@@ -39,7 +39,7 @@ msgstr "OK"
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr ""
|
||||
msgstr "Konfigurationsfehler"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
@@ -47,11 +47,14 @@ msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Ein oder mehrere Konfigurationsfehler wurden gefunden. Bitte überprüfe "
|
||||
"die untenstehenden Fehler und lade entweder deine Konfiguration erneut oder "
|
||||
"ignoriere die Fehler."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
msgid "Ignore"
|
||||
msgstr ""
|
||||
msgstr "Ignorieren"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
@@ -86,7 +89,7 @@ msgstr "Fenster nach rechts teilen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Einen Befehl ausführen…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -159,7 +162,7 @@ msgstr "Konfiguration öffnen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Befehlspalette"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -207,12 +210,13 @@ msgstr "Erlauben"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Auswahl für dieses geteilte Fenster beibehalten"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
"Lade die Konfiguration erneut, um diese Eingabeaufforderung erneut anzuzeigen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -277,15 +281,15 @@ msgstr "In die Zwischenablage kopiert"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Zwischenablage geleert"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Befehl erfolgreich"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Befehl fehlgeschlagen"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -297,7 +301,7 @@ msgstr "Offene Tabs einblenden"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Neues geteiltes Fenster"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-05-19 20:17-0300\n"
|
||||
"PO-Revision-Date: 2025-08-22 09:35-0300\n"
|
||||
"Last-Translator: Alan Moyano <alanmoyano203@gmail.com>\n"
|
||||
"Language-Team: Argentinian <es@tp.org.es>\n"
|
||||
"Language: es_AR\n"
|
||||
@@ -208,12 +208,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Recordar elección para esta división"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Recargar la configuración para volver a mostrar este mensaje"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +278,15 @@ msgstr "Copiado al portapapeles"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Portapapeles limpiado"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Comando ejecutado correctamente"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "El comando ha fallado"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-28 17:46+0200\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:46+0200\n"
|
||||
"Last-Translator: Miguel Peredo <miguelp@quientienemail.com>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
"Language: es_BO\n"
|
||||
@@ -87,7 +87,7 @@ msgstr "Dividir a la derecha"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Ejecutar comando..."
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -160,7 +160,7 @@ msgstr "Abrir configuración"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Paleta de comandos"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -208,12 +208,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Recordar su elección para esta división de ventana"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Recargar configuración para mostrar este aviso nuevamente"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +278,15 @@ msgstr "Copiado al portapapeles"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "El portapapeles está limpio"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Comando ejecutado con éxito"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Comando fallido"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -298,7 +298,7 @@ msgstr "Ver pestañas abiertas"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Nueva ventana dividida"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-22 09:31+0100\n"
|
||||
"PO-Revision-Date: 2025-08-23 21:01+0200\n"
|
||||
"Last-Translator: Kirwiisp <swiip__@hotmail.com>\n"
|
||||
"Language-Team: French <traduc@traduc.org>\n"
|
||||
"Language: fr\n"
|
||||
@@ -88,7 +88,7 @@ msgstr "Panneau à droite"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Exécuter une commande…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +161,7 @@ msgstr "Ouvrir la configuration"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Palette de commandes"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +209,12 @@ msgstr "Autoriser"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Se rappeler du choix pour ce panneau"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Recharger la configuration pour afficher à nouveau ce message"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +279,15 @@ msgstr "Copié dans le presse-papiers"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Presse-papiers vidé"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Commande réussie"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "La commande a échoué"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -299,7 +299,7 @@ msgstr "Voir les onglets ouverts"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Nouveau panneau"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-06-29 21:15+0100\n"
|
||||
"PO-Revision-Date: 2025-08-26 15:46+0100\n"
|
||||
"Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n"
|
||||
"Language-Team: Irish <gaeilge-gnulinux@lists.sourceforge.net>\n"
|
||||
"Language: ga\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;\n"
|
||||
"X-Generator: Poedit 3.4.4\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
@@ -209,12 +209,12 @@ msgstr "Ceadaigh"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Sábháil an rogha don scoilt seo"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Athlódáil an chumraíocht chun an teachtaireacht seo a thaispeáint arís"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -280,15 +280,15 @@ msgstr "Cóipeáilte chuig an ghearrthaisce"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Gearrthaisce glanta"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "D'éirigh leis an ordú"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Theip ar an ordú"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,15 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo.com>, 2025.
|
||||
# CraziestOwl <craziestowl@proton.me>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-13 00:00+0000\n"
|
||||
"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo."
|
||||
"com>\n"
|
||||
"PO-Revision-Date: 2025-08-23 08:00+0300\n"
|
||||
"Last-Translator: CraziestOwl <craziestowl@proton.me>\n"
|
||||
"Language-Team: Hebrew <he_IL@lists.sourceforge.net>\n"
|
||||
"Language: he\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -276,15 +276,15 @@ msgstr "הועתק ללוח ההעתקה"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "לוח ההעתקה רוקן"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "הפקודה הצליחה"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "הפקודה נכשלה"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
320
po/hu_HU.UTF-8.po
Normal file
320
po/hu_HU.UTF-8.po
Normal file
@@ -0,0 +1,320 @@
|
||||
# Hungarian translations for com.mitchellh.ghostty package.
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Balázs Szücs <bszucs1209@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:14+0200\n"
|
||||
"Last-Translator: Balázs Szücs <bszucs1209@gmail.com>\n"
|
||||
"Language-Team: Hungarian <translation-team-hu@lists.sourceforge.net>\n"
|
||||
"Language: hu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "Terminál címének módosítása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Hagyja üresen az alapértelmezett cím visszaállításához."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
|
||||
#: src/apprt/gtk/CloseDialog.zig:44
|
||||
msgid "Cancel"
|
||||
msgstr "Mégse"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
msgstr "Rendben"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "Konfigurációs hibák"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Egy vagy több konfigurációs hiba található. Kérjük, ellenőrizze az alábbi "
|
||||
"hibákat, és frissítse a konfigurációt, vagy hagyja figyelmen kívül ezeket a "
|
||||
"hibákat."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
msgid "Ignore"
|
||||
msgstr "Figyelmen kívül hagyás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
|
||||
msgid "Reload Configuration"
|
||||
msgstr "Konfiguráció frissítése"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||
msgid "Split Up"
|
||||
msgstr "Felosztás felfelé"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||
msgid "Split Down"
|
||||
msgstr "Felosztás lefelé"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||
msgid "Split Left"
|
||||
msgstr "Felosztás balra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||
msgid "Split Right"
|
||||
msgstr "Felosztás jobbra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Parancs végrehajtása…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
msgid "Copy"
|
||||
msgstr "Másolás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11
|
||||
msgid "Paste"
|
||||
msgstr "Beillesztés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||
msgid "Clear"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||
msgid "Reset"
|
||||
msgstr "Visszaállítás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||
msgid "Split"
|
||||
msgstr "Felosztás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||
msgid "Change Title…"
|
||||
msgstr "Cím módosítása…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||
msgid "Tab"
|
||||
msgstr "Fül"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||
#: src/apprt/gtk/Window.zig:265
|
||||
msgid "New Tab"
|
||||
msgstr "Új fül"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
|
||||
msgid "Close Tab"
|
||||
msgstr "Fül bezárása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||
msgid "Window"
|
||||
msgstr "Ablak"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
|
||||
msgid "New Window"
|
||||
msgstr "Új ablak"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
|
||||
msgid "Close Window"
|
||||
msgstr "Ablak bezárása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "Konfiguráció"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "Konfiguráció megnyitása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Parancspaletta"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr "Terminálvizsgáló"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1038
|
||||
msgid "About Ghostty"
|
||||
msgstr "A Ghostty névjegye"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr "Kilépés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr "Vágólap-hozzáférés engedélyezése"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Egy alkalmazás megpróbál olvasni a vágólapról. A vágólap jelenlegi tartalma "
|
||||
"lent látható."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "Elutasítás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11
|
||||
msgid "Allow"
|
||||
msgstr "Engedélyezés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Választás megjegyzése erre a felosztásra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Konfiguráció frissítése a kérdés újbóli megjelenítéséhez"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Egy alkalmazás megpróbál írni a vágólapra. A vágólap jelenlegi tartalma lent "
|
||||
"látható."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
msgstr "Figyelem: potenciálisan veszélyes beillesztés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
|
||||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"Ennek a szövegnek a terminálba való beillesztése veszélyes lehet, mivel "
|
||||
"néhány parancs végrehajtásra kerülhet."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
msgstr "Bezárás"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:87
|
||||
msgid "Quit Ghostty?"
|
||||
msgstr "Kilép a Ghostty-ból?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:88
|
||||
msgid "Close Window?"
|
||||
msgstr "Ablak bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:89
|
||||
msgid "Close Tab?"
|
||||
msgstr "Fül bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "Felosztás bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
msgstr "Minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:97
|
||||
msgid "All terminal sessions in this window will be terminated."
|
||||
msgstr "Ebben az ablakban minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:98
|
||||
msgid "All terminal sessions in this tab will be terminated."
|
||||
msgstr "Ezen a fülön minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr "Ebben a felosztásban a jelenleg futó folyamat lezárul."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1266
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Vágólapra másolva"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Vágólap törölve"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Parancs sikeres"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Parancs sikertelen"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
msgstr "Főmenü"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:239
|
||||
msgid "View Open Tabs"
|
||||
msgstr "Megnyitott fülek megtekintése"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Új felosztás"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr ""
|
||||
"⚠️ A Ghostty hibakereső verzióját futtatja! A teljesítmény csökkenni fog."
|
||||
|
||||
#: src/apprt/gtk/Window.zig:775
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "Konfiguráció frissítve"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "Ghostty fejlesztők"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty: Terminálvizsgáló"
|
@@ -2,14 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Andrej Daskalov <andrej.daskalov@gmail.com>, 2025.
|
||||
# Marija Gjorgjieva Gjondeva <mgjorgjieva2013@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-23 14:17+0100\n"
|
||||
"Last-Translator: Andrej Daskalov <andrej.daskalov@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-25 22:17+0200\n"
|
||||
"Last-Translator: Marija Gjorgjieva Gjondeva <mgjorgjieva2013@gmail.com>\n"
|
||||
"Language-Team: Macedonian\n"
|
||||
"Language: mk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -87,7 +88,7 @@ msgstr "Подели надесно"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Изврши команда…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -160,7 +161,7 @@ msgstr "Отвори конфигурација"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Командна палета"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -208,12 +209,12 @@ msgstr "Дозволи"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Запомни го изборот за оваа поделба"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Одново вчитај конфигурација за да се повторно прикаже пораката"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +279,15 @@ msgstr "Копирано во привремена меморија"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Исчистена привремена меморија"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Командата успеа"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Командата не успеа"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -298,7 +299,7 @@ msgstr "Прегледај отворени јазичиња"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Нова поделба"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Norwegian Bokmal translations for com.mitchellh.ghostty package.
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Hanna Rose <hanna@hanna.lol>, 2025.
|
||||
# Hanna Rose <me@hanna.lol>, 2025.
|
||||
# Uzair Aftab <uzaaft@outlook.com>, 2025.
|
||||
# Christoffer Tønnessen <christoffer@cto.gg>, 2025.
|
||||
# cryptocode <cryptocode@zolo.io>, 2025.
|
||||
@@ -11,8 +11,8 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-04-14 16:25+0200\n"
|
||||
"Last-Translator: cryptocode <cryptocode@zolo.io>\n"
|
||||
"PO-Revision-Date: 2025-08-23 12:52+0000\n"
|
||||
"Last-Translator: Hanna Rose <me@hanna.lol>\n"
|
||||
"Language-Team: Norwegian Bokmal <l10n-no@lister.huftis.org>\n"
|
||||
"Language: nb\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -90,7 +90,7 @@ msgstr "Del til høyre"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Kjør en kommando..."
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -163,7 +163,7 @@ msgstr "Åpne konfigurasjon"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Kommandopalett"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -211,12 +211,12 @@ msgstr "Tillat"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Husk valget for dette delte vinduet?"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Last inn konfigurasjonen på nytt for å vise denne meldingen igjen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -281,15 +281,15 @@ msgstr "Kopiert til utklippstavlen"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Utklippstavle tømt"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Kommando lyktes"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Kommando mislyktes"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -3,14 +3,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Gustavo Peres <gsodevel@gmail.com>, 2025.
|
||||
# Guilherme Tiscoski <github@guilhermetiscoski.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-06-20 10:19-0300\n"
|
||||
"Last-Translator: Mário Victor Ribeiro Silva <mariovictorrs@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-25 11:46-0500\n"
|
||||
"Last-Translator: Guilherme Tiscoski <github@guihermetiscoski.com>\n"
|
||||
"Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge."
|
||||
"net>\n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -89,7 +90,7 @@ msgstr "Dividir à direita"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Executar um comando…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -162,7 +163,7 @@ msgstr "Abrir configuração"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Paleta de comandos"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -210,12 +211,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Lembrar escolha para esta divisão"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Recarregue a configuração para mostrar este aviso novamente"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -280,15 +281,15 @@ msgstr "Copiado para a área de transferência"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Área de transferência limpa"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Comando executado com sucesso"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Comando falhou"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -3,15 +3,16 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# blackzeshi <sergey_zhuzhgov@mail.ru>, 2025.
|
||||
#
|
||||
# Ivan Bastrakov <bastaynav@proton.me>, 2025.
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-24 00:01+0500\n"
|
||||
"Last-Translator: blackzeshi <sergey_zhuzhgov@mail.ru>\n"
|
||||
"Language-Team: Russian <gnu@d07.ru>\n"
|
||||
"PO-Revision-Date: 2025-09-03 01:50+0300\n"
|
||||
"Last-Translator: Ivan Bastrakov <bastaynav@proton.me>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -89,7 +90,7 @@ msgstr "Сплит вправо"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Выполнить команду…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -162,7 +163,7 @@ msgstr "Открыть конфигурационный файл"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Палитра команд"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -210,12 +211,12 @@ msgstr "Разрешить"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Запомнить выбор для этого сплита"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Перезагрузите конфигурацию, чтобы снова увидеть это сообщение"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -223,7 +224,8 @@ msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Приложение пытается записать данные в буфер обмена. Эти данные показаны ниже."
|
||||
"Приложение пытается записать данные в буфер обмена. Текущее содержимое "
|
||||
"буфера обмена показано ниже."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
@@ -279,15 +281,15 @@ msgstr "Скопировано в буфер обмена"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Буфер обмена очищен"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Команда выполнена успешно"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Команда завершилась с ошибкой"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -299,7 +301,7 @@ msgstr "Просмотреть открытые вкладки"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Новый сплит"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-24 22:01+0300\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:30+0300\n"
|
||||
"Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr\n"
|
||||
@@ -88,7 +88,7 @@ msgstr "Sağa Doğru Böl"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Bir komut çalıştır…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +161,7 @@ msgstr "Yapılandırmayı Aç"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Komut Paleti"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +209,12 @@ msgstr "İzin Ver"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Bu bölme için tercihi anımsa"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Bu istemi tekrar göstermek için yapılandırmayı yeniden yükle"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +279,15 @@ msgstr "Panoya kopyalandı"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Pano temizlendi"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Komut başarılı oldu"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Komut başarısız oldu"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,14 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Danylo Zalizchuk <danilmail0110@gmail.com>, 2025.
|
||||
# Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-16 20:16+0200\n"
|
||||
"Last-Translator: Danylo Zalizchuk <danilmail0110@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-08-25 19:59+0100\n"
|
||||
"Last-Translator: Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com>\n"
|
||||
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -20,17 +21,17 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "Змінити назву терміналу"
|
||||
msgstr "Змінити заголовок терміналу"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Залиште порожнім, щоб відновити назву за замовчуванням."
|
||||
msgstr "Залиште порожнім, щоб відновити заголовок за замовчуванням."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
|
||||
#: src/apprt/gtk/CloseDialog.zig:44
|
||||
msgid "Cancel"
|
||||
msgstr "Відмінити"
|
||||
msgstr "Скасувати"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
@@ -39,7 +40,7 @@ msgstr "ОК"
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "Помилки конфігурації"
|
||||
msgstr "Помилки налаштування"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
@@ -47,9 +48,8 @@ msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Виявлено одну або декілька помилок у конфігурації. Будь ласка, перегляньте "
|
||||
"наведені нижче помилки і або перезавантажте конфігурацію, або проігноруйте "
|
||||
"ці помилки."
|
||||
"Виявлено одну або декілька помилок налаштування. Будь ласка, перегляньте "
|
||||
"помилки нижче і або перезавантажте налаштування, або проігноруйте ці помилки."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
@@ -61,35 +61,35 @@ msgstr "Ігнорувати"
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
|
||||
msgid "Reload Configuration"
|
||||
msgstr "Перезавантажити конфігурацію"
|
||||
msgstr "Перезавантажити налаштування"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||
msgid "Split Up"
|
||||
msgstr "Розділити панель вгору"
|
||||
msgstr "Нова панель зверху"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||
msgid "Split Down"
|
||||
msgstr "Розділити панель вниз"
|
||||
msgstr "Нова панель знизу"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||
msgid "Split Left"
|
||||
msgstr "Розділити панель ліворуч"
|
||||
msgstr "Нова панель ліворуч"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||
msgid "Split Right"
|
||||
msgstr "Розділити панель праворуч"
|
||||
msgstr "Нова панель праворуч"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr ""
|
||||
msgstr "Виконати команду…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -115,7 +115,7 @@ msgstr "Скинути"
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||
msgid "Split"
|
||||
msgstr "Розділена панель"
|
||||
msgstr "Панель"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||
@@ -153,16 +153,16 @@ msgstr "Закрити вікно"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "Конфігурація"
|
||||
msgstr "Налаштування"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "Відкрити конфігурацію"
|
||||
msgstr "Відкрити налаштування"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr ""
|
||||
msgstr "Палітра команд"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -182,7 +182,7 @@ msgstr "Завершити"
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr "Дозволити доступ до буфера обміну"
|
||||
msgstr "Надати доступ до буфера обміну"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
@@ -190,15 +190,15 @@ msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Програма намагається прочитати дані з буфера обміну. Нижче показано поточний "
|
||||
"вміст буфера обміну."
|
||||
"Програма намагається прочитати дані з буфера обміну. Нижче наведено вміст "
|
||||
"буфера обміну."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "Відхилити"
|
||||
msgstr "Заборонити"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
@@ -210,12 +210,12 @@ msgstr "Дозволити"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr ""
|
||||
msgstr "Запамʼятати для цієї панелі"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
msgstr "Перезавантажте налаштування, щоб показати це повідомлення знову"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -223,8 +223,8 @@ msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Програма намагається записати дані до буфера обміну. Нижче показано поточний "
|
||||
"вміст буфера обміну."
|
||||
"Програма намагається записати дані до буфера обміну. Нижче наведено вміст "
|
||||
"буфера обміну."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
@@ -235,8 +235,8 @@ msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"Вставка цього тексту в термінал може бути небезпечною, оскільки виглядає "
|
||||
"так, ніби деякі команди можуть бути виконані."
|
||||
"Вставка цього тексту в термінал може бути небезпечною, бо схоже, що деякі "
|
||||
"команди можуть бути виконані."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
@@ -256,7 +256,7 @@ msgstr "Закрити вкладку?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "Закрити розділену панель?"
|
||||
msgstr "Закрити панель?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
@@ -272,24 +272,23 @@ msgstr "Всі сесії терміналу в цій вкладці будут
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr ""
|
||||
"Поточний процес, що виконується в цій розділеній панелі, буде завершено."
|
||||
msgstr "Процес, що виконується в цій панелі, буде завершено."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1266
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Скопійовано в буфер обміну"
|
||||
msgstr "Скопійовано до буферa обміну"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr ""
|
||||
msgstr "Буфер обміну очищено"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr ""
|
||||
msgstr "Команда завершилась успішно"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr ""
|
||||
msgstr "Команда завершилась з помилкою"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -301,7 +300,7 @@ msgstr "Переглянути відкриті вкладки"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr ""
|
||||
msgstr "Нова панель"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
@@ -311,7 +310,7 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:775
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "Конфігурацію перезавантажено"
|
||||
msgstr "Налаштування перезавантажено"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
|
165
src/Surface.zig
165
src/Surface.zig
@@ -258,6 +258,7 @@ const DerivedConfig = struct {
|
||||
mouse_shift_capture: configpkg.MouseShiftCapture,
|
||||
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
|
||||
macos_option_as_alt: ?configpkg.OptionAsAlt,
|
||||
selection_clear_on_copy: bool,
|
||||
selection_clear_on_typing: bool,
|
||||
vt_kam_allowed: bool,
|
||||
wait_after_command: bool,
|
||||
@@ -272,6 +273,7 @@ const DerivedConfig = struct {
|
||||
title_report: bool,
|
||||
links: []Link,
|
||||
link_previews: configpkg.LinkPreviews,
|
||||
scroll_to_bottom: configpkg.Config.ScrollToBottom,
|
||||
|
||||
const Link = struct {
|
||||
regex: oni.Regex,
|
||||
@@ -326,6 +328,7 @@ const DerivedConfig = struct {
|
||||
.mouse_shift_capture = config.@"mouse-shift-capture",
|
||||
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
|
||||
.macos_option_as_alt = config.@"macos-option-as-alt",
|
||||
.selection_clear_on_copy = config.@"selection-clear-on-copy",
|
||||
.selection_clear_on_typing = config.@"selection-clear-on-typing",
|
||||
.vt_kam_allowed = config.@"vt-kam-allowed",
|
||||
.wait_after_command = config.@"wait-after-command",
|
||||
@@ -340,6 +343,7 @@ const DerivedConfig = struct {
|
||||
.title_report = config.@"title-report",
|
||||
.links = links,
|
||||
.link_previews = config.@"link-previews",
|
||||
.scroll_to_bottom = config.@"scroll-to-bottom",
|
||||
|
||||
// Assignments happen sequentially so we have to do this last
|
||||
// so that the memory is captured from allocs above.
|
||||
@@ -1728,6 +1732,7 @@ pub fn pwd(
|
||||
pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
self.renderer_state.mutex.lock();
|
||||
const cursor = self.renderer_state.terminal.screen.cursor;
|
||||
const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0;
|
||||
self.renderer_state.mutex.unlock();
|
||||
|
||||
// TODO: need to handle when scrolling and the cursor is not
|
||||
@@ -1762,7 +1767,38 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
break :y y;
|
||||
};
|
||||
|
||||
return .{ .x = x, .y = y };
|
||||
// Our height for now is always just the cell height because our preedit
|
||||
// rendering only renders in a single line.
|
||||
const height: f64 = height: {
|
||||
var height: f64 = @floatFromInt(self.size.cell.height);
|
||||
height /= content_scale.y;
|
||||
break :height height;
|
||||
};
|
||||
const width: f64 = width: {
|
||||
var width: f64 = @floatFromInt(preedit_width * self.size.cell.width);
|
||||
|
||||
// Our max width is the remaining screen width after the cursor.
|
||||
// We don't have to deal with wrapping because the preedit doesn't
|
||||
// wrap right now.
|
||||
const screen_width: f64 = @floatFromInt(self.size.terminal().width);
|
||||
const x_offset: f64 = @floatFromInt((cursor.x + 1) * self.size.cell.width);
|
||||
const max = screen_width - x_offset;
|
||||
width = @min(width, max);
|
||||
|
||||
// Note: we don't apply content scale here because it looks like
|
||||
// for some reason in macOS its already scaled. I'm not sure why
|
||||
// that is so I'm going to just leave this comment here so its known
|
||||
// that I left this out on purpose pending more investigation.
|
||||
|
||||
break :width width;
|
||||
};
|
||||
|
||||
return .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
||||
@@ -2280,7 +2316,8 @@ pub fn keyCallback(
|
||||
try self.setSelection(null);
|
||||
}
|
||||
|
||||
try self.io.terminal.scrollViewport(.{ .bottom = {} });
|
||||
if (self.config.scroll_to_bottom.keystroke) try self.io.terminal.scrollViewport(.bottom);
|
||||
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
@@ -2766,8 +2803,21 @@ pub fn scrollCallback(
|
||||
// that a wheel tick of 1 results in single scroll event.
|
||||
const yoff_adjusted: f64 = if (scroll_mods.precision)
|
||||
yoff
|
||||
else
|
||||
yoff * cell_size * self.config.mouse_scroll_multiplier;
|
||||
else yoff_adjusted: {
|
||||
// Round out the yoff to an absolute minimum of 1. macos tries to
|
||||
// simulate precision scrolling with non precision events by
|
||||
// ramping up the magnitude of the offsets as it detects faster
|
||||
// scrolling. Single click (very slow) scrolls are reported with a
|
||||
// magnitude of 0.1 which would normally require a few clicks
|
||||
// before we register an actual scroll event (depending on cell
|
||||
// height and the mouse_scroll_multiplier setting).
|
||||
const yoff_max: f64 = if (yoff > 0)
|
||||
@max(yoff, 1)
|
||||
else
|
||||
@min(yoff, -1);
|
||||
|
||||
break :yoff_adjusted yoff_max * cell_size * self.config.mouse_scroll_multiplier;
|
||||
};
|
||||
|
||||
// Add our previously saved pending amount to the offset to get the
|
||||
// new offset value. The signs of the pending and yoff should match
|
||||
@@ -3555,26 +3605,7 @@ pub fn mouseButtonCallback(
|
||||
};
|
||||
|
||||
switch (self.config.right_click_action) {
|
||||
.ignore => {
|
||||
// Return early to skip clearing the selection.
|
||||
try self.queueRender();
|
||||
return true;
|
||||
},
|
||||
.copy => {
|
||||
if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
}
|
||||
},
|
||||
.@"copy-or-paste" => {
|
||||
if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
} else {
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
}
|
||||
},
|
||||
.paste => {
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
},
|
||||
.ignore => {},
|
||||
.@"context-menu" => {
|
||||
// If we already have a selection and the selection contains
|
||||
// where we clicked then we don't want to modify the selection.
|
||||
@@ -3588,12 +3619,45 @@ pub fn mouseButtonCallback(
|
||||
const sel = screen.selectWord(pin) orelse break :sel;
|
||||
try self.setSelection(sel);
|
||||
try self.queueRender();
|
||||
|
||||
// Don't consume so that we show the context menu in apprt.
|
||||
return false;
|
||||
},
|
||||
}
|
||||
.copy => {
|
||||
if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
}
|
||||
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
},
|
||||
.@"copy-or-paste" => if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
} else {
|
||||
// Pasting can trigger a lock grab in complete clipboard
|
||||
// request so we need to unlock.
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
|
||||
// We don't need to clear selection because we didn't have
|
||||
// one to begin with.
|
||||
},
|
||||
.paste => {
|
||||
// Before we yield the lock, clear our selection if we have
|
||||
// one.
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
|
||||
// Pasting can trigger a lock grab in complete clipboard
|
||||
// request so we need to unlock.
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
},
|
||||
}
|
||||
|
||||
// Consume the event such that the context menu is not displayed.
|
||||
return true;
|
||||
@@ -4482,6 +4546,17 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
return true;
|
||||
};
|
||||
|
||||
// Clear the selection if configured to do so.
|
||||
if (self.config.selection_clear_on_copy) {
|
||||
if (self.setSelection(null)) {
|
||||
self.queueRender() catch |err| {
|
||||
log.warn("failed to queue render after clear selection err={}", .{err});
|
||||
};
|
||||
} else |err| {
|
||||
log.warn("failed to clear selection after copy err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4494,19 +4569,32 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
|
||||
const pos = try self.rt_surface.getCursorPos();
|
||||
if (try self.linkAtPos(pos)) |link_info| {
|
||||
// Get the URL text from selection
|
||||
const url_text = (self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = link_info[1],
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
})) catch |err| {
|
||||
log.err("error reading url string err={}", .{err});
|
||||
return false;
|
||||
const url_text = switch (link_info[0]) {
|
||||
.open => url_text: {
|
||||
// For regex links, get the text from selection
|
||||
break :url_text (self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = link_info[1],
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
})) catch |err| {
|
||||
log.err("error reading url string err={}", .{err});
|
||||
return false;
|
||||
};
|
||||
},
|
||||
|
||||
._open_osc8 => url_text: {
|
||||
// For OSC8 links, get the URI directly from hyperlink data
|
||||
const uri = self.osc8URI(link_info[1].start()) orelse {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
return false;
|
||||
};
|
||||
break :url_text try self.alloc.dupeZ(u8, uri);
|
||||
},
|
||||
};
|
||||
defer self.alloc.free(url_text);
|
||||
|
||||
self.rt_surface.setClipboardString(url_text, .standard, false) catch |err| {
|
||||
log.err("error copying url to clipboard err={}", .{err});
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return true;
|
||||
@@ -4674,10 +4762,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
{},
|
||||
),
|
||||
|
||||
.close_tab => return try self.rt_app.performAction(
|
||||
.close_tab => |v| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.close_tab,
|
||||
{},
|
||||
switch (v) {
|
||||
.this => .this,
|
||||
.other => .other,
|
||||
},
|
||||
),
|
||||
|
||||
inline .previous_tab,
|
||||
|
@@ -70,13 +70,15 @@ pub const Runtime = enum {
|
||||
gtk,
|
||||
|
||||
pub fn default(target: std.Target) Runtime {
|
||||
// The Linux default is GTK because it is a full featured application.
|
||||
if (target.os.tag == .linux) return .@"gtk-ng";
|
||||
|
||||
// Otherwise, we do NONE so we don't create an exe and we
|
||||
// create libghostty. On macOS, Xcode is used to build the app
|
||||
// that links to libghostty.
|
||||
return .none;
|
||||
return switch (target.os.tag) {
|
||||
// The Linux and FreeBSD default is GTK because it is a full
|
||||
// featured application.
|
||||
.linux, .freebsd => .@"gtk-ng",
|
||||
// Otherwise, we do NONE so we don't create an exe and we create
|
||||
// libghostty. On macOS, Xcode is used to build the app that links
|
||||
// to libghostty.
|
||||
else => .none,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -83,8 +83,9 @@ pub const Action = union(Key) {
|
||||
/// the tab should be opened in a new window.
|
||||
new_tab,
|
||||
|
||||
/// Closes the tab belonging to the currently focused split.
|
||||
close_tab,
|
||||
/// Closes the tab belonging to the currently focused split, or all other
|
||||
/// tabs, depending on the mode.
|
||||
close_tab: CloseTabMode,
|
||||
|
||||
/// Create a new split. The value determines the location of the split
|
||||
/// relative to the target.
|
||||
@@ -701,3 +702,11 @@ pub const OpenUrl = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// sync with ghostty_action_close_tab_mode_e in ghostty.h
|
||||
pub const CloseTabMode = enum(c_int) {
|
||||
/// Close the current tab.
|
||||
this,
|
||||
/// Close all other tabs.
|
||||
other,
|
||||
};
|
||||
|
@@ -447,6 +447,9 @@ pub const Surface = struct {
|
||||
|
||||
/// Input to send to the command after it is started.
|
||||
initial_input: ?[*:0]const u8 = null,
|
||||
|
||||
/// Wait after the command exits
|
||||
wait_after_command: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||
@@ -540,6 +543,11 @@ pub const Surface = struct {
|
||||
);
|
||||
}
|
||||
|
||||
// Wait after command
|
||||
if (opts.wait_after_command) {
|
||||
config.@"wait-after-command" = true;
|
||||
}
|
||||
|
||||
// Initialize our surface right away. We're given a view that is
|
||||
// ready to use.
|
||||
try self.core_surface.init(
|
||||
@@ -1814,10 +1822,18 @@ pub const CAPI = struct {
|
||||
surface.mousePressureCallback(stage, pressure);
|
||||
}
|
||||
|
||||
export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void {
|
||||
export fn ghostty_surface_ime_point(
|
||||
surface: *Surface,
|
||||
x: *f64,
|
||||
y: *f64,
|
||||
width: *f64,
|
||||
height: *f64,
|
||||
) void {
|
||||
const pos = surface.core_surface.imePoint();
|
||||
x.* = pos.x;
|
||||
y.* = pos.y;
|
||||
width.* = pos.width;
|
||||
height.* = pos.height;
|
||||
}
|
||||
|
||||
/// Request that the surface become closed. This will go through the
|
||||
|
@@ -116,6 +116,11 @@ pub const Application = extern struct {
|
||||
/// and initialization was successful.
|
||||
transient_cgroup_base: ?[]const u8 = null,
|
||||
|
||||
/// This is set to true so long as we request a window exactly
|
||||
/// once. This prevents quitting the app before we've shown one
|
||||
/// window.
|
||||
requested_window: bool = false,
|
||||
|
||||
/// This is set to false internally when the event loop
|
||||
/// should exit and the application should quit. This must
|
||||
/// only be set by the main loop thread.
|
||||
@@ -461,11 +466,24 @@ pub const Application = extern struct {
|
||||
// If the quit timer has expired, quit.
|
||||
if (priv.quit_timer == .expired) break :q true;
|
||||
|
||||
// There's no quit timer running, or it hasn't expired, don't quit.
|
||||
// If we have no windows attached to our app, also quit.
|
||||
if (priv.requested_window and @as(
|
||||
?*glib.List,
|
||||
self.as(gtk.Application).getWindows(),
|
||||
) == null) break :q true;
|
||||
|
||||
// No quit conditions met
|
||||
break :q false;
|
||||
};
|
||||
|
||||
if (must_quit) self.quit();
|
||||
if (must_quit) {
|
||||
// All must quit scenarios do not need confirmation.
|
||||
// Furthermore, must quit scenarios may result in a situation
|
||||
// where its unsafe to even access the app/surface memory
|
||||
// since its in the process of being freed. We must simply
|
||||
// begin our exit immediately.
|
||||
self.quitNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +506,15 @@ pub const Application = extern struct {
|
||||
const parent: ?*gtk.Widget = parent: {
|
||||
const list = gtk.Window.listToplevels();
|
||||
defer list.free();
|
||||
const focused = list.findCustom(null, findActiveWindow);
|
||||
const focused = @as(?*glib.List, list.findCustom(
|
||||
null,
|
||||
findActiveWindow,
|
||||
)) orelse {
|
||||
// If we have an active surface then we should have
|
||||
// a window available but in the rare case we don't we
|
||||
// should exit so we don't crash.
|
||||
break :parent null;
|
||||
};
|
||||
break :parent @ptrCast(@alignCast(focused.f_data));
|
||||
};
|
||||
|
||||
@@ -542,7 +568,7 @@ pub const Application = extern struct {
|
||||
value: apprt.Action.Value(action),
|
||||
) !bool {
|
||||
switch (action) {
|
||||
.close_tab => return Action.closeTab(target),
|
||||
.close_tab => return Action.closeTab(target, value),
|
||||
.close_window => return Action.closeWindow(target),
|
||||
|
||||
.config_change => try Action.configChange(
|
||||
@@ -713,27 +739,24 @@ pub const Application = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn loadRuntimeCss(
|
||||
self: *Self,
|
||||
) Allocator.Error!void {
|
||||
fn loadRuntimeCss(self: *Self) Allocator.Error!void {
|
||||
const alloc = self.allocator();
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||
const config = self.private().config.get();
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(alloc, 2048);
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
const writer = buf.writer(alloc);
|
||||
|
||||
const config = self.private().config.get();
|
||||
const window_theme = config.@"window-theme";
|
||||
const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background;
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.print(
|
||||
\\widget.unfocused-split {{
|
||||
\\ opacity: {d:.2};
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
1.0 - config.@"unfocused-split-opacity",
|
||||
unfocused_fill.r,
|
||||
@@ -747,6 +770,7 @@ pub const Application = extern struct {
|
||||
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
@@ -759,9 +783,129 @@ pub const Application = extern struct {
|
||||
\\.window headerbar {{
|
||||
\\ font-family: "{[font_family]s}";
|
||||
\\}}
|
||||
\\
|
||||
, .{ .font_family = font_family });
|
||||
}
|
||||
|
||||
try loadRuntimeCss414(config, &writer);
|
||||
try loadRuntimeCss416(config, &writer);
|
||||
|
||||
// ensure that we have a sentinel
|
||||
try writer.writeByte(0);
|
||||
|
||||
const data = buf.items[0 .. buf.items.len - 1 :0];
|
||||
|
||||
log.debug("runtime CSS is {d} bytes", .{data.len + 1});
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/// Load runtime CSS for older than GTK 4.16
|
||||
fn loadRuntimeCss414(
|
||||
config: *const CoreConfig,
|
||||
writer: *const std.ArrayListUnmanaged(u8).Writer,
|
||||
) Allocator.Error!void {
|
||||
if (gtk_version.runtimeAtLeast(4, 16, 0)) return;
|
||||
|
||||
const window_theme = config.@"window-theme";
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\windowhandle {{
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\ color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
\\windowhandle:backdrop {{
|
||||
\\ background-color: oklab(from rgb({d},{d},{d}) calc(l * 0.9) a b / alpha);
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
headerbar_foreground.r,
|
||||
headerbar_foreground.g,
|
||||
headerbar_foreground.b,
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Load runtime for GTK 4.16 and newer
|
||||
fn loadRuntimeCss416(
|
||||
config: *const CoreConfig,
|
||||
writer: *const std.ArrayListUnmanaged(u8).Writer,
|
||||
) Allocator.Error!void {
|
||||
if (gtk_version.runtimeUntil(4, 16, 0)) return;
|
||||
|
||||
const window_theme = config.@"window-theme";
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.writeAll(
|
||||
\\/*
|
||||
\\ * Child Exited Overlay
|
||||
\\ */
|
||||
\\
|
||||
\\.child-exited.normal revealer widget {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--success-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\.child-exited.abnormal revealer widget {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--error-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\/*
|
||||
\\ * Surface
|
||||
\\ */
|
||||
\\
|
||||
\\.surface progressbar.error trough progress {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--error-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\.surface .bell-overlay {
|
||||
\\ border-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--accent-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\/*
|
||||
\\ * Splits
|
||||
\\ */
|
||||
\\
|
||||
\\.window .split paned > separator {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--window-bg-color),
|
||||
\\ transparent 0%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\:root {{
|
||||
@@ -794,15 +938,6 @@ pub const Application = extern struct {
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
|
||||
const data = try alloc.dupeZ(u8, buf.items);
|
||||
defer alloc.free(data);
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
fn loadCustomCss(self: *Self) !void {
|
||||
@@ -872,7 +1007,8 @@ pub const Application = extern struct {
|
||||
self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||
self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||
self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||
self.syncActionAccelerator("win.close-tab", .{ .close_tab = {} });
|
||||
self.syncActionAccelerator("win.close-tab::this", .{ .close_tab = .this });
|
||||
self.syncActionAccelerator("tab.close::this", .{ .close_tab = .this });
|
||||
self.syncActionAccelerator("win.split-right", .{ .new_split = .right });
|
||||
self.syncActionAccelerator("win.split-down", .{ .new_split = .down });
|
||||
self.syncActionAccelerator("win.split-left", .{ .new_split = .left });
|
||||
@@ -1576,12 +1712,16 @@ pub const Application = extern struct {
|
||||
|
||||
/// All apprt action handlers
|
||||
const Action = struct {
|
||||
pub fn closeTab(target: apprt.Target) bool {
|
||||
pub fn closeTab(target: apprt.Target, value: apprt.Action.Value(.close_tab)) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
return surface.as(gtk.Widget).activateAction("tab.close", null) != 0;
|
||||
return surface.as(gtk.Widget).activateAction(
|
||||
"tab.close",
|
||||
glib.ext.VariantType.stringFor([:0]const u8),
|
||||
@as([*:0]const u8, @tagName(value)),
|
||||
) != 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1853,6 +1993,13 @@ const Action = struct {
|
||||
self: *Application,
|
||||
parent: ?*CoreSurface,
|
||||
) !void {
|
||||
// Note that we've requested a window at least once. This is used
|
||||
// to trigger quit on no windows. Note I'm not sure if this is REALLY
|
||||
// necessary, but I don't want to risk a bug where on a slow machine
|
||||
// or something we quit immediately after starting up because there
|
||||
// was a delay in the event loop before we created a Window.
|
||||
self.private().requested_window = true;
|
||||
|
||||
const win = Window.new(self);
|
||||
initAndShowWindow(self, win, parent);
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Dialog = @import("dialog.zig").Dialog;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_config_errors_dialog);
|
||||
const log = std.log.scoped(.gtk_ghostty_close_confirmation_dialog);
|
||||
|
||||
pub const CloseConfirmationDialog = extern struct {
|
||||
const Self = @This();
|
||||
|
@@ -105,6 +105,24 @@ pub const Surface = extern struct {
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"error" = struct {
|
||||
pub const name = "error";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = false,
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"error",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"font-size-request" = struct {
|
||||
pub const name = "font-size-request";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
@@ -472,6 +490,12 @@ pub const Surface = extern struct {
|
||||
// false by a parent widget.
|
||||
bell_ringing: bool = false,
|
||||
|
||||
/// True if this surface is in an error state. This is currently
|
||||
/// a simple boolean with no additional information on WHAT the
|
||||
/// error state is, because we don't yet need it or use it. For now,
|
||||
/// if this is true, then it means the terminal is non-functional.
|
||||
@"error": bool = false,
|
||||
|
||||
/// A weak reference to an inspector window.
|
||||
inspector: ?*InspectorWindow = null,
|
||||
|
||||
@@ -571,6 +595,17 @@ pub const Surface = extern struct {
|
||||
return @intFromBool(config.@"bell-features".border);
|
||||
}
|
||||
|
||||
fn closureStackChildName(
|
||||
_: *Self,
|
||||
error_: c_int,
|
||||
) callconv(.c) ?[*:0]const u8 {
|
||||
const err = error_ != 0;
|
||||
return if (err)
|
||||
glib.ext.dupeZ(u8, "error")
|
||||
else
|
||||
glib.ext.dupeZ(u8, "terminal");
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(self: *Self) void {
|
||||
signals.@"toggle-fullscreen".impl.emit(
|
||||
self,
|
||||
@@ -838,7 +873,7 @@ pub const Surface = extern struct {
|
||||
// such as single quote on a US international keyboard layout.
|
||||
if (priv.im_composing) return true;
|
||||
|
||||
// If we were composing and now we're not it means that we committed
|
||||
// If we were composing and now we're not, it means that we committed
|
||||
// the text. We also don't want to encode a key event for this.
|
||||
// Example: enable Japanese input method, press "konn" and then
|
||||
// press enter. The final enter should not be encoded and "konn"
|
||||
@@ -878,9 +913,24 @@ pub const Surface = extern struct {
|
||||
|
||||
// We want to get the physical unmapped key to process physical keybinds.
|
||||
// (These are keybinds explicitly marked as requesting physical mapping).
|
||||
const physical_key = keycode: for (input.keycodes.entries) |entry| {
|
||||
if (entry.native == keycode) break :keycode entry.key;
|
||||
} else .unidentified;
|
||||
const physical_key = keycode: {
|
||||
const w3c_key: input.Key = w3c: for (input.keycodes.entries) |entry| {
|
||||
if (entry.native == keycode) break :w3c entry.key;
|
||||
} else .unidentified;
|
||||
|
||||
// If the key should be remappable, then consult the pre-remapped
|
||||
// XKB keyval/keysym to get the (possibly) remapped key.
|
||||
//
|
||||
// See the docs for `shouldBeRemappable` for why we even have to
|
||||
// do this in the first place.
|
||||
if (w3c_key.shouldBeRemappable()) {
|
||||
if (gtk_key.keyFromKeyval(keyval)) |remapped|
|
||||
break :keycode remapped;
|
||||
}
|
||||
|
||||
// Return the original physical key
|
||||
break :keycode w3c_key;
|
||||
};
|
||||
|
||||
// Get our modifier for the event
|
||||
const mods: input.Mods = gtk_key.eventMods(
|
||||
@@ -1525,6 +1575,12 @@ pub const Surface = extern struct {
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"bell-ringing".impl.param_spec);
|
||||
}
|
||||
|
||||
pub fn setError(self: *Self, v: bool) void {
|
||||
const priv = self.private();
|
||||
priv.@"error" = v;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec);
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -1577,6 +1633,28 @@ pub const Surface = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn propError(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
_: ?*anyopaque,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
if (priv.@"error") {
|
||||
// Ensure we have an opaque background. The window will NOT set
|
||||
// this if we have transparency set and we need an opaque
|
||||
// background for the error message to be readable.
|
||||
self.as(gtk.Widget).addCssClass("background");
|
||||
} else {
|
||||
// Regardless of transparency setting, we remove the background
|
||||
// CSS class from this widget. Parent widgets will set it
|
||||
// appropriately (see window.zig for example).
|
||||
self.as(gtk.Widget).removeCssClass("background");
|
||||
}
|
||||
|
||||
// Note above: in both cases setting our error view is handled by
|
||||
// a Gtk.Stack visible-child-name binding.
|
||||
}
|
||||
|
||||
fn propMouseHoverUrl(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -1927,8 +2005,11 @@ pub const Surface = extern struct {
|
||||
// Bell stops ringing if any mouse button is pressed.
|
||||
self.setBellRinging(false);
|
||||
|
||||
// If we don't have focus, grab it.
|
||||
// Get our surface. If we don't have one, ignore this.
|
||||
const priv = self.private();
|
||||
const core_surface = priv.core_surface orelse return;
|
||||
|
||||
// If we don't have focus, grab it.
|
||||
const gl_area_widget = priv.gl_area.as(gtk.Widget);
|
||||
if (gl_area_widget.hasFocus() == 0) {
|
||||
_ = gl_area_widget.grabFocus();
|
||||
@@ -1936,10 +2017,10 @@ pub const Surface = extern struct {
|
||||
|
||||
// Report the event
|
||||
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
||||
const consumed = if (priv.core_surface) |surface| consumed: {
|
||||
const consumed = consumed: {
|
||||
const gtk_mods = event.getModifierState();
|
||||
const mods = gtk_key.translateMods(gtk_mods);
|
||||
break :consumed surface.mouseButtonCallback(
|
||||
break :consumed core_surface.mouseButtonCallback(
|
||||
.press,
|
||||
button,
|
||||
mods,
|
||||
@@ -1947,7 +2028,7 @@ pub const Surface = extern struct {
|
||||
log.warn("error in key callback err={}", .{err});
|
||||
break :err false;
|
||||
};
|
||||
} else false;
|
||||
};
|
||||
|
||||
// If a right click isn't consumed, mouseButtonCallback selects the hovered
|
||||
// word and returns false. We can use this to handle the context menu
|
||||
@@ -2288,21 +2369,23 @@ pub const Surface = extern struct {
|
||||
) callconv(.c) void {
|
||||
log.debug("realize", .{});
|
||||
|
||||
// Make the GL area current so we can detect any OpenGL errors. If
|
||||
// we have errors here we can't render and we switch to the error
|
||||
// state.
|
||||
const priv = self.private();
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||
log.warn("this error is almost always due to a library, driver, or GTK issue", .{});
|
||||
log.warn("this is a common cause of this issue: https://ghostty.org/docs/help/gtk-opengl-context", .{});
|
||||
self.setError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we already have an initialized surface then we notify it.
|
||||
// If we don't, we'll initialize it on the first resize so we have
|
||||
// our proper initial dimensions.
|
||||
const priv = self.private();
|
||||
if (priv.core_surface) |v| realize: {
|
||||
// We need to make the context current so we can call GL functions.
|
||||
// This is required for all surface operations.
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||
log.warn("this error is usually due to a driver or gtk bug", .{});
|
||||
log.warn("this is a common cause of this issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/4950", .{});
|
||||
break :realize;
|
||||
}
|
||||
|
||||
v.renderer.displayRealized() catch |err| {
|
||||
log.warn("core displayRealized failed err={}", .{err});
|
||||
break :realize;
|
||||
@@ -2647,11 +2730,13 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
||||
class.bindTemplateCallback("context_menu_closed", &contextMenuClosed);
|
||||
class.bindTemplateCallback("notify_config", &propConfig);
|
||||
class.bindTemplateCallback("notify_error", &propError);
|
||||
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
||||
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
||||
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
|
||||
class.bindTemplateCallback("notify_bell_ringing", &propBellRinging);
|
||||
class.bindTemplateCallback("should_border_be_shown", &closureShouldBorderBeShown);
|
||||
class.bindTemplateCallback("stack_child_name", &closureStackChildName);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
@@ -2659,6 +2744,7 @@ pub const Surface = extern struct {
|
||||
properties.config.impl,
|
||||
properties.@"child-exited".impl,
|
||||
properties.@"default-size".impl,
|
||||
properties.@"error".impl,
|
||||
properties.@"font-size-request".impl,
|
||||
properties.focused.impl,
|
||||
properties.@"min-size".impl,
|
||||
|
@@ -18,7 +18,6 @@ const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
@@ -199,8 +198,11 @@ pub const Tab = extern struct {
|
||||
}
|
||||
|
||||
fn initActionMap(self: *Self) void {
|
||||
const s_param_type = glib.ext.VariantType.newFor([:0]const u8);
|
||||
defer s_param_type.free();
|
||||
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("close", actionClose, null),
|
||||
.init("close", actionClose, s_param_type),
|
||||
.init("ring-bell", actionRingBell, null),
|
||||
};
|
||||
|
||||
@@ -314,18 +316,44 @@ pub const Tab = extern struct {
|
||||
|
||||
fn actionClose(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
param_: ?*glib.Variant,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const param = param_ orelse {
|
||||
log.warn("tab.close-tab called without a parameter", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
var str: ?[*:0]const u8 = null;
|
||||
param.get("&s", &str);
|
||||
|
||||
const tab_view = ext.getAncestor(
|
||||
adw.TabView,
|
||||
self.as(gtk.Widget),
|
||||
) orelse return;
|
||||
|
||||
const page = tab_view.getPage(self.as(gtk.Widget));
|
||||
|
||||
const mode = std.meta.stringToEnum(
|
||||
apprt.action.CloseTabMode,
|
||||
std.mem.span(
|
||||
str orelse {
|
||||
log.warn("invalid mode provided to tab.close-tab", .{});
|
||||
return;
|
||||
},
|
||||
),
|
||||
) orelse {
|
||||
// Need to be defensive here since actions can be triggered externally.
|
||||
log.warn("invalid mode provided to tab.close-tab: {s}", .{str.?});
|
||||
return;
|
||||
};
|
||||
|
||||
// Delegate to our parent to handle this, since this will emit
|
||||
// a close-page signal that the parent can intercept.
|
||||
tab_view.closePage(page);
|
||||
switch (mode) {
|
||||
.this => tab_view.closePage(page),
|
||||
.other => tab_view.closeOtherPages(page),
|
||||
}
|
||||
}
|
||||
|
||||
fn actionRingBell(
|
||||
|
@@ -320,10 +320,13 @@ pub const Window = extern struct {
|
||||
|
||||
/// Setup our action map.
|
||||
fn initActionMap(self: *Self) void {
|
||||
const s_variant_type = glib.ext.VariantType.newFor([:0]const u8);
|
||||
defer s_variant_type.free();
|
||||
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("about", actionAbout, null),
|
||||
.init("close", actionClose, null),
|
||||
.init("close-tab", actionCloseTab, null),
|
||||
.init("close-tab", actionCloseTab, s_variant_type),
|
||||
.init("new-tab", actionNewTab, null),
|
||||
.init("new-window", actionNewWindow, null),
|
||||
.init("ring-bell", actionRingBell, null),
|
||||
@@ -961,7 +964,14 @@ pub const Window = extern struct {
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
self.addToast(i18n._("Reloaded the configuration"));
|
||||
const priv = self.private();
|
||||
if (priv.config) |config_obj| {
|
||||
const config = config_obj.get();
|
||||
if (config.@"app-notifications".@"config-reload") {
|
||||
self.addToast(i18n._("Reloaded the configuration"));
|
||||
}
|
||||
}
|
||||
|
||||
self.syncAppearance();
|
||||
}
|
||||
|
||||
@@ -980,6 +990,22 @@ pub const Window = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn propIsActive(
|
||||
_: *gtk.Window,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// Don't change urgency if we're not the active window.
|
||||
if (self.as(gtk.Window).isActive() == 0) return;
|
||||
|
||||
self.winproto().setUrgent(false) catch |err| {
|
||||
log.warn(
|
||||
"winproto failed to reset urgency={}",
|
||||
.{err},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn propGdkSurfaceWidth(
|
||||
_: *gdk.Surface,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -1656,10 +1682,31 @@ pub const Window = extern struct {
|
||||
|
||||
fn actionCloseTab(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
param_: ?*glib.Variant,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
self.performBindingAction(.close_tab);
|
||||
const param = param_ orelse {
|
||||
log.warn("win.close-tab called without a parameter", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
var str: ?[*:0]const u8 = null;
|
||||
param.get("&s", &str);
|
||||
|
||||
const mode = std.meta.stringToEnum(
|
||||
input.Binding.Action.CloseTabMode,
|
||||
std.mem.span(
|
||||
str orelse {
|
||||
log.warn("invalid mode provided to win.close-tab", .{});
|
||||
return;
|
||||
},
|
||||
),
|
||||
) orelse {
|
||||
log.warn("invalid mode provided to win.close-tab: {s}", .{str.?});
|
||||
return;
|
||||
};
|
||||
|
||||
self.performBindingAction(.{ .close_tab = mode });
|
||||
}
|
||||
|
||||
fn actionNewWindow(
|
||||
@@ -1758,10 +1805,13 @@ pub const Window = extern struct {
|
||||
native.beep();
|
||||
}
|
||||
|
||||
if (config.@"bell-features".attention) {
|
||||
if (config.@"bell-features".attention) attention: {
|
||||
// Dont set urgency if the window is already active.
|
||||
if (self.as(gtk.Window).isActive() != 0) break :attention;
|
||||
|
||||
// Request user attention
|
||||
self.winproto().setUrgent(true) catch |err| {
|
||||
log.warn("failed to request user attention={}", .{err});
|
||||
log.warn("winproto failed to set urgency={}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1905,6 +1955,7 @@ pub const Window = extern struct {
|
||||
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
|
||||
class.bindTemplateCallback("notify_config", &propConfig);
|
||||
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
|
||||
class.bindTemplateCallback("notify_is_active", &propIsActive);
|
||||
class.bindTemplateCallback("notify_maximized", &propMaximized);
|
||||
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
|
||||
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
|
||||
|
@@ -12,7 +12,7 @@ window.ssd.no-border-radius {
|
||||
border-radius: 0 0;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* GhosttySurface URL overlay
|
||||
*/
|
||||
label.url-overlay {
|
||||
@@ -83,13 +83,13 @@ label.resize-overlay {
|
||||
*/
|
||||
.child-exited.normal revealer widget {
|
||||
background-color: rgba(38, 162, 105, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following:
|
||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||
/* background-color: color-mix(in srgb, var(--success-bg-color), transparent 50%); */
|
||||
}
|
||||
|
||||
.child-exited.abnormal revealer widget {
|
||||
background-color: rgba(192, 28, 40, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following:
|
||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
|
||||
}
|
||||
|
||||
@@ -97,13 +97,15 @@ label.resize-overlay {
|
||||
* Surface
|
||||
*/
|
||||
.surface progressbar.error trough progress {
|
||||
background-color: rgb(192, 28, 40);
|
||||
background-color: rgba(192, 28, 40, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent); */
|
||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
|
||||
}
|
||||
|
||||
.surface .bell-overlay {
|
||||
border-color: color-mix(in srgb, var(--accent-color), transparent 50%);
|
||||
border-color: rgba(58, 148, 74, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||
/* background-color: color-mix(in srgb, var(--accent-color), transparent 50%); */
|
||||
border-width: 3px;
|
||||
border-style: solid;
|
||||
}
|
||||
@@ -127,6 +129,8 @@ label.resize-overlay {
|
||||
|
||||
.window .split paned > separator {
|
||||
background-color: rgba(250, 250, 250, 1);
|
||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||
/* background-color: color-mix(in srgb, var(--window-bg-color), transparent 0%); */
|
||||
background-clip: content-box;
|
||||
|
||||
/* This works around the oversized drag area for the right side of GtkPaned.
|
||||
|
189
src/apprt/gtk-ng/ipc/DBus.zig
Normal file
189
src/apprt/gtk-ng/ipc/DBus.zig
Normal file
@@ -0,0 +1,189 @@
|
||||
//! DBus helper for IPC
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const ApprtApp = @import("../App.zig");
|
||||
|
||||
/// The target for this IPC.
|
||||
target: apprt.ipc.Target,
|
||||
|
||||
/// Connection to the DBus session bus.
|
||||
dbus: *gio.DBusConnection,
|
||||
|
||||
/// The bus name of the Ghostty instance that we are calling.
|
||||
bus_name: [:0]const u8,
|
||||
|
||||
/// The object path of the Ghostty instance that we are calling.
|
||||
object_path: [:0]const u8,
|
||||
|
||||
/// Used to build the DBus payload.
|
||||
payload_builder: *glib.VariantBuilder,
|
||||
|
||||
/// Used to build the parameters for the IPC.
|
||||
parameters_builder: *glib.VariantBuilder,
|
||||
|
||||
/// Initialize the helper.
|
||||
pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!Self {
|
||||
|
||||
// Get the appropriate bus name and object path for contacting the
|
||||
// Ghostty instance we're interested in.
|
||||
const bus_name: [:0]const u8, const object_path: [:0]const u8 = switch (target) {
|
||||
.class => |class| result: {
|
||||
// Force the usage of the class specified on the CLI to determine the
|
||||
// bus name and object path.
|
||||
const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class});
|
||||
|
||||
std.mem.replaceScalar(u8, object_path, '.', '/');
|
||||
std.mem.replaceScalar(u8, object_path, '-', '_');
|
||||
|
||||
break :result .{ class, object_path };
|
||||
},
|
||||
.detect => .{ ApprtApp.application_id, ApprtApp.object_path },
|
||||
};
|
||||
errdefer {
|
||||
switch (target) {
|
||||
.class => alloc.free(object_path),
|
||||
.detect => {},
|
||||
}
|
||||
}
|
||||
|
||||
if (gio.Application.idIsValid(bus_name.ptr) == 0) {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name});
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
if (glib.Variant.isObjectPath(object_path.ptr) == 0) {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path});
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
// Get a connection to the DBus session bus.
|
||||
const dbus = dbus: {
|
||||
var err_: ?*glib.Error = null;
|
||||
defer if (err_) |err| err.free();
|
||||
|
||||
const dbus_ = gio.busGetSync(.session, null, &err_);
|
||||
if (err_) |err| {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print(
|
||||
"Unable to establish connection to D-Bus session bus: {s}\n",
|
||||
.{err.f_message orelse "(unknown)"},
|
||||
);
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
break :dbus dbus_ orelse {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print("gio.busGetSync returned null\n", .{});
|
||||
return error.IPCFailed;
|
||||
};
|
||||
};
|
||||
|
||||
// Set up the payload builder.
|
||||
const payload_variant_type = glib.VariantType.new("(sava{sv})");
|
||||
defer glib.free(payload_variant_type);
|
||||
|
||||
const payload_builder = glib.VariantBuilder.new(payload_variant_type);
|
||||
|
||||
// Add the action name to the payload.
|
||||
{
|
||||
const s_variant_type = glib.VariantType.new("s");
|
||||
defer s_variant_type.free();
|
||||
|
||||
const bytes = glib.Bytes.new(action.ptr, action.len + 1);
|
||||
defer bytes.unref();
|
||||
const value = glib.Variant.newFromBytes(s_variant_type, bytes, @intFromBool(true));
|
||||
|
||||
payload_builder.addValue(value);
|
||||
}
|
||||
|
||||
// Set up the parameter builder.
|
||||
const parameters_variant_type = glib.VariantType.new("av");
|
||||
defer parameters_variant_type.free();
|
||||
|
||||
const parameters_builder = glib.VariantBuilder.new(parameters_variant_type);
|
||||
|
||||
return .{
|
||||
.target = target,
|
||||
.dbus = dbus,
|
||||
.bus_name = bus_name,
|
||||
.object_path = object_path,
|
||||
.payload_builder = payload_builder,
|
||||
.parameters_builder = parameters_builder,
|
||||
};
|
||||
}
|
||||
|
||||
/// Add a parameter to the IPC call.
|
||||
pub fn addParameter(self: *Self, variant: *glib.Variant) void {
|
||||
self.parameters_builder.add("v", variant);
|
||||
}
|
||||
|
||||
/// Send the IPC to the remote Ghostty. Once it completes, nothing further
|
||||
/// should be done with this object other than call `deinit`.
|
||||
pub fn send(self: *Self) (std.posix.WriteError || apprt.ipc.Errors)!void {
|
||||
// finish building the parameters
|
||||
const parameters = self.parameters_builder.end();
|
||||
|
||||
// Add the parameters to the payload.
|
||||
self.payload_builder.addValue(parameters);
|
||||
|
||||
// Add the platform data to the payload.
|
||||
{
|
||||
const platform_data_variant_type = glib.VariantType.new("a{sv}");
|
||||
defer platform_data_variant_type.free();
|
||||
|
||||
self.payload_builder.open(platform_data_variant_type);
|
||||
defer self.payload_builder.close();
|
||||
|
||||
// We have no platform data.
|
||||
}
|
||||
|
||||
const payload = self.payload_builder.end();
|
||||
|
||||
{
|
||||
var err_: ?*glib.Error = null;
|
||||
defer if (err_) |err| err.free();
|
||||
|
||||
const result_ = self.dbus.callSync(
|
||||
self.bus_name,
|
||||
self.object_path,
|
||||
"org.gtk.Actions",
|
||||
"Activate",
|
||||
payload,
|
||||
null, // We don't care about the return type, we don't do anything with it.
|
||||
.{}, // no flags
|
||||
-1, // default timeout
|
||||
null, // not cancellable
|
||||
&err_,
|
||||
);
|
||||
defer if (result_) |result| result.unref();
|
||||
|
||||
if (err_) |err| {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
try stderr.print(
|
||||
"D-Bus method call returned an error err={s}\n",
|
||||
.{err.f_message orelse "(unknown)"},
|
||||
);
|
||||
return error.IPCFailed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free/unref any data held by this instance.
|
||||
pub fn deinit(self: *Self, alloc: Allocator) void {
|
||||
switch (self.target) {
|
||||
.class => alloc.free(self.object_path),
|
||||
.detect => {},
|
||||
}
|
||||
self.parameters_builder.unref();
|
||||
self.payload_builder.unref();
|
||||
self.dbus.unref();
|
||||
}
|
@@ -1,11 +1,10 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const ApprtApp = @import("../App.zig");
|
||||
const DBus = @import("DBus.zig");
|
||||
|
||||
// Use a D-Bus method call to open a new window on GTK.
|
||||
// See: https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
|
||||
@@ -22,149 +21,42 @@ const ApprtApp = @import("../App.zig");
|
||||
// gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' []
|
||||
// ```
|
||||
pub fn newWindow(alloc: Allocator, target: apprt.ipc.Target, value: apprt.ipc.Action.NewWindow) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool {
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
var dbus = try DBus.init(
|
||||
alloc,
|
||||
target,
|
||||
if (value.arguments == null)
|
||||
"new-window"
|
||||
else
|
||||
"new-window-command",
|
||||
);
|
||||
defer dbus.deinit(alloc);
|
||||
|
||||
// Get the appropriate bus name and object path for contacting the
|
||||
// Ghostty instance we're interested in.
|
||||
const bus_name: [:0]const u8, const object_path: [:0]const u8 = switch (target) {
|
||||
.class => |class| result: {
|
||||
// Force the usage of the class specified on the CLI to determine the
|
||||
// bus name and object path.
|
||||
const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class});
|
||||
if (value.arguments) |arguments| {
|
||||
// If `-e` was specified on the command line, the first
|
||||
// parameter is an array of strings that contain the arguments
|
||||
// that came after `-e`, which will be interpreted as a command
|
||||
// to run.
|
||||
const as_variant_type = glib.VariantType.new("as");
|
||||
defer as_variant_type.free();
|
||||
|
||||
std.mem.replaceScalar(u8, object_path, '.', '/');
|
||||
std.mem.replaceScalar(u8, object_path, '-', '_');
|
||||
const s_variant_type = glib.VariantType.new("s");
|
||||
defer s_variant_type.free();
|
||||
|
||||
break :result .{ class, object_path };
|
||||
},
|
||||
.detect => .{ ApprtApp.application_id, ApprtApp.object_path },
|
||||
};
|
||||
defer {
|
||||
switch (target) {
|
||||
.class => alloc.free(object_path),
|
||||
.detect => {},
|
||||
var command: glib.VariantBuilder = undefined;
|
||||
command.init(as_variant_type);
|
||||
errdefer command.clear();
|
||||
|
||||
for (arguments) |argument| {
|
||||
const bytes = glib.Bytes.new(argument.ptr, argument.len + 1);
|
||||
defer bytes.unref();
|
||||
const string = glib.Variant.newFromBytes(s_variant_type, bytes, @intFromBool(true));
|
||||
command.addValue(string);
|
||||
}
|
||||
|
||||
dbus.addParameter(command.end());
|
||||
}
|
||||
|
||||
if (gio.Application.idIsValid(bus_name.ptr) == 0) {
|
||||
try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name});
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
if (glib.Variant.isObjectPath(object_path.ptr) == 0) {
|
||||
try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path});
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
const dbus = dbus: {
|
||||
var err_: ?*glib.Error = null;
|
||||
defer if (err_) |err| err.free();
|
||||
|
||||
const dbus_ = gio.busGetSync(.session, null, &err_);
|
||||
if (err_) |err| {
|
||||
try stderr.print(
|
||||
"Unable to establish connection to D-Bus session bus: {s}\n",
|
||||
.{err.f_message orelse "(unknown)"},
|
||||
);
|
||||
return error.IPCFailed;
|
||||
}
|
||||
|
||||
break :dbus dbus_ orelse {
|
||||
try stderr.print("gio.busGetSync returned null\n", .{});
|
||||
return error.IPCFailed;
|
||||
};
|
||||
};
|
||||
defer dbus.unref();
|
||||
|
||||
// use a builder to create the D-Bus method call payload
|
||||
const payload = payload: {
|
||||
const payload_variant_type = glib.VariantType.new("(sava{sv})");
|
||||
defer glib.free(payload_variant_type);
|
||||
|
||||
// Initialize our builder to build up our parameters
|
||||
var builder: glib.VariantBuilder = undefined;
|
||||
builder.init(payload_variant_type);
|
||||
errdefer builder.clear();
|
||||
|
||||
// action
|
||||
if (value.arguments == null) {
|
||||
builder.add("s", "new-window");
|
||||
} else {
|
||||
builder.add("s", "new-window-command");
|
||||
}
|
||||
|
||||
// parameters
|
||||
{
|
||||
const av_variant_type = glib.VariantType.new("av");
|
||||
defer av_variant_type.free();
|
||||
|
||||
var parameters: glib.VariantBuilder = undefined;
|
||||
parameters.init(av_variant_type);
|
||||
errdefer parameters.clear();
|
||||
|
||||
if (value.arguments) |arguments| {
|
||||
// If `-e` was specified on the command line, the first
|
||||
// parameter is an array of strings that contain the arguments
|
||||
// that came after `-e`, which will be interpreted as a command
|
||||
// to run.
|
||||
{
|
||||
const as = glib.VariantType.new("as");
|
||||
defer as.free();
|
||||
|
||||
var command: glib.VariantBuilder = undefined;
|
||||
command.init(as);
|
||||
errdefer command.clear();
|
||||
|
||||
for (arguments) |argument| {
|
||||
command.add("s", argument.ptr);
|
||||
}
|
||||
|
||||
parameters.add("v", command.end());
|
||||
}
|
||||
}
|
||||
|
||||
builder.addValue(parameters.end());
|
||||
}
|
||||
|
||||
{
|
||||
const platform_data_variant_type = glib.VariantType.new("a{sv}");
|
||||
defer platform_data_variant_type.free();
|
||||
|
||||
builder.open(platform_data_variant_type);
|
||||
defer builder.close();
|
||||
|
||||
// we have no platform data
|
||||
}
|
||||
|
||||
break :payload builder.end();
|
||||
};
|
||||
|
||||
{
|
||||
var err_: ?*glib.Error = null;
|
||||
defer if (err_) |err| err.free();
|
||||
|
||||
const result_ = dbus.callSync(
|
||||
bus_name,
|
||||
object_path,
|
||||
"org.gtk.Actions",
|
||||
"Activate",
|
||||
payload,
|
||||
null, // We don't care about the return type, we don't do anything with it.
|
||||
.{}, // no flags
|
||||
-1, // default timeout
|
||||
null, // not cancellable
|
||||
&err_,
|
||||
);
|
||||
defer if (result_) |result| result.unref();
|
||||
|
||||
if (err_) |err| {
|
||||
try stderr.print(
|
||||
"D-Bus method call returned an error err={s}\n",
|
||||
.{err.f_message orelse "(unknown)"},
|
||||
);
|
||||
return error.IPCFailed;
|
||||
}
|
||||
}
|
||||
try dbus.send();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -7,4 +7,6 @@ template $GhosttyCloseConfirmationDialog: $GhosttyDialog {
|
||||
cancel: _("Cancel"),
|
||||
close: _("Close") destructive,
|
||||
]
|
||||
|
||||
close-response: "cancel";
|
||||
}
|
||||
|
@@ -8,146 +8,172 @@ template $GhosttySurface: Adw.Bin {
|
||||
|
||||
notify::bell-ringing => $notify_bell_ringing();
|
||||
notify::config => $notify_config();
|
||||
notify::error => $notify_error();
|
||||
notify::mouse-hover-url => $notify_mouse_hover_url();
|
||||
notify::mouse-hidden => $notify_mouse_hidden();
|
||||
notify::mouse-shape => $notify_mouse_shape();
|
||||
|
||||
Overlay {
|
||||
focusable: false;
|
||||
focus-on-click: false;
|
||||
Stack {
|
||||
StackPage {
|
||||
name: "terminal";
|
||||
|
||||
child: Box {
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
child: Overlay {
|
||||
focusable: false;
|
||||
focus-on-click: false;
|
||||
|
||||
GLArea gl_area {
|
||||
realize => $gl_realize();
|
||||
unrealize => $gl_unrealize();
|
||||
render => $gl_render();
|
||||
resize => $gl_resize();
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
focusable: true;
|
||||
focus-on-click: true;
|
||||
has-stencil-buffer: false;
|
||||
has-depth-buffer: false;
|
||||
allowed-apis: gl;
|
||||
}
|
||||
child: Box {
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
|
||||
PopoverMenu context_menu {
|
||||
closed => $context_menu_closed();
|
||||
menu-model: context_menu_model;
|
||||
flags: nested;
|
||||
halign: start;
|
||||
has-arrow: false;
|
||||
}
|
||||
};
|
||||
GLArea gl_area {
|
||||
realize => $gl_realize();
|
||||
unrealize => $gl_unrealize();
|
||||
render => $gl_render();
|
||||
resize => $gl_resize();
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
focusable: true;
|
||||
focus-on-click: true;
|
||||
has-stencil-buffer: false;
|
||||
has-depth-buffer: false;
|
||||
allowed-apis: gl;
|
||||
}
|
||||
|
||||
[overlay]
|
||||
ProgressBar progress_bar_overlay {
|
||||
styles [
|
||||
"osd",
|
||||
]
|
||||
PopoverMenu context_menu {
|
||||
closed => $context_menu_closed();
|
||||
menu-model: context_menu_model;
|
||||
flags: nested;
|
||||
halign: start;
|
||||
has-arrow: false;
|
||||
}
|
||||
};
|
||||
|
||||
visible: false;
|
||||
halign: fill;
|
||||
valign: start;
|
||||
[overlay]
|
||||
ProgressBar progress_bar_overlay {
|
||||
styles [
|
||||
"osd",
|
||||
]
|
||||
|
||||
visible: false;
|
||||
halign: fill;
|
||||
valign: start;
|
||||
}
|
||||
|
||||
[overlay]
|
||||
// The "border" bell feature is implemented here as an overlay rather than
|
||||
// just adding a border to the GLArea or other widget for two reasons.
|
||||
// First, adding a border to an existing widget causes a resize of the
|
||||
// widget which undesirable side effects. Second, we can make it reactive
|
||||
// here in the blueprint with relatively little code.
|
||||
Revealer {
|
||||
reveal-child: bind $should_border_be_shown(template.config, template.bell-ringing) as <bool>;
|
||||
transition-type: crossfade;
|
||||
transition-duration: 500;
|
||||
|
||||
Box bell_overlay {
|
||||
styles [
|
||||
"bell-overlay",
|
||||
]
|
||||
|
||||
halign: fill;
|
||||
valign: fill;
|
||||
}
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttySurfaceChildExited child_exited_overlay {
|
||||
visible: bind template.child-exited;
|
||||
close-request => $child_exited_close();
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttyResizeOverlay resize_overlay {}
|
||||
|
||||
[overlay]
|
||||
Label url_left {
|
||||
styles [
|
||||
"background",
|
||||
"url-overlay",
|
||||
]
|
||||
|
||||
visible: false;
|
||||
halign: start;
|
||||
valign: end;
|
||||
label: bind template.mouse-hover-url;
|
||||
|
||||
EventControllerMotion url_ec_motion {
|
||||
enter => $url_mouse_enter();
|
||||
leave => $url_mouse_leave();
|
||||
}
|
||||
}
|
||||
|
||||
[overlay]
|
||||
Label url_right {
|
||||
styles [
|
||||
"background",
|
||||
"url-overlay",
|
||||
]
|
||||
|
||||
visible: false;
|
||||
halign: end;
|
||||
valign: end;
|
||||
label: bind template.mouse-hover-url;
|
||||
}
|
||||
|
||||
// Event controllers for interactivity
|
||||
EventControllerFocus {
|
||||
enter => $focus_enter();
|
||||
leave => $focus_leave();
|
||||
}
|
||||
|
||||
EventControllerKey {
|
||||
key-pressed => $key_pressed();
|
||||
key-released => $key_released();
|
||||
}
|
||||
|
||||
EventControllerMotion {
|
||||
motion => $mouse_motion();
|
||||
leave => $mouse_leave();
|
||||
}
|
||||
|
||||
EventControllerScroll {
|
||||
scroll => $scroll();
|
||||
scroll-begin => $scroll_begin();
|
||||
scroll-end => $scroll_end();
|
||||
flags: both_axes;
|
||||
}
|
||||
|
||||
GestureClick {
|
||||
pressed => $mouse_down();
|
||||
released => $mouse_up();
|
||||
button: 0;
|
||||
}
|
||||
|
||||
DropTarget drop_target {
|
||||
drop => $drop();
|
||||
actions: copy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[overlay]
|
||||
// The "border" bell feature is implemented here as an overlay rather than
|
||||
// just adding a border to the GLArea or other widget for two reasons.
|
||||
// First, adding a border to an existing widget causes a resize of the
|
||||
// widget which undesirable side effects. Second, we can make it reactive
|
||||
// here in the blueprint with relatively little code.
|
||||
Revealer {
|
||||
reveal-child: bind $should_border_be_shown(template.config, template.bell-ringing) as <bool>;
|
||||
transition-type: crossfade;
|
||||
transition-duration: 500;
|
||||
StackPage {
|
||||
name: "error";
|
||||
|
||||
Box bell_overlay {
|
||||
styles [
|
||||
"bell-overlay",
|
||||
]
|
||||
child: Adw.StatusPage {
|
||||
icon-name: "computer-fail-symbolic";
|
||||
title: _("Oh, no.");
|
||||
description: _("Unable to acquire an OpenGL context for rendering.");
|
||||
|
||||
halign: fill;
|
||||
valign: fill;
|
||||
}
|
||||
child: LinkButton {
|
||||
label: "https://ghostty.org/docs/help/gtk-opengl-context";
|
||||
uri: "https://ghostty.org/docs/help/gtk-opengl-context";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttySurfaceChildExited child_exited_overlay {
|
||||
visible: bind template.child-exited;
|
||||
close-request => $child_exited_close();
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttyResizeOverlay resize_overlay {}
|
||||
|
||||
[overlay]
|
||||
Label url_left {
|
||||
styles [
|
||||
"background",
|
||||
"url-overlay",
|
||||
]
|
||||
|
||||
visible: false;
|
||||
halign: start;
|
||||
valign: end;
|
||||
label: bind template.mouse-hover-url;
|
||||
|
||||
EventControllerMotion url_ec_motion {
|
||||
enter => $url_mouse_enter();
|
||||
leave => $url_mouse_leave();
|
||||
}
|
||||
}
|
||||
|
||||
[overlay]
|
||||
Label url_right {
|
||||
styles [
|
||||
"background",
|
||||
"url-overlay",
|
||||
]
|
||||
|
||||
visible: false;
|
||||
halign: end;
|
||||
valign: end;
|
||||
label: bind template.mouse-hover-url;
|
||||
}
|
||||
}
|
||||
|
||||
// Event controllers for interactivity
|
||||
EventControllerFocus {
|
||||
enter => $focus_enter();
|
||||
leave => $focus_leave();
|
||||
}
|
||||
|
||||
EventControllerKey {
|
||||
key-pressed => $key_pressed();
|
||||
key-released => $key_released();
|
||||
}
|
||||
|
||||
EventControllerMotion {
|
||||
motion => $mouse_motion();
|
||||
leave => $mouse_leave();
|
||||
}
|
||||
|
||||
EventControllerScroll {
|
||||
scroll => $scroll();
|
||||
scroll-begin => $scroll_begin();
|
||||
scroll-end => $scroll_end();
|
||||
flags: both_axes;
|
||||
}
|
||||
|
||||
GestureClick {
|
||||
pressed => $mouse_down();
|
||||
released => $mouse_up();
|
||||
button: 0;
|
||||
}
|
||||
|
||||
DropTarget drop_target {
|
||||
drop => $drop();
|
||||
actions: copy;
|
||||
// The order matters here: we can only set this after the stack
|
||||
// pages above have been created.
|
||||
visible-child-name: bind $stack_child_name(template.error) as <string>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +254,8 @@ menu context_menu_model {
|
||||
|
||||
item {
|
||||
label: _("Close Tab");
|
||||
action: "win.close-tab";
|
||||
action: "tab.close";
|
||||
target: "this";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
||||
realize => $realize();
|
||||
notify::config => $notify_config();
|
||||
notify::fullscreened => $notify_fullscreened();
|
||||
notify::is-active => $notify_is_active();
|
||||
notify::maximized => $notify_maximized();
|
||||
notify::quick-terminal => $notify_quick_terminal();
|
||||
notify::scale-factor => $notify_scale_factor();
|
||||
@@ -225,6 +226,7 @@ menu main_menu {
|
||||
item {
|
||||
label: _("Close Tab");
|
||||
action: "win.close-tab";
|
||||
target: "this";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -491,7 +491,7 @@ pub fn performAction(
|
||||
.toggle_maximize => self.toggleMaximize(target),
|
||||
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
||||
.new_tab => try self.newTab(target),
|
||||
.close_tab => return try self.closeTab(target),
|
||||
.close_tab => return try self.closeTab(target, value),
|
||||
.goto_tab => return self.gotoTab(target, value),
|
||||
.move_tab => self.moveTab(target, value),
|
||||
.new_split => try self.newSplit(target, value),
|
||||
@@ -585,7 +585,7 @@ fn newTab(_: *App, target: apprt.Target) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn closeTab(_: *App, target: apprt.Target) !bool {
|
||||
fn closeTab(_: *App, target: apprt.Target, value: apprt.Action.Value(.close_tab)) !bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |v| {
|
||||
@@ -597,8 +597,16 @@ fn closeTab(_: *App, target: apprt.Target) !bool {
|
||||
return false;
|
||||
};
|
||||
|
||||
tab.closeWithConfirmation();
|
||||
return true;
|
||||
switch (value) {
|
||||
.this => {
|
||||
tab.closeWithConfirmation();
|
||||
return true;
|
||||
},
|
||||
.other => {
|
||||
log.warn("close-tab:other is not implemented", .{});
|
||||
return false;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1145,7 +1153,7 @@ fn syncActionAccelerators(self: *App) !void {
|
||||
try self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||
try self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||
try self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||
try self.syncActionAccelerator("win.close-tab", .{ .close_tab = {} });
|
||||
try self.syncActionAccelerator("win.close-tab", .{ .close_tab = .this });
|
||||
try self.syncActionAccelerator("win.split-right", .{ .new_split = .right });
|
||||
try self.syncActionAccelerator("win.split-down", .{ .new_split = .down });
|
||||
try self.syncActionAccelerator("win.split-left", .{ .new_split = .left });
|
||||
|
@@ -772,7 +772,9 @@ pub fn focusCurrentTab(self: *Window) void {
|
||||
}
|
||||
|
||||
pub fn onConfigReloaded(self: *Window) void {
|
||||
self.sendToast(i18n._("Reloaded the configuration"));
|
||||
if (self.app.config.@"app-notifications".@"config-reload") {
|
||||
self.sendToast(i18n._("Reloaded the configuration"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendToast(self: *Window, title: [*:0]const u8) void {
|
||||
@@ -1074,7 +1076,7 @@ fn gtkActionCloseTab(
|
||||
_: ?*glib.Variant,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
self.performBindingAction(.{ .close_tab = {} });
|
||||
self.performBindingAction(.{ .close_tab = .this });
|
||||
}
|
||||
|
||||
fn gtkActionSplitRight(
|
||||
|
@@ -24,6 +24,8 @@ pub const CursorPos = struct {
|
||||
pub const IMEPos = struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
};
|
||||
|
||||
/// The clipboard type.
|
||||
|
@@ -131,6 +131,13 @@ pub const VTable = struct {
|
||||
};
|
||||
|
||||
test Benchmark {
|
||||
// This test fails on FreeBSD so skip:
|
||||
//
|
||||
// /home/runner/work/ghostty/ghostty/src/benchmark/Benchmark.zig:165:5: 0x3cd2de1 in decltest.Benchmark (ghostty-test)
|
||||
// try testing.expect(result.duration > 0);
|
||||
// ^
|
||||
if (builtin.os.tag == .freebsd) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const Simple = struct {
|
||||
const Self = @This();
|
||||
|
@@ -1,13 +1,20 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Action = @import("ghostty.zig").Action;
|
||||
const args = @import("args.zig");
|
||||
const x11_color = @import("../terminal/main.zig").x11_color;
|
||||
const vaxis = @import("vaxis");
|
||||
const tui = @import("tui.zig");
|
||||
|
||||
pub const Options = struct {
|
||||
pub fn deinit(self: Options) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
/// If `true`, print without formatting even if printing to a tty
|
||||
plain: bool = false,
|
||||
|
||||
/// Enables "-h" and "--help" to work.
|
||||
pub fn help(self: Options) !void {
|
||||
_ = self;
|
||||
@@ -17,7 +24,12 @@ pub const Options = struct {
|
||||
|
||||
/// The `list-colors` command is used to list all the named RGB colors in
|
||||
/// Ghostty.
|
||||
pub fn run(alloc: std.mem.Allocator) !u8 {
|
||||
///
|
||||
/// Flags:
|
||||
///
|
||||
/// * `--plain`: will disable formatting and make the output more
|
||||
/// friendly for Unix tooling. This is default when not printing to a tty.
|
||||
pub fn run(alloc: Allocator) !u8 {
|
||||
var opts: Options = .{};
|
||||
defer opts.deinit();
|
||||
|
||||
@@ -27,7 +39,7 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
|
||||
try args.parse(Options, alloc, &opts, &iter);
|
||||
}
|
||||
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stdout = std.io.getStdOut();
|
||||
|
||||
var keys = std.ArrayList([]const u8).init(alloc);
|
||||
defer keys.deinit();
|
||||
@@ -39,15 +51,163 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
|
||||
}
|
||||
}.lessThan);
|
||||
|
||||
for (keys.items) |name| {
|
||||
const rgb = x11_color.map.get(name).?;
|
||||
try stdout.print("{s} = #{x:0>2}{x:0>2}{x:0>2}\n", .{
|
||||
name,
|
||||
rgb.r,
|
||||
rgb.g,
|
||||
rgb.b,
|
||||
});
|
||||
// Despite being under the posix namespace, this also works on Windows as of zig 0.13.0
|
||||
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) {
|
||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||
defer arena.deinit();
|
||||
return prettyPrint(arena.allocator(), keys.items);
|
||||
} else {
|
||||
const writer = stdout.writer();
|
||||
for (keys.items) |name| {
|
||||
const rgb = x11_color.map.get(name).?;
|
||||
try writer.print("{s} = #{x:0>2}{x:0>2}{x:0>2}\n", .{
|
||||
name,
|
||||
rgb.r,
|
||||
rgb.g,
|
||||
rgb.b,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 {
|
||||
// Set up vaxis
|
||||
var tty = try vaxis.Tty.init();
|
||||
defer tty.deinit();
|
||||
var vx = try vaxis.init(alloc, .{});
|
||||
defer vx.deinit(alloc, tty.anyWriter());
|
||||
|
||||
// We know we are ghostty, so let's enable mode 2027. Vaxis normally does this but you need an
|
||||
// event loop to auto-enable it.
|
||||
vx.caps.unicode = .unicode;
|
||||
try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set);
|
||||
defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {};
|
||||
|
||||
var buf_writer = tty.bufferedWriter();
|
||||
const writer = buf_writer.writer().any();
|
||||
|
||||
const winsize: vaxis.Winsize = switch (builtin.os.tag) {
|
||||
// We use some default, it doesn't really matter for what
|
||||
// we're doing because we don't do any wrapping.
|
||||
.windows => .{
|
||||
.rows = 24,
|
||||
.cols = 120,
|
||||
.x_pixel = 1024,
|
||||
.y_pixel = 768,
|
||||
},
|
||||
|
||||
else => try vaxis.Tty.getWinsize(tty.fd),
|
||||
};
|
||||
try vx.resize(alloc, tty.anyWriter(), winsize);
|
||||
|
||||
const win = vx.window();
|
||||
|
||||
var max_name_len: usize = 0;
|
||||
for (keys) |name| {
|
||||
if (name.len > max_name_len) max_name_len = name.len;
|
||||
}
|
||||
|
||||
// max name length plus " = #RRGGBB XX" plus " " gutter between columns
|
||||
const column_size = max_name_len + 15;
|
||||
// add two to take into account lack of gutter after last column
|
||||
const columns: usize = @divFloor(win.width + 2, column_size);
|
||||
|
||||
var i: usize = 0;
|
||||
const step = @divFloor(keys.len, columns) + 1;
|
||||
while (i < step) : (i += 1) {
|
||||
win.clear();
|
||||
|
||||
var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false };
|
||||
|
||||
for (0..columns) |j| {
|
||||
const k = i + (step * j);
|
||||
if (k >= keys.len) continue;
|
||||
|
||||
const name = keys[k];
|
||||
const rgb = x11_color.map.get(name).?;
|
||||
|
||||
const style1: vaxis.Style = .{
|
||||
.fg = .{
|
||||
.rgb = .{ rgb.r, rgb.g, rgb.b },
|
||||
},
|
||||
};
|
||||
const style2: vaxis.Style = .{
|
||||
.fg = .{
|
||||
.rgb = .{ rgb.r, rgb.g, rgb.b },
|
||||
},
|
||||
.bg = .{
|
||||
.rgb = .{ rgb.r, rgb.g, rgb.b },
|
||||
},
|
||||
};
|
||||
|
||||
// name of the color
|
||||
result = win.printSegment(
|
||||
.{ .text = name },
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
// push the color data to the end of the column
|
||||
for (0..max_name_len - name.len) |_| {
|
||||
result = win.printSegment(
|
||||
.{ .text = " " },
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
}
|
||||
result = win.printSegment(
|
||||
.{ .text = " = " },
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
// rgb triple
|
||||
result = win.printSegment(.{
|
||||
.text = try std.fmt.allocPrint(
|
||||
alloc,
|
||||
"#{x:0>2}{x:0>2}{x:0>2}",
|
||||
.{
|
||||
rgb.r, rgb.g, rgb.b,
|
||||
},
|
||||
),
|
||||
.style = style1,
|
||||
}, .{ .col_offset = result.col });
|
||||
result = win.printSegment(
|
||||
.{ .text = " " },
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
// colored block
|
||||
result = win.printSegment(
|
||||
.{
|
||||
.text = " ",
|
||||
.style = style2,
|
||||
},
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
// add the gutter if needed
|
||||
if (j + 1 < columns) {
|
||||
result = win.printSegment(
|
||||
.{
|
||||
.text = " ",
|
||||
},
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the rest of the line
|
||||
while (result.col != 0) {
|
||||
result = win.printSegment(
|
||||
.{
|
||||
.text = " ",
|
||||
},
|
||||
.{ .col_offset = result.col },
|
||||
);
|
||||
}
|
||||
|
||||
// output the data
|
||||
try vx.prettyPrint(writer);
|
||||
}
|
||||
|
||||
// be sure to flush!
|
||||
try buf_writer.flush();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ const zf = @import("zf");
|
||||
// scroll position for larger lists.
|
||||
const SMALL_LIST_THRESHOLD = 10;
|
||||
|
||||
const ColorScheme = enum { all, dark, light };
|
||||
|
||||
pub const Options = struct {
|
||||
/// If true, print the full path to the theme.
|
||||
path: bool = false,
|
||||
@@ -25,7 +27,7 @@ pub const Options = struct {
|
||||
plain: bool = false,
|
||||
|
||||
/// Specifies the color scheme of the themes to include in the list.
|
||||
color: enum { all, dark, light } = .all,
|
||||
color: ColorScheme = .all,
|
||||
|
||||
pub fn deinit(self: Options) void {
|
||||
_ = self;
|
||||
@@ -146,28 +148,11 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
|
||||
count += 1;
|
||||
|
||||
const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
|
||||
// if there is no need to filter just append the theme to the list
|
||||
if (opts.color == .all) {
|
||||
try themes.append(.{
|
||||
.path = path,
|
||||
.location = loc.location,
|
||||
.theme = try alloc.dupe(u8, entry.name),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise check if the theme should be included based on the provided options
|
||||
var config = try Config.default(alloc);
|
||||
defer config.deinit();
|
||||
try config.loadFile(config._arena.?.allocator(), path);
|
||||
|
||||
if (shouldIncludeTheme(opts, config)) {
|
||||
try themes.append(.{
|
||||
.path = path,
|
||||
.location = loc.location,
|
||||
.theme = try alloc.dupe(u8, entry.name),
|
||||
});
|
||||
}
|
||||
try themes.append(.{
|
||||
.path = path,
|
||||
.location = loc.location,
|
||||
.theme = try alloc.dupe(u8, entry.name),
|
||||
});
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -182,7 +167,7 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
|
||||
std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan);
|
||||
|
||||
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(std.io.getStdOut().handle)) {
|
||||
try preview(gpa_alloc, themes.items);
|
||||
try preview(gpa_alloc, themes.items, opts.color);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -222,8 +207,9 @@ const Preview = struct {
|
||||
},
|
||||
color_scheme: vaxis.Color.Scheme,
|
||||
text_input: vaxis.widgets.TextInput,
|
||||
theme_filter: ColorScheme,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement) !*Preview {
|
||||
pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !*Preview {
|
||||
const self = try allocator.create(Preview);
|
||||
|
||||
self.* = .{
|
||||
@@ -240,11 +226,10 @@ const Preview = struct {
|
||||
.mode = .normal,
|
||||
.color_scheme = .light,
|
||||
.text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode),
|
||||
.theme_filter = theme_filter,
|
||||
};
|
||||
|
||||
for (0..themes.len) |i| {
|
||||
try self.filtered.append(i);
|
||||
}
|
||||
try self.updateFiltered();
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -308,6 +293,8 @@ const Preview = struct {
|
||||
|
||||
self.filtered.clearRetainingCapacity();
|
||||
|
||||
var theme_config = try Config.default(self.allocator);
|
||||
defer theme_config.deinit();
|
||||
if (self.text_input.buf.realLength() > 0) {
|
||||
const first_half = self.text_input.buf.firstHalf();
|
||||
const second_half = self.text_input.buf.secondHalf();
|
||||
@@ -328,6 +315,9 @@ const Preview = struct {
|
||||
while (it.next()) |token| try tokens.append(token);
|
||||
|
||||
for (self.themes, 0..) |*theme, i| {
|
||||
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
|
||||
if (!shouldIncludeTheme(self.theme_filter, theme_config)) continue;
|
||||
|
||||
theme.rank = zf.rank(theme.theme, tokens.items, .{
|
||||
.to_lower = true,
|
||||
.plain = true,
|
||||
@@ -336,8 +326,11 @@ const Preview = struct {
|
||||
}
|
||||
} else {
|
||||
for (self.themes, 0..) |*theme, i| {
|
||||
try self.filtered.append(i);
|
||||
theme.rank = null;
|
||||
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
|
||||
if (shouldIncludeTheme(self.theme_filter, theme_config)) {
|
||||
try self.filtered.append(i);
|
||||
theme.rank = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +431,14 @@ const Preview = struct {
|
||||
self.themes[self.filtered.items[self.current]].path,
|
||||
alloc,
|
||||
);
|
||||
if (key.matches('f', .{})) {
|
||||
switch (self.theme_filter) {
|
||||
.all => self.theme_filter = .dark,
|
||||
.dark => self.theme_filter = .light,
|
||||
.light => self.theme_filter = .all,
|
||||
}
|
||||
try self.updateFiltered();
|
||||
}
|
||||
},
|
||||
.help => {
|
||||
if (key.matches('q', .{}))
|
||||
@@ -695,6 +696,7 @@ const Preview = struct {
|
||||
const key_help = [_]struct { keys: []const u8, help: []const u8 }{
|
||||
.{ .keys = "^C, q, ESC", .help = "Quit." },
|
||||
.{ .keys = "F1, ?, ^H", .help = "Toggle help window." },
|
||||
.{ .keys = "f", .help = "Cycle through theme filters." },
|
||||
.{ .keys = "k, ↑", .help = "Move up 1 theme." },
|
||||
.{ .keys = "ScrollUp", .help = "Move up 1 theme." },
|
||||
.{ .keys = "PgUp", .help = "Move up 20 themes." },
|
||||
@@ -1615,18 +1617,17 @@ fn color(config: Config, palette: usize) vaxis.Color {
|
||||
|
||||
const lorem_ipsum = @embedFile("lorem_ipsum.txt");
|
||||
|
||||
fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement) !void {
|
||||
var app = try Preview.init(allocator, themes);
|
||||
fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !void {
|
||||
var app = try Preview.init(allocator, themes, theme_filter);
|
||||
defer app.deinit();
|
||||
try app.run();
|
||||
}
|
||||
|
||||
fn shouldIncludeTheme(opts: Options, theme_config: Config) bool {
|
||||
fn shouldIncludeTheme(theme_filter: ColorScheme, theme_config: Config) bool {
|
||||
const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0;
|
||||
const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0;
|
||||
const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0;
|
||||
const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
|
||||
const is_dark = luminance < 0.5;
|
||||
|
||||
return (opts.color == .dark and is_dark) or (opts.color == .light and !is_dark);
|
||||
return (theme_filter == .all) or (theme_filter == .dark and is_dark) or (theme_filter == .light and !is_dark);
|
||||
}
|
||||
|
@@ -654,6 +654,18 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
||||
/// Available since: 1.2.0
|
||||
@"selection-clear-on-typing": bool = true,
|
||||
|
||||
/// Whether to clear selected text after copying. This defaults to `false`.
|
||||
///
|
||||
/// When set to `true`, the selection will be automatically cleared after
|
||||
/// any copy operation that invokes the `copy_to_clipboard` keyboard binding.
|
||||
/// Importantly, this will not clear the selection if the copy operation
|
||||
/// was invoked via `copy-on-select`.
|
||||
///
|
||||
/// When set to `false`, the selection remains visible after copying, allowing
|
||||
/// to see what was copied and potentially perform additional operations
|
||||
/// on the same selection.
|
||||
@"selection-clear-on-copy": bool = false,
|
||||
|
||||
/// The minimum contrast ratio between the foreground and background colors.
|
||||
/// The contrast ratio is a value between 1 and 21. A value of 1 allows for no
|
||||
/// contrast (e.g. black on black). This value is the contrast ratio as defined
|
||||
@@ -767,6 +779,22 @@ palette: Palette = .{},
|
||||
/// the mouse is shown again when a new window, tab, or split is created.
|
||||
@"mouse-hide-while-typing": bool = false,
|
||||
|
||||
/// When to scroll the surface to the bottom. The format of this is a list of
|
||||
/// options to enable separated by commas. If you prefix an option with `no-`
|
||||
/// then it is disabled. If you omit an option, its default value is used.
|
||||
///
|
||||
/// Available options:
|
||||
///
|
||||
/// - `keystroke` If set, scroll the surface to the bottom when the user
|
||||
/// presses a key that results in data being sent to the PTY (basically
|
||||
/// anything but modifiers or keybinds that are processed by Ghostty).
|
||||
///
|
||||
/// - `output` If set, scroll the surface to the bottom if there is new data
|
||||
/// to display. (Currently unimplemented.)
|
||||
///
|
||||
/// The default is `keystroke, no-output`.
|
||||
@"scroll-to-bottom": ScrollToBottom = .default,
|
||||
|
||||
/// Determines whether running programs can detect the shift key pressed with a
|
||||
/// mouse click. Typically, the shift key is used to extend mouse selection.
|
||||
///
|
||||
@@ -2499,6 +2527,8 @@ keybind: Keybinds = .{},
|
||||
///
|
||||
/// - `clipboard-copy` (default: true) - Show a notification when text is copied
|
||||
/// to the clipboard.
|
||||
/// - `config-reload` (default: true) - Show a notification when
|
||||
/// the configuration is reloaded.
|
||||
///
|
||||
/// To specify a notification to enable, specify the name of the notification.
|
||||
/// To specify a notification to disable, prefix the name with `no-`. For
|
||||
@@ -3017,6 +3047,13 @@ else
|
||||
/// Available since Ghostty 1.2.0.
|
||||
@"bold-color": ?BoldColor = null,
|
||||
|
||||
/// The opacity level (opposite of transparency) of the faint text. A value of
|
||||
/// 1 is fully opaque and a value of 0 is fully transparent. A value less than 0
|
||||
/// or greater than 1 will be clamped to the nearest valid value.
|
||||
///
|
||||
/// Available since Ghostty 1.2.0.
|
||||
@"faint-opacity": f64 = 0.5,
|
||||
|
||||
/// This will be used to set the `TERM` environment variable.
|
||||
/// HACK: We set this with an `xterm` prefix because vim uses that to enable key
|
||||
/// protocols (specifically this will enable `modifyOtherKeys`), among other
|
||||
@@ -3999,6 +4036,8 @@ pub fn finalize(self: *Config) !void {
|
||||
if (self.@"auto-update-channel" == null) {
|
||||
self.@"auto-update-channel" = build_config.release_channel;
|
||||
}
|
||||
|
||||
self.@"faint-opacity" = std.math.clamp(self.@"faint-opacity", 0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Callback for src/cli/args.zig to allow us to handle special cases
|
||||
@@ -5596,7 +5635,7 @@ pub const Keybinds = struct {
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'w' }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .close_tab = {} },
|
||||
.{ .close_tab = .this },
|
||||
);
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
@@ -5758,15 +5797,24 @@ pub const Keybinds = struct {
|
||||
else
|
||||
.{ .alt = true };
|
||||
|
||||
// Cmd+N for goto tab N
|
||||
// Cmd/Alt+N for goto tab N
|
||||
const start: u21 = '1';
|
||||
const end: u21 = '8';
|
||||
var i: u21 = start;
|
||||
while (i <= end) : (i += 1) {
|
||||
comptime var i: u21 = start;
|
||||
inline while (i <= end) : (i += 1) {
|
||||
// We register BOTH the physical `digit_N` key and the unicode
|
||||
// `N` key. This allows most keyboard layouts to work with
|
||||
// this shortcut. Namely, AZERTY doesn't produce unicode `N`
|
||||
// for their digit keys (they're on shifted keys on the same
|
||||
// physical keys).
|
||||
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
.{
|
||||
.key = .{ .unicode = i },
|
||||
.key = .{ .physical = @field(
|
||||
inputpkg.Key,
|
||||
std.fmt.comptimePrint("digit_{u}", .{i}),
|
||||
) },
|
||||
.mods = mods,
|
||||
},
|
||||
.{ .goto_tab = (i - start) + 1 },
|
||||
@@ -5779,6 +5827,22 @@ pub const Keybinds = struct {
|
||||
.performable = !builtin.target.os.tag.isDarwin(),
|
||||
},
|
||||
);
|
||||
|
||||
// Important: this must be the LAST binding set so that the
|
||||
// libghostty trigger API returns this one for the action,
|
||||
// so that things like the macOS tab bar key equivalent label
|
||||
// work properly.
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
.{
|
||||
.key = .{ .unicode = i },
|
||||
.mods = mods,
|
||||
},
|
||||
.{ .goto_tab = (i - start) + 1 },
|
||||
.{
|
||||
.performable = !builtin.target.os.tag.isDarwin(),
|
||||
},
|
||||
);
|
||||
}
|
||||
try self.set.putFlags(
|
||||
alloc,
|
||||
@@ -5902,7 +5966,7 @@ pub const Keybinds = struct {
|
||||
try self.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .unicode = 'w' }, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .close_tab = {} },
|
||||
.{ .close_tab = .this },
|
||||
);
|
||||
try self.set.put(
|
||||
alloc,
|
||||
@@ -7058,6 +7122,7 @@ pub const GtkTitlebarStyle = enum(c_int) {
|
||||
/// See app-notifications
|
||||
pub const AppNotifications = packed struct {
|
||||
@"clipboard-copy": bool = true,
|
||||
@"config-reload": bool = true,
|
||||
};
|
||||
|
||||
/// See bell-features
|
||||
@@ -7195,6 +7260,53 @@ pub const QuickTerminalSize = struct {
|
||||
height: u32,
|
||||
};
|
||||
|
||||
/// C API structure for QuickTerminalSize
|
||||
pub const C = extern struct {
|
||||
primary: C.Size,
|
||||
secondary: C.Size,
|
||||
|
||||
pub const Size = extern struct {
|
||||
tag: Tag,
|
||||
value: Value,
|
||||
|
||||
pub const Tag = enum(u8) { none, percentage, pixels };
|
||||
|
||||
pub const Value = extern union {
|
||||
percentage: f32,
|
||||
pixels: u32,
|
||||
};
|
||||
|
||||
pub const none: C.Size = .{ .tag = .none, .value = undefined };
|
||||
|
||||
pub fn percentage(v: f32) C.Size {
|
||||
return .{
|
||||
.tag = .percentage,
|
||||
.value = .{ .percentage = v },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pixels(v: u32) C.Size {
|
||||
return .{
|
||||
.tag = .pixels,
|
||||
.value = .{ .pixels = v },
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub fn cval(self: QuickTerminalSize) C {
|
||||
return .{
|
||||
.primary = if (self.primary) |p| switch (p) {
|
||||
.percentage => |v| .percentage(v),
|
||||
.pixels => |v| .pixels(v),
|
||||
} else .none,
|
||||
.secondary = if (self.secondary) |s| switch (s) {
|
||||
.percentage => |v| .percentage(v),
|
||||
.pixels => |v| .pixels(v),
|
||||
} else .none,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn calculate(
|
||||
self: QuickTerminalSize,
|
||||
position: QuickTerminalPosition,
|
||||
@@ -7268,6 +7380,7 @@ pub const QuickTerminalSize = struct {
|
||||
|
||||
try formatter.formatEntry([]const u8, fbs.getWritten());
|
||||
}
|
||||
|
||||
test "parse QuickTerminalSize" {
|
||||
const testing = std.testing;
|
||||
var v: QuickTerminalSize = undefined;
|
||||
@@ -7980,6 +8093,14 @@ pub const WindowPadding = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// See scroll-to-bottom
|
||||
pub const ScrollToBottom = packed struct {
|
||||
keystroke: bool = true,
|
||||
output: bool = false,
|
||||
|
||||
pub const default: ScrollToBottom = .{};
|
||||
};
|
||||
|
||||
test "parse duration" {
|
||||
inline for (Duration.units) |unit| {
|
||||
var buf: [16]u8 = undefined;
|
||||
|
@@ -806,14 +806,41 @@ pub const Face = struct {
|
||||
const ic_width: ?f64 = ic_width: {
|
||||
const glyph = self.glyphIndex('水') orelse break :ic_width null;
|
||||
|
||||
var advances: [1]macos.graphics.Size = undefined;
|
||||
_ = ct_font.getAdvancesForGlyphs(
|
||||
const advance = ct_font.getAdvancesForGlyphs(
|
||||
.horizontal,
|
||||
&.{@intCast(glyph)},
|
||||
&advances,
|
||||
null,
|
||||
);
|
||||
|
||||
break :ic_width advances[0].width;
|
||||
const bounds = ct_font.getBoundingRectsForGlyphs(
|
||||
.horizontal,
|
||||
&.{@intCast(glyph)},
|
||||
null,
|
||||
);
|
||||
|
||||
// If the advance of the glyph is less than the width of the actual
|
||||
// glyph then we just treat it as invalid since it's probably wrong
|
||||
// and using it for size normalization will instead make the font
|
||||
// way too big.
|
||||
//
|
||||
// This can sometimes happen if there's a CJK font that has been
|
||||
// patched with the nerd fonts patcher and it butchers the advance
|
||||
// values so the advance ends up half the width of the actual glyph.
|
||||
if (bounds.size.width > advance) {
|
||||
var buf: [1024]u8 = undefined;
|
||||
const font_name = self.name(&buf) catch "<Error getting font name>";
|
||||
log.warn(
|
||||
"(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
|
||||
.{
|
||||
font_name,
|
||||
bounds.size.width,
|
||||
advance,
|
||||
},
|
||||
);
|
||||
break :ic_width null;
|
||||
}
|
||||
|
||||
break :ic_width advance;
|
||||
};
|
||||
|
||||
return .{
|
||||
|
@@ -1007,7 +1007,31 @@ pub const Face = struct {
|
||||
.no_svg = true,
|
||||
}) catch break :ic_width null;
|
||||
|
||||
break :ic_width f26dot6ToF64(face.handle.*.glyph.*.advance.x);
|
||||
const ft_glyph = face.handle.*.glyph;
|
||||
|
||||
// If the advance of the glyph is less than the width of the actual
|
||||
// glyph then we just treat it as invalid since it's probably wrong
|
||||
// and using it for size normalization will instead make the font
|
||||
// way too big.
|
||||
//
|
||||
// This can sometimes happen if there's a CJK font that has been
|
||||
// patched with the nerd fonts patcher and it butchers the advance
|
||||
// values so the advance ends up half the width of the actual glyph.
|
||||
if (ft_glyph.*.metrics.width > ft_glyph.*.advance.x) {
|
||||
var buf: [1024]u8 = undefined;
|
||||
const font_name = self.name(&buf) catch "<Error getting font name>";
|
||||
log.warn(
|
||||
"(getMetrics) Width of glyph '水' for font \"{s}\" is greater than its advance ({d} > {d}), discarding ic_width metric.",
|
||||
.{
|
||||
font_name,
|
||||
f26dot6ToF64(ft_glyph.*.metrics.width),
|
||||
f26dot6ToF64(ft_glyph.*.advance.x),
|
||||
},
|
||||
);
|
||||
break :ic_width null;
|
||||
}
|
||||
|
||||
break :ic_width f26dot6ToF64(ft_glyph.*.advance.x);
|
||||
};
|
||||
|
||||
return .{
|
||||
|
@@ -552,11 +552,15 @@ pub const Action = union(enum) {
|
||||
/// of the `confirm-close-surface` configuration setting.
|
||||
close_surface,
|
||||
|
||||
/// Close the current tab and all splits therein.
|
||||
/// Close the current tab and all splits therein _or_ close all tabs and
|
||||
/// splits thein of tabs _other_ than the current tab, depending on the
|
||||
/// mode.
|
||||
///
|
||||
/// If the mode is not specified, defaults to closing the current tab.
|
||||
///
|
||||
/// This might trigger a close confirmation popup, depending on the value
|
||||
/// of the `confirm-close-surface` configuration setting.
|
||||
close_tab,
|
||||
close_tab: CloseTabMode,
|
||||
|
||||
/// Close the current window and all tabs and splits therein.
|
||||
///
|
||||
@@ -858,6 +862,13 @@ pub const Action = union(enum) {
|
||||
hide,
|
||||
};
|
||||
|
||||
pub const CloseTabMode = enum {
|
||||
this,
|
||||
other,
|
||||
|
||||
pub const default: CloseTabMode = .this;
|
||||
};
|
||||
|
||||
fn parseEnum(comptime T: type, value: []const u8) !T {
|
||||
return std.meta.stringToEnum(T, value) orelse return Error.InvalidFormat;
|
||||
}
|
||||
|
@@ -393,11 +393,18 @@ fn actionCommands(action: Action.Key) []const Command {
|
||||
.description = "Close the current terminal.",
|
||||
}},
|
||||
|
||||
.close_tab => comptime &.{.{
|
||||
.action = .close_tab,
|
||||
.title = "Close Tab",
|
||||
.description = "Close the current tab.",
|
||||
}},
|
||||
.close_tab => comptime &.{
|
||||
.{
|
||||
.action = .{ .close_tab = .this },
|
||||
.title = "Close Tab",
|
||||
.description = "Close the current tab.",
|
||||
},
|
||||
.{
|
||||
.action = .{ .close_tab = .other },
|
||||
.title = "Close Other Tabs",
|
||||
.description = "Close all tabs in this window except the current one.",
|
||||
},
|
||||
},
|
||||
|
||||
.close_window => comptime &.{.{
|
||||
.action = .close_window,
|
||||
|
@@ -589,6 +589,84 @@ pub const Key = enum(c_int) {
|
||||
};
|
||||
}
|
||||
|
||||
/// Whether this key should be remappable by the operating system.
|
||||
///
|
||||
/// On certain OSes (namely Linux and the BSDs) certain keys like the
|
||||
/// functional keys are expected to be remappable by the user, such as
|
||||
/// in the very common use case of swapping the Caps Lock key with the
|
||||
/// Escape key with the XKB option `caps:swapescape`.
|
||||
///
|
||||
/// However, the way XKB implements this is by essentially acting as a
|
||||
/// software key remapper that destroys all information about the original
|
||||
/// physical key, leading to very annoying bugs like #7309 where the
|
||||
/// physical key `XKB_KEY_c` gets remapped into `XKB_KEY_Cyrillic_tse`,
|
||||
/// which causes all of our physical key handling to completely break down.
|
||||
/// _Very naughty._
|
||||
///
|
||||
/// As a compromise, given that writing system keys (§3.1.1) comprise the
|
||||
/// majority of keys that "change meaning [...] based on the current locale
|
||||
/// and keyboard layout", we allow all other keys to be remapped by default
|
||||
/// since they should be fairly harmless. We might consider making this
|
||||
/// configurable, but for now this should at least placate most people.
|
||||
pub fn shouldBeRemappable(self: Key) bool {
|
||||
return switch (self) {
|
||||
// "Writing System Keys" § 3.1.1
|
||||
.backquote,
|
||||
.backslash,
|
||||
.bracket_left,
|
||||
.bracket_right,
|
||||
.comma,
|
||||
.digit_0,
|
||||
.digit_1,
|
||||
.digit_2,
|
||||
.digit_3,
|
||||
.digit_4,
|
||||
.digit_5,
|
||||
.digit_6,
|
||||
.digit_7,
|
||||
.digit_8,
|
||||
.digit_9,
|
||||
.equal,
|
||||
.intl_backslash,
|
||||
.intl_ro,
|
||||
.intl_yen,
|
||||
.key_a,
|
||||
.key_b,
|
||||
.key_c,
|
||||
.key_d,
|
||||
.key_e,
|
||||
.key_f,
|
||||
.key_g,
|
||||
.key_h,
|
||||
.key_i,
|
||||
.key_j,
|
||||
.key_k,
|
||||
.key_l,
|
||||
.key_m,
|
||||
.key_n,
|
||||
.key_o,
|
||||
.key_p,
|
||||
.key_q,
|
||||
.key_r,
|
||||
.key_s,
|
||||
.key_t,
|
||||
.key_u,
|
||||
.key_v,
|
||||
.key_w,
|
||||
.key_x,
|
||||
.key_y,
|
||||
.key_z,
|
||||
.minus,
|
||||
.period,
|
||||
.quote,
|
||||
.semicolon,
|
||||
.slash,
|
||||
=> false,
|
||||
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true if this is a keypad key.
|
||||
pub fn keypad(self: Key) bool {
|
||||
return switch (self) {
|
||||
|
@@ -49,6 +49,7 @@ pub const locales = [_][:0]const u8{
|
||||
"ca_ES.UTF-8",
|
||||
"bg_BG.UTF-8",
|
||||
"ga_IE.UTF-8",
|
||||
"hu_HU.UTF-8",
|
||||
"he_IL.UTF-8",
|
||||
};
|
||||
|
||||
@@ -136,7 +137,12 @@ pub fn canonicalizeLocale(
|
||||
buf: []u8,
|
||||
locale: []const u8,
|
||||
) error{NoSpaceLeft}![:0]const u8 {
|
||||
if (comptime !build_config.i18n) return locale;
|
||||
if (comptime !build_config.i18n) {
|
||||
if (buf.len < locale.len + 1) return error.NoSpaceLeft;
|
||||
@memcpy(buf[0..locale.len], locale);
|
||||
buf[locale.len] = 0;
|
||||
return buf[0..locale.len :0];
|
||||
}
|
||||
|
||||
// Fix zh locales for macOS
|
||||
if (fixZhLocale(locale)) |fixed| {
|
||||
|
@@ -522,6 +522,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
selection_background: ?configpkg.Config.TerminalColor,
|
||||
selection_foreground: ?configpkg.Config.TerminalColor,
|
||||
bold_color: ?configpkg.BoldColor,
|
||||
faint_opacity: u8,
|
||||
min_contrast: f32,
|
||||
padding_color: configpkg.WindowPaddingColor,
|
||||
custom_shaders: configpkg.RepeatablePath,
|
||||
@@ -584,6 +585,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.background = config.background.toTerminalRGB(),
|
||||
.foreground = config.foreground.toTerminalRGB(),
|
||||
.bold_color = config.@"bold-color",
|
||||
.faint_opacity = @intFromFloat(@ceil(config.@"faint-opacity" * 255)),
|
||||
|
||||
.min_contrast = @floatCast(config.@"minimum-contrast"),
|
||||
.padding_color = config.@"window-padding-color",
|
||||
@@ -1549,15 +1551,17 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
// Look up the image
|
||||
const image = self.images.get(p.image_id) orelse {
|
||||
log.warn("image not found for placement image_id={}", .{p.image_id});
|
||||
return;
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the texture
|
||||
const texture = switch (image.image) {
|
||||
.ready => |t| t,
|
||||
.ready,
|
||||
.unload_ready,
|
||||
=> |t| t,
|
||||
else => {
|
||||
log.warn("image not ready for placement image_id={}", .{p.image_id});
|
||||
return;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1907,7 +1911,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
if (img.isUnloading()) {
|
||||
img.deinit(self.alloc);
|
||||
self.images.removeByPtr(kv.key_ptr);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
if (img.isPending()) try img.upload(self.alloc, &self.api);
|
||||
}
|
||||
@@ -2225,23 +2229,44 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
const cursor_width: f32 = @floatFromInt(cursor.glyph_size[0]);
|
||||
const cursor_height: f32 = @floatFromInt(cursor.glyph_size[1]);
|
||||
|
||||
// Left edge of the cell the cursor is in.
|
||||
var pixel_x: f32 = @floatFromInt(
|
||||
cursor.grid_pos[0] * cell.width + padding.left,
|
||||
);
|
||||
// Top edge, relative to the top of the
|
||||
// screen, of the cell the cursor is in.
|
||||
var pixel_y: f32 = @floatFromInt(
|
||||
cursor.grid_pos[1] * cell.height + padding.top,
|
||||
);
|
||||
|
||||
pixel_x += @floatFromInt(cursor.bearings[0]);
|
||||
pixel_y += @floatFromInt(cursor.bearings[1]);
|
||||
|
||||
// If +Y is up in our shaders, we need to flip the coordinate.
|
||||
// If +Y is up in our shaders, we need to flip the coordinate
|
||||
// so that it's instead the top edge of the cell relative to
|
||||
// the *bottom* of the screen.
|
||||
if (!GraphicsAPI.custom_shader_y_is_down) {
|
||||
pixel_y = @as(f32, @floatFromInt(screen.height)) - pixel_y;
|
||||
// We need to add the cursor height because we need the +Y
|
||||
// edge for the Y coordinate, and flipping means that it's
|
||||
// the -Y edge now.
|
||||
pixel_y += cursor_height;
|
||||
}
|
||||
|
||||
// Add the X bearing to get the -X (left) edge of the cursor.
|
||||
pixel_x += @floatFromInt(cursor.bearings[0]);
|
||||
|
||||
// How we deal with the Y bearing depends on which direction
|
||||
// is "up", since we want our final `pixel_y` value to be the
|
||||
// +Y edge of the cursor.
|
||||
if (GraphicsAPI.custom_shader_y_is_down) {
|
||||
// As a reminder, the Y bearing is the distance from the
|
||||
// bottom of the cell to the top of the glyph, so to get
|
||||
// the +Y edge we need to add the cell height, subtract
|
||||
// the Y bearing, and add the glyph height to get the +Y
|
||||
// (bottom) edge of the cursor.
|
||||
pixel_y += @floatFromInt(cell.height);
|
||||
pixel_y -= @floatFromInt(cursor.bearings[1]);
|
||||
pixel_y += @floatFromInt(cursor.glyph_size[1]);
|
||||
} else {
|
||||
// If the Y direction is reversed though, we instead want
|
||||
// the *top* edge of the cursor, which means we just need
|
||||
// to subtract the cell height and add the Y bearing.
|
||||
pixel_y -= @floatFromInt(cell.height);
|
||||
pixel_y += @floatFromInt(cursor.bearings[1]);
|
||||
}
|
||||
|
||||
const new_cursor: [4]f32 = .{
|
||||
@@ -2612,7 +2637,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
};
|
||||
|
||||
// Foreground alpha for this cell.
|
||||
const alpha: u8 = if (style.flags.faint) 175 else 255;
|
||||
const alpha: u8 = if (style.flags.faint) self.config.faint_opacity else 255;
|
||||
|
||||
// Set the cell's background color.
|
||||
{
|
||||
|
@@ -193,7 +193,7 @@ pub const Action = union(enum) {
|
||||
/// Maximum number of intermediate characters during parsing. This is
|
||||
/// 4 because we also use the intermediates array for UTF8 decoding which
|
||||
/// can be at most 4 bytes.
|
||||
const MAX_INTERMEDIATE = 4;
|
||||
pub const MAX_INTERMEDIATE = 4;
|
||||
|
||||
/// Maximum number of CSI parameters. This is arbitrary. Practically, the
|
||||
/// only CSI command that uses more than 3 parameters is the SGR command
|
||||
@@ -206,7 +206,7 @@ const MAX_INTERMEDIATE = 4;
|
||||
/// number. I implore TUI authors to not use more than this number of CSI
|
||||
/// params, but I suspect we'll introduce a slow path with heap allocation
|
||||
/// one day.
|
||||
const MAX_PARAMS = 24;
|
||||
pub const MAX_PARAMS = 24;
|
||||
|
||||
/// Current state of the state machine
|
||||
state: State,
|
||||
@@ -949,6 +949,55 @@ test "csi: too many params" {
|
||||
}
|
||||
}
|
||||
|
||||
test "csi: sgr with up to our max parameters" {
|
||||
for (1..MAX_PARAMS + 1) |max| {
|
||||
var p = init();
|
||||
_ = p.next(0x1B);
|
||||
_ = p.next('[');
|
||||
|
||||
for (0..max - 1) |_| {
|
||||
_ = p.next('1');
|
||||
_ = p.next(';');
|
||||
}
|
||||
_ = p.next('2');
|
||||
|
||||
{
|
||||
const a = p.next('H');
|
||||
try testing.expect(p.state == .ground);
|
||||
try testing.expect(a[0] == null);
|
||||
try testing.expect(a[1].? == .csi_dispatch);
|
||||
try testing.expect(a[2] == null);
|
||||
|
||||
const csi = a[1].?.csi_dispatch;
|
||||
try testing.expectEqual(@as(usize, max), csi.params.len);
|
||||
try testing.expectEqual(@as(u16, 2), csi.params[max - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "csi: sgr beyond our max drops it" {
|
||||
// Has to be +2 for the loops below
|
||||
const max = MAX_PARAMS + 2;
|
||||
|
||||
var p = init();
|
||||
_ = p.next(0x1B);
|
||||
_ = p.next('[');
|
||||
|
||||
for (0..max - 1) |_| {
|
||||
_ = p.next('1');
|
||||
_ = p.next(';');
|
||||
}
|
||||
_ = p.next('2');
|
||||
|
||||
{
|
||||
const a = p.next('H');
|
||||
try testing.expect(p.state == .ground);
|
||||
try testing.expect(a[0] == null);
|
||||
try testing.expect(a[1] == null);
|
||||
try testing.expect(a[2] == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "dcs: XTGETTCAP" {
|
||||
var p = init();
|
||||
_ = p.next(0x1B);
|
||||
|
@@ -147,25 +147,28 @@ pub const Command = union(enum) {
|
||||
/// End a hyperlink (OSC 8)
|
||||
hyperlink_end: void,
|
||||
|
||||
/// Sleep (OSC 9;1)
|
||||
sleep: struct {
|
||||
/// ConEmu sleep (OSC 9;1)
|
||||
conemu_sleep: struct {
|
||||
duration_ms: u16,
|
||||
},
|
||||
|
||||
/// Show GUI message Box (OSC 9;2)
|
||||
show_message_box: []const u8,
|
||||
/// ConEmu show GUI message box (OSC 9;2)
|
||||
conemu_show_message_box: []const u8,
|
||||
|
||||
/// Change ConEmu tab (OSC 9;3)
|
||||
change_conemu_tab_title: union(enum) {
|
||||
reset: void,
|
||||
/// ConEmu change tab title (OSC 9;3)
|
||||
conemu_change_tab_title: union(enum) {
|
||||
reset,
|
||||
value: []const u8,
|
||||
},
|
||||
|
||||
/// Set progress state (OSC 9;4)
|
||||
progress_report: ProgressReport,
|
||||
/// ConEmu progress report (OSC 9;4)
|
||||
conemu_progress_report: ProgressReport,
|
||||
|
||||
/// Wait input (OSC 9;5)
|
||||
wait_input: void,
|
||||
/// ConEmu wait input (OSC 9;5)
|
||||
conemu_wait_input,
|
||||
|
||||
/// ConEmu GUI macro (OSC 9;6)
|
||||
conemu_guimacro: []const u8,
|
||||
|
||||
pub const ColorOperation = union(enum) {
|
||||
pub const Source = enum(u16) {
|
||||
@@ -208,7 +211,6 @@ pub const Command = union(enum) {
|
||||
};
|
||||
|
||||
pub const ProgressReport = struct {
|
||||
// sync with ghostty_terminal_osc_command_progressreport_state_e in include/ghostty.h
|
||||
pub const State = enum(c_int) {
|
||||
remove,
|
||||
set,
|
||||
@@ -220,7 +222,7 @@ pub const Command = union(enum) {
|
||||
state: State,
|
||||
progress: ?u8 = null,
|
||||
|
||||
// sync with ghostty_terminal_osc_command_progressreport_s in include/ghostty.h
|
||||
// sync with ghostty_action_progress_report_s
|
||||
pub const C = extern struct {
|
||||
state: c_int,
|
||||
progress: i8,
|
||||
@@ -229,7 +231,11 @@ pub const Command = union(enum) {
|
||||
pub fn cval(self: ProgressReport) C {
|
||||
return .{
|
||||
.state = @intFromEnum(self.state),
|
||||
.progress = if (self.progress) |progress| @intCast(std.math.clamp(progress, 0, 100)) else -1,
|
||||
.progress = if (self.progress) |progress| @intCast(std.math.clamp(
|
||||
progress,
|
||||
0,
|
||||
100,
|
||||
)) else -1,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -431,6 +437,7 @@ pub const Parser = struct {
|
||||
conemu_progress_state,
|
||||
conemu_progress_prevalue,
|
||||
conemu_progress_value,
|
||||
conemu_guimacro,
|
||||
};
|
||||
|
||||
pub fn init() Parser {
|
||||
@@ -957,107 +964,147 @@ pub const Parser = struct {
|
||||
.osc_9 => switch (c) {
|
||||
'1' => {
|
||||
self.state = .conemu_sleep;
|
||||
// This will end up being either a ConEmu sleep OSC 9;1,
|
||||
// or a desktop notification OSC 9 that begins with '1', so
|
||||
// mark as complete.
|
||||
self.complete = true;
|
||||
},
|
||||
'2' => {
|
||||
self.state = .conemu_message_box;
|
||||
// This will end up being either a ConEmu message box OSC 9;2,
|
||||
// or a desktop notification OSC 9 that begins with '2', so
|
||||
// mark as complete.
|
||||
self.complete = true;
|
||||
},
|
||||
'3' => {
|
||||
self.state = .conemu_tab;
|
||||
// This will end up being either a ConEmu message box OSC 9;3,
|
||||
// or a desktop notification OSC 9 that begins with '3', so
|
||||
// mark as complete.
|
||||
self.complete = true;
|
||||
},
|
||||
'4' => {
|
||||
self.state = .conemu_progress_prestate;
|
||||
// This will end up being either a ConEmu progress report
|
||||
// OSC 9;4, or a desktop notification OSC 9 that begins with
|
||||
// '4', so mark as complete.
|
||||
self.complete = true;
|
||||
},
|
||||
'5' => {
|
||||
// Note that sending an OSC 9 desktop notification that
|
||||
// starts with 5 is impossible due to this.
|
||||
self.state = .swallow;
|
||||
self.command = .{ .wait_input = {} };
|
||||
self.command = .conemu_wait_input;
|
||||
self.complete = true;
|
||||
},
|
||||
'6' => {
|
||||
self.state = .conemu_guimacro;
|
||||
// This will end up being either a ConEmu GUI macro OSC 9;6,
|
||||
// or a desktop notification OSC 9 that begins with '6', so
|
||||
// mark as complete.
|
||||
self.complete = true;
|
||||
},
|
||||
|
||||
// Todo: parse out other ConEmu operating system commands.
|
||||
// Even if we don't support them we probably don't want
|
||||
// them showing up as desktop notifications.
|
||||
// Todo: parse out other ConEmu operating system commands. Even
|
||||
// if we don't support them we probably don't want them showing
|
||||
// up as desktop notifications.
|
||||
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.conemu_sleep => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .sleep = .{ .duration_ms = 100 } };
|
||||
self.command = .{ .conemu_sleep = .{ .duration_ms = 100 } };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
self.state = .conemu_sleep_value;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
},
|
||||
|
||||
.conemu_message_box => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .show_message_box = undefined };
|
||||
self.temp_state = .{ .str = &self.command.show_message_box };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
self.prepAllocableString();
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
// OSC 9;1 <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.conemu_sleep_value => switch (c) {
|
||||
else => self.complete = true,
|
||||
},
|
||||
|
||||
.conemu_message_box => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .conemu_show_message_box = undefined };
|
||||
self.temp_state = .{ .str = &self.command.conemu_show_message_box };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
self.prepAllocableString();
|
||||
},
|
||||
|
||||
// OSC 9;2 <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.conemu_tab => switch (c) {
|
||||
';' => {
|
||||
self.state = .conemu_tab_txt;
|
||||
self.command = .{ .change_conemu_tab_title = .{ .reset = {} } };
|
||||
self.command = .{ .conemu_change_tab_title = .reset };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.complete = true;
|
||||
},
|
||||
else => self.state = .invalid,
|
||||
|
||||
// OSC 9;3 <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.conemu_tab_txt => {
|
||||
self.command = .{ .change_conemu_tab_title = .{ .value = undefined } };
|
||||
self.temp_state = .{ .str = &self.command.change_conemu_tab_title.value };
|
||||
self.command = .{ .conemu_change_tab_title = .{ .value = undefined } };
|
||||
self.temp_state = .{ .str = &self.command.conemu_change_tab_title.value };
|
||||
self.complete = true;
|
||||
self.prepAllocableString();
|
||||
},
|
||||
|
||||
.conemu_progress_prestate => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .progress_report = .{
|
||||
self.command = .{ .conemu_progress_report = .{
|
||||
.state = undefined,
|
||||
} };
|
||||
self.state = .conemu_progress_state;
|
||||
},
|
||||
|
||||
// OSC 9;4 <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.conemu_progress_state => switch (c) {
|
||||
'0' => {
|
||||
self.command.progress_report.state = .remove;
|
||||
self.command.conemu_progress_report.state = .remove;
|
||||
self.state = .swallow;
|
||||
self.complete = true;
|
||||
},
|
||||
'1' => {
|
||||
self.command.progress_report.state = .set;
|
||||
self.command.progress_report.progress = 0;
|
||||
self.command.conemu_progress_report.state = .set;
|
||||
self.command.conemu_progress_report.progress = 0;
|
||||
self.state = .conemu_progress_prevalue;
|
||||
},
|
||||
'2' => {
|
||||
self.command.progress_report.state = .@"error";
|
||||
self.command.conemu_progress_report.state = .@"error";
|
||||
self.complete = true;
|
||||
self.state = .conemu_progress_prevalue;
|
||||
},
|
||||
'3' => {
|
||||
self.command.progress_report.state = .indeterminate;
|
||||
self.command.conemu_progress_report.state = .indeterminate;
|
||||
self.complete = true;
|
||||
self.state = .swallow;
|
||||
},
|
||||
'4' => {
|
||||
self.command.progress_report.state = .pause;
|
||||
self.command.conemu_progress_report.state = .pause;
|
||||
self.complete = true;
|
||||
self.state = .conemu_progress_prevalue;
|
||||
},
|
||||
|
||||
// OSC 9;4; <something other than 0-4> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
@@ -1066,6 +1113,8 @@ pub const Parser = struct {
|
||||
self.state = .conemu_progress_value;
|
||||
},
|
||||
|
||||
// OSC 9;4;<0-4> <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
@@ -1077,8 +1126,16 @@ pub const Parser = struct {
|
||||
|
||||
// If we aren't a set substate, then we don't care
|
||||
// about the value.
|
||||
const p = &self.command.progress_report;
|
||||
if (p.state != .set and p.state != .@"error" and p.state != .pause) break :value;
|
||||
const p = &self.command.conemu_progress_report;
|
||||
switch (p.state) {
|
||||
.remove,
|
||||
.indeterminate,
|
||||
=> break :value,
|
||||
.set,
|
||||
.@"error",
|
||||
.pause,
|
||||
=> {},
|
||||
}
|
||||
|
||||
if (p.state == .set)
|
||||
assert(p.progress != null)
|
||||
@@ -1104,6 +1161,20 @@ pub const Parser = struct {
|
||||
},
|
||||
},
|
||||
|
||||
.conemu_guimacro => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .conemu_guimacro = undefined };
|
||||
self.temp_state = .{ .str = &self.command.conemu_guimacro };
|
||||
self.buf_start = self.buf_idx;
|
||||
self.state = .string;
|
||||
self.complete = true;
|
||||
},
|
||||
|
||||
// OSC 9;6 <something other than semicolon> is a desktop
|
||||
// notification.
|
||||
else => self.showDesktopNotification(),
|
||||
},
|
||||
|
||||
.semantic_prompt => switch (c) {
|
||||
'A' => {
|
||||
self.state = .semantic_option_start;
|
||||
@@ -1212,6 +1283,11 @@ pub const Parser = struct {
|
||||
|
||||
self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
|
||||
self.state = .string;
|
||||
// Set as complete as we've already seen one character that should be
|
||||
// part of the notification. If we wait for another character to set
|
||||
// `complete` when the state is `.string` we won't be able to send any
|
||||
// single character notifications.
|
||||
self.complete = true;
|
||||
}
|
||||
|
||||
fn prepAllocableString(self: *Parser) void {
|
||||
@@ -1332,7 +1408,7 @@ pub const Parser = struct {
|
||||
|
||||
fn endConEmuSleepValue(self: *Parser) void {
|
||||
switch (self.command) {
|
||||
.sleep => |*v| v.duration_ms = value: {
|
||||
.conemu_sleep => |*v| v.duration_ms = value: {
|
||||
const str = self.buf[self.buf_start..self.buf_idx];
|
||||
if (str.len == 0) break :value 100;
|
||||
|
||||
@@ -1595,6 +1671,26 @@ pub const Parser = struct {
|
||||
.hyperlink_uri => self.endHyperlink(),
|
||||
.string => self.endString(),
|
||||
.conemu_sleep_value => self.endConEmuSleepValue(),
|
||||
|
||||
// We received OSC 9;X ST, but nothing else, finish off as a
|
||||
// desktop notification with "X" as the body.
|
||||
.conemu_sleep,
|
||||
.conemu_message_box,
|
||||
.conemu_tab,
|
||||
.conemu_progress_prestate,
|
||||
.conemu_progress_state,
|
||||
.conemu_guimacro,
|
||||
=> {
|
||||
self.showDesktopNotification();
|
||||
self.endString();
|
||||
},
|
||||
|
||||
// A ConEmu progress report that has reached these states is
|
||||
// complete, don't do anything to them.
|
||||
.conemu_progress_prevalue,
|
||||
.conemu_progress_value,
|
||||
=> {},
|
||||
|
||||
.allocable_string => self.endAllocableString(),
|
||||
.kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true),
|
||||
.kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true),
|
||||
@@ -2770,7 +2866,7 @@ test "OSC: OSC104: empty palette index" {
|
||||
try std.testing.expect(it.next() == null);
|
||||
}
|
||||
|
||||
test "OSC: conemu sleep" {
|
||||
test "OSC: OSC 9;1 ConEmu sleep" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2780,11 +2876,11 @@ test "OSC: conemu sleep" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .sleep);
|
||||
try testing.expectEqual(420, cmd.sleep.duration_ms);
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(420, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC: conemu sleep with no value default to 100ms" {
|
||||
test "OSC: OSC 9;1 ConEmu sleep with no value default to 100ms" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2794,11 +2890,11 @@ test "OSC: conemu sleep with no value default to 100ms" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .sleep);
|
||||
try testing.expectEqual(100, cmd.sleep.duration_ms);
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC: conemu sleep cannot exceed 10000ms" {
|
||||
test "OSC: OSC 9;1 conemu sleep cannot exceed 10000ms" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2808,11 +2904,11 @@ test "OSC: conemu sleep cannot exceed 10000ms" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .sleep);
|
||||
try testing.expectEqual(10000, cmd.sleep.duration_ms);
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(10000, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC: conemu sleep invalid input" {
|
||||
test "OSC: OSC 9;1 conemu sleep invalid input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2822,11 +2918,39 @@ test "OSC: conemu sleep invalid input" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .sleep);
|
||||
try testing.expectEqual(100, cmd.sleep.duration_ms);
|
||||
try testing.expect(cmd == .conemu_sleep);
|
||||
try testing.expectEqual(100, cmd.conemu_sleep.duration_ms);
|
||||
}
|
||||
|
||||
test "OSC: show desktop notification" {
|
||||
test "OSC: OSC 9;1 conemu sleep -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;1";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("1", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;1 conemu sleep -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;1a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("1a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9 show desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2836,11 +2960,25 @@ test "OSC: show desktop notification" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings(cmd.show_desktop_notification.title, "");
|
||||
try testing.expectEqualStrings(cmd.show_desktop_notification.body, "Hello world");
|
||||
try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
|
||||
try testing.expectEqualStrings("Hello world", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: show desktop notification with title" {
|
||||
test "OSC: OSC 9 show single character desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;H";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("", cmd.show_desktop_notification.title);
|
||||
try testing.expectEqualStrings("H", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 777 show desktop notification with title" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2854,7 +2992,7 @@ test "OSC: show desktop notification with title" {
|
||||
try testing.expectEqualStrings(cmd.show_desktop_notification.body, "Body");
|
||||
}
|
||||
|
||||
test "OSC: conemu message box" {
|
||||
test "OSC: OSC 9;2 ConEmu message box" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2863,11 +3001,11 @@ test "OSC: conemu message box" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_message_box);
|
||||
try testing.expectEqualStrings("hello world", cmd.show_message_box);
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings("hello world", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC: conemu message box invalid input" {
|
||||
test "OSC: 9;2 ConEmu message box invalid input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2875,11 +3013,12 @@ test "OSC: conemu message box invalid input" {
|
||||
const input = "9;2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b');
|
||||
try testing.expect(cmd == null);
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: conemu message box empty message" {
|
||||
test "OSC: 9;2 ConEmu message box empty message" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2888,11 +3027,11 @@ test "OSC: conemu message box empty message" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_message_box);
|
||||
try testing.expectEqualStrings("", cmd.show_message_box);
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings("", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC: conemu message box spaces only message" {
|
||||
test "OSC: 9;2 ConEmu message box spaces only message" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2901,11 +3040,39 @@ test "OSC: conemu message box spaces only message" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_message_box);
|
||||
try testing.expectEqualStrings(" ", cmd.show_message_box);
|
||||
try testing.expect(cmd == .conemu_show_message_box);
|
||||
try testing.expectEqualStrings(" ", cmd.conemu_show_message_box);
|
||||
}
|
||||
|
||||
test "OSC: conemu change tab title" {
|
||||
test "OSC: OSC 9;2 message box -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;2";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;2 message box -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;2a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("2a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: 9;3 ConEmu change tab title" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2914,11 +3081,11 @@ test "OSC: conemu change tab title" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .change_conemu_tab_title);
|
||||
try testing.expectEqualStrings("foo bar", cmd.change_conemu_tab_title.value);
|
||||
try testing.expect(cmd == .conemu_change_tab_title);
|
||||
try testing.expectEqualStrings("foo bar", cmd.conemu_change_tab_title.value);
|
||||
}
|
||||
|
||||
test "OSC: conemu change tab reset title" {
|
||||
test "OSC: 9;3 ConEmu change tab title reset" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2928,11 +3095,11 @@ test "OSC: conemu change tab reset title" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
const expected_command: Command = .{ .change_conemu_tab_title = .{ .reset = {} } };
|
||||
const expected_command: Command = .{ .conemu_change_tab_title = .reset };
|
||||
try testing.expectEqual(expected_command, cmd);
|
||||
}
|
||||
|
||||
test "OSC: conemu change tab spaces only title" {
|
||||
test "OSC: 9;3 ConEmu change tab title spaces only" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2942,11 +3109,11 @@ test "OSC: conemu change tab spaces only title" {
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .change_conemu_tab_title);
|
||||
try testing.expectEqualStrings(" ", cmd.change_conemu_tab_title.value);
|
||||
try testing.expect(cmd == .conemu_change_tab_title);
|
||||
try testing.expectEqualStrings(" ", cmd.conemu_change_tab_title.value);
|
||||
}
|
||||
|
||||
test "OSC: conemu change tab invalid input" {
|
||||
test "OSC: OSC 9;3 change tab title -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2954,11 +3121,27 @@ test "OSC: conemu change tab invalid input" {
|
||||
const input = "9;3";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b');
|
||||
try testing.expect(cmd == null);
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("3", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress set" {
|
||||
test "OSC: OSC 9;3 message box -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;3a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("3a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;4 ConEmu progress set" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2967,12 +3150,12 @@ test "OSC: OSC9 progress set" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .set);
|
||||
try testing.expect(cmd.progress_report.progress == 100);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .set);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == 100);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress set overflow" {
|
||||
test "OSC: OSC 9;4 ConEmu progress set overflow" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2981,12 +3164,12 @@ test "OSC: OSC9 progress set overflow" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .set);
|
||||
try testing.expect(cmd.progress_report.progress == 100);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .set);
|
||||
try testing.expectEqual(100, cmd.conemu_progress_report.progress);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress set single digit" {
|
||||
test "OSC: OSC 9;4 ConEmu progress set single digit" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -2995,12 +3178,12 @@ test "OSC: OSC9 progress set single digit" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .set);
|
||||
try testing.expect(cmd.progress_report.progress == 9);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .set);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == 9);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress set double digit" {
|
||||
test "OSC: OSC 9;4 ConEmu progress set double digit" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3009,12 +3192,12 @@ test "OSC: OSC9 progress set double digit" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .set);
|
||||
try testing.expect(cmd.progress_report.progress == 94);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .set);
|
||||
try testing.expectEqual(94, cmd.conemu_progress_report.progress);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress set extra semicolon ignored" {
|
||||
test "OSC: OSC 9;4 ConEmu progress set extra semicolon ignored" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3023,12 +3206,12 @@ test "OSC: OSC9 progress set extra semicolon ignored" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .set);
|
||||
try testing.expect(cmd.progress_report.progress == 100);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .set);
|
||||
try testing.expectEqual(100, cmd.conemu_progress_report.progress);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress remove with no progress" {
|
||||
test "OSC: OSC 9;4 ConEmu progress remove with no progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3037,12 +3220,12 @@ test "OSC: OSC9 progress remove with no progress" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .remove);
|
||||
try testing.expect(cmd.progress_report.progress == null);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .remove);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress remove with double semicolon" {
|
||||
test "OSC: OSC 9;4 ConEmu progress remove with double semicolon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3051,12 +3234,12 @@ test "OSC: OSC9 progress remove with double semicolon" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .remove);
|
||||
try testing.expect(cmd.progress_report.progress == null);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .remove);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress remove ignores progress" {
|
||||
test "OSC: OSC 9;4 ConEmu progress remove ignores progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3065,12 +3248,12 @@ test "OSC: OSC9 progress remove ignores progress" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .remove);
|
||||
try testing.expect(cmd.progress_report.progress == null);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .remove);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress remove extra semicolon" {
|
||||
test "OSC: OSC 9;4 ConEmu progress remove extra semicolon" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3079,11 +3262,11 @@ test "OSC: OSC9 progress remove extra semicolon" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .remove);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .remove);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress error" {
|
||||
test "OSC: OSC 9;4 ConEmu progress error" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3092,12 +3275,12 @@ test "OSC: OSC9 progress error" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .@"error");
|
||||
try testing.expect(cmd.progress_report.progress == null);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .@"error");
|
||||
try testing.expect(cmd.conemu_progress_report.progress == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress error with progress" {
|
||||
test "OSC: OSC 9;4 ConEmu progress error with progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3106,12 +3289,12 @@ test "OSC: OSC9 progress error with progress" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .@"error");
|
||||
try testing.expect(cmd.progress_report.progress == 100);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .@"error");
|
||||
try testing.expect(cmd.conemu_progress_report.progress == 100);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress pause" {
|
||||
test "OSC: OSC 9;4 progress pause" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3120,12 +3303,12 @@ test "OSC: OSC9 progress pause" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .pause);
|
||||
try testing.expect(cmd.progress_report.progress == null);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .pause);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == null);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 progress pause with progress" {
|
||||
test "OSC: OSC 9;4 ConEmu progress pause with progress" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3134,12 +3317,68 @@ test "OSC: OSC9 progress pause with progress" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .progress_report);
|
||||
try testing.expect(cmd.progress_report.state == .pause);
|
||||
try testing.expect(cmd.progress_report.progress == 100);
|
||||
try testing.expect(cmd == .conemu_progress_report);
|
||||
try testing.expect(cmd.conemu_progress_report.state == .pause);
|
||||
try testing.expect(cmd.conemu_progress_report.progress == 100);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 conemu wait input" {
|
||||
test "OSC: OSC 9;4 progress -> desktop notification 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;4";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;4 progress -> desktop notification 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;4;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;4 progress -> desktop notification 3" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;4;5";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;5", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;4 progress -> desktop notification 4" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
|
||||
const input = "9;4;5a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("4;5a", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
||||
test "OSC: OSC 9;5 ConEmu wait input" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3148,10 +3387,10 @@ test "OSC: OSC9 conemu wait input" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .wait_input);
|
||||
try testing.expect(cmd == .conemu_wait_input);
|
||||
}
|
||||
|
||||
test "OSC: OSC9 conemu wait ignores trailing characters" {
|
||||
test "OSC: OSC 9;5 ConEmu wait ignores trailing characters" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .init();
|
||||
@@ -3160,7 +3399,7 @@ test "OSC: OSC9 conemu wait ignores trailing characters" {
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .wait_input);
|
||||
try testing.expect(cmd == .conemu_wait_input);
|
||||
}
|
||||
|
||||
test "OSC: empty param" {
|
||||
@@ -3415,3 +3654,45 @@ test "OSC: kitty color protocol no key" {
|
||||
try testing.expect(cmd == .kitty_color_protocol);
|
||||
try testing.expectEqual(0, cmd.kitty_color_protocol.list.items.len);
|
||||
}
|
||||
|
||||
test "OSC: 9;6: ConEmu guimacro 1" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6;a";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .conemu_guimacro);
|
||||
try testing.expectEqualStrings("a", cmd.conemu_guimacro);
|
||||
}
|
||||
|
||||
test "OSC: 9;6: ConEmu guimacro 2" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6;ab";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .conemu_guimacro);
|
||||
try testing.expectEqualStrings("ab", cmd.conemu_guimacro);
|
||||
}
|
||||
|
||||
test "OSC: 9;6: ConEmu guimacro 3 incomplete -> desktop notification" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .initAlloc(testing.allocator);
|
||||
defer p.deinit();
|
||||
|
||||
const input = "9;6";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
const cmd = p.end('\x1b').?;
|
||||
try testing.expect(cmd == .show_desktop_notification);
|
||||
try testing.expectEqualStrings("6", cmd.show_desktop_notification.body);
|
||||
}
|
||||
|
@@ -134,7 +134,7 @@ pub const Parser = struct {
|
||||
self.idx += 1;
|
||||
return .{ .unknown = .{
|
||||
.full = self.params,
|
||||
.partial = slice[0 .. self.idx - start + 1],
|
||||
.partial = slice[0..@min(self.idx - start + 1, slice.len)],
|
||||
} };
|
||||
},
|
||||
};
|
||||
|
@@ -249,7 +249,7 @@ pub fn Stream(comptime Handler: type) type {
|
||||
// the parser state to ground.
|
||||
0x18, 0x1A => self.parser.state = .ground,
|
||||
// A parameter digit:
|
||||
'0'...'9' => if (self.parser.params_idx < 16) {
|
||||
'0'...'9' => if (self.parser.params_idx < Parser.MAX_PARAMS) {
|
||||
self.parser.param_acc *|= 10;
|
||||
self.parser.param_acc +|= c - '0';
|
||||
// The parser's CSI param action uses param_acc_idx
|
||||
@@ -259,7 +259,7 @@ pub fn Stream(comptime Handler: type) type {
|
||||
self.parser.param_acc_idx |= 1;
|
||||
},
|
||||
// A parameter separator:
|
||||
':', ';' => if (self.parser.params_idx < 16) {
|
||||
':', ';' => if (self.parser.params_idx < Parser.MAX_PARAMS) {
|
||||
self.parser.params[self.parser.params_idx] = self.parser.param_acc;
|
||||
if (c == ':') self.parser.params_sep.set(self.parser.params_idx);
|
||||
self.parser.params_idx += 1;
|
||||
@@ -1598,14 +1598,19 @@ pub fn Stream(comptime Handler: type) type {
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
.progress_report => |v| {
|
||||
.conemu_progress_report => |v| {
|
||||
if (@hasDecl(T, "handleProgressReport")) {
|
||||
try self.handler.handleProgressReport(v);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
.sleep, .show_message_box, .change_conemu_tab_title, .wait_input => {
|
||||
.conemu_sleep,
|
||||
.conemu_show_message_box,
|
||||
.conemu_change_tab_title,
|
||||
.conemu_wait_input,
|
||||
.conemu_guimacro,
|
||||
=> {
|
||||
log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
@@ -2596,3 +2601,22 @@ test "stream CSI ? W reset tab stops" {
|
||||
try s.nextSlice("\x1b[?1;2;3W");
|
||||
try testing.expect(s.handler.reset);
|
||||
}
|
||||
|
||||
test "stream: SGR with 17+ parameters for underline color" {
|
||||
const H = struct {
|
||||
attrs: ?sgr.Attribute = null,
|
||||
called: bool = false,
|
||||
|
||||
pub fn setAttribute(self: *@This(), attr: sgr.Attribute) !void {
|
||||
self.attrs = attr;
|
||||
self.called = true;
|
||||
}
|
||||
};
|
||||
|
||||
var s: Stream(H) = .init(.{});
|
||||
|
||||
// Kakoune-style SGR with underline color as 17th parameter
|
||||
// This tests the fix where param 17 was being dropped
|
||||
try s.nextSlice("\x1b[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136;0m");
|
||||
try testing.expect(s.handler.called);
|
||||
}
|
||||
|
@@ -84,10 +84,23 @@ pub const Style = struct {
|
||||
}
|
||||
|
||||
/// True if the style is equal to another style.
|
||||
/// For performance do direct comparisons first.
|
||||
pub fn eql(self: Style, other: Style) bool {
|
||||
// We convert the styles to packed structs and compare as integers
|
||||
// because this is much faster than comparing each field separately.
|
||||
return PackedStyle.fromStyle(self) == PackedStyle.fromStyle(other);
|
||||
inline for (comptime std.meta.fields(Style)) |field| {
|
||||
if (comptime std.meta.hasUniqueRepresentation(field.type)) {
|
||||
if (@field(self, field.name) != @field(other, field.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
inline for (comptime std.meta.fields(Style)) |field| {
|
||||
if (comptime !std.meta.hasUniqueRepresentation(field.type)) {
|
||||
if (!std.meta.eql(@field(self, field.name), @field(other, field.name))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns the bg color for a cell with this style given the cell
|
||||
|
@@ -1513,7 +1513,8 @@ fn execCommand(
|
||||
}
|
||||
|
||||
return switch (command) {
|
||||
.direct => |v| v,
|
||||
// We need to clone the command since there's no guarantee the config remains valid.
|
||||
.direct => |_| (try command.clone(alloc)).direct,
|
||||
|
||||
.shell => |v| shell: {
|
||||
var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4);
|
||||
@@ -1688,3 +1689,35 @@ test "execCommand: direct command, error passwd" {
|
||||
try testing.expectEqualStrings(result[0], "foo");
|
||||
try testing.expectEqualStrings(result[1], "bar baz");
|
||||
}
|
||||
|
||||
test "execCommand: direct command, config freed" {
|
||||
if (comptime builtin.os.tag == .windows) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var command_arena = ArenaAllocator.init(alloc);
|
||||
const command_alloc = command_arena.allocator();
|
||||
const command = try (configpkg.Command{
|
||||
.direct = &.{
|
||||
"foo",
|
||||
"bar baz",
|
||||
},
|
||||
}).clone(command_alloc);
|
||||
|
||||
const result = try execCommand(alloc, command, struct {
|
||||
fn get(_: Allocator) !PasswdEntry {
|
||||
// Failed passwd entry means we can't construct a macOS
|
||||
// login command and falls back to POSIX behavior.
|
||||
return error.Fail;
|
||||
}
|
||||
});
|
||||
|
||||
command_arena.deinit();
|
||||
|
||||
try testing.expectEqual(2, result.len);
|
||||
try testing.expectEqualStrings(result[0], "foo");
|
||||
try testing.expectEqualStrings(result[1], "bar baz");
|
||||
}
|
||||
|
Reference in New Issue
Block a user