fix: Windows core build, embedded init, byte-exact paths, version info
Closes the remaining core/lib/termio/GTK rows (final-Z2.md): - ghostty-core compiles for Windows (Unix-only exec fields and file modes gated); checked with cargo check --target x86_64-pc-windows-gnu. - Embedded init: ensure_locale, SIGPIPE ignored in ghostty_init for C/Swift hosts, Apple crash tags; the argv passed to ghostty_init feeds ghostty_config_load_cli_args; ghostty_config_load_file accepts non-UTF-8 paths. - OSC 7 pwd, embedded surface cwd/command/env/initial input, ghostty_surface_text and GTK file drops keep raw bytes. - XTVERSION, TERM_PROGRAM_VERSION, the About dialog and crash metadata use ghostty_build_info instead of hardcoded versions. Test Plan: - cargo test --workspace (3327 passed) - cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
1 parent
8519f1ba1b
commit
ebefad5031
30 files changed
+863
-80
No files matched your search
Generated
+390
-1
@@ -325,6 +325,9 @@ name = "bitflags"
|
|||||||
version = "2.13.2"
|
version = "2.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block"
|
name = "block"
|
||||||
@@ -395,6 +398,18 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-rs"
|
name = "cairo-rs"
|
||||||
version = "0.22.9"
|
version = "0.22.9"
|
||||||
@@ -547,6 +562,12 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core_detect"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -556,6 +577,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crash-context"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3748e166981fa3ba9dc54ff950ad0ed5ca4aab4b73eae21d9f539659fe806e28"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"mach2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.2"
|
version = "1.5.2"
|
||||||
@@ -718,6 +750,15 @@ dependencies = [
|
|||||||
"mint",
|
"mint",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "debugid"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||||
|
dependencies = [
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defmt"
|
name = "defmt"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -835,6 +876,34 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embed-resource"
|
||||||
|
version = "3.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6168487b5e7eb2673e6efe1200b1848016e436a6ca0b8031bf04bd9d493a249f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"memchr",
|
||||||
|
"rustc_version",
|
||||||
|
"toml",
|
||||||
|
"vswhom",
|
||||||
|
"winreg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"core_detect",
|
||||||
|
"multiversion_no_op",
|
||||||
|
"rustversion",
|
||||||
|
"scopeguard",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -901,6 +970,15 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-graph"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b920e777967421aa5f9bf34f842c0ab6ba19b3bdb4a082946093860f5858879"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "euclid"
|
name = "euclid"
|
||||||
version = "0.22.14"
|
version = "0.22.14"
|
||||||
@@ -930,6 +1008,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "failspot"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c942e64b20ecd39933d5ff938ca4fdb6ef0d298cc3855b231179a5ef0b24948d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fancy-regex"
|
name = "fancy-regex"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -1291,6 +1375,7 @@ dependencies = [
|
|||||||
"gdk4-wayland",
|
"gdk4-wayland",
|
||||||
"gdk4-x11",
|
"gdk4-x11",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
|
"ghostty-build-info",
|
||||||
"ghostty-config",
|
"ghostty-config",
|
||||||
"ghostty-datastruct",
|
"ghostty-datastruct",
|
||||||
"ghostty-input",
|
"ghostty-input",
|
||||||
@@ -1336,12 +1421,17 @@ dependencies = [
|
|||||||
"ghostty-termio",
|
"ghostty-termio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghostty-build-info"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghostty-cli"
|
name = "ghostty-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
|
"ghostty-build-info",
|
||||||
"ghostty-config",
|
"ghostty-config",
|
||||||
"ghostty-crash",
|
"ghostty-crash",
|
||||||
"ghostty-font",
|
"ghostty-font",
|
||||||
@@ -1378,6 +1468,7 @@ dependencies = [
|
|||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
|
"ghostty-build-info",
|
||||||
"ghostty-config",
|
"ghostty-config",
|
||||||
"ghostty-crash",
|
"ghostty-crash",
|
||||||
"ghostty-datastruct",
|
"ghostty-datastruct",
|
||||||
@@ -1405,6 +1496,8 @@ dependencies = [
|
|||||||
"ghostty-os",
|
"ghostty-os",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
"minidump",
|
||||||
|
"minidump-writer",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.21",
|
"thiserror 2.0.21",
|
||||||
@@ -1481,15 +1574,18 @@ dependencies = [
|
|||||||
"dispatch2",
|
"dispatch2",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
"ghostty-bench",
|
"ghostty-bench",
|
||||||
|
"ghostty-build-info",
|
||||||
"ghostty-cli",
|
"ghostty-cli",
|
||||||
"ghostty-config",
|
"ghostty-config",
|
||||||
"ghostty-core",
|
"ghostty-core",
|
||||||
|
"ghostty-crash",
|
||||||
"ghostty-input",
|
"ghostty-input",
|
||||||
"ghostty-inspector",
|
"ghostty-inspector",
|
||||||
"ghostty-os",
|
"ghostty-os",
|
||||||
"ghostty-renderer",
|
"ghostty-renderer",
|
||||||
"ghostty-terminal",
|
"ghostty-terminal",
|
||||||
"ghostty-vt",
|
"ghostty-vt",
|
||||||
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
@@ -1566,6 +1662,7 @@ dependencies = [
|
|||||||
name = "ghostty-rs"
|
name = "ghostty-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"embed-resource",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
"ghostty-apprt-gtk",
|
"ghostty-apprt-gtk",
|
||||||
"ghostty-cli",
|
"ghostty-cli",
|
||||||
@@ -1605,6 +1702,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"ghostty-apprt",
|
"ghostty-apprt",
|
||||||
|
"ghostty-build-info",
|
||||||
"ghostty-config",
|
"ghostty-config",
|
||||||
"ghostty-crash",
|
"ghostty-crash",
|
||||||
"ghostty-datastruct",
|
"ghostty-datastruct",
|
||||||
@@ -1787,6 +1885,17 @@ dependencies = [
|
|||||||
"system-deps 9.0.0",
|
"system-deps 9.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "goblin"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"plain",
|
||||||
|
"scroll 0.12.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphene-rs"
|
name = "graphene-rs"
|
||||||
version = "0.22.8"
|
version = "0.22.8"
|
||||||
@@ -2303,6 +2412,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mach2"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
@@ -2318,6 +2433,15 @@ version = "2.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.9.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmem"
|
name = "memmem"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2333,6 +2457,83 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minidump"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a03516f742a87e757019a3019707b38900663397d63cdb0442721c5dbd29ed6b"
|
||||||
|
dependencies = [
|
||||||
|
"debugid",
|
||||||
|
"encoding_rs",
|
||||||
|
"memmap2",
|
||||||
|
"minidump-common 0.27.0",
|
||||||
|
"num-traits",
|
||||||
|
"procfs-core",
|
||||||
|
"prost",
|
||||||
|
"range-map",
|
||||||
|
"scroll 0.13.0",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
"time",
|
||||||
|
"tracing",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minidump-common"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e16d10087ae9e375bad7a40e8ef5504bc08e808ccc6019067ff9de42a84570f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"debugid",
|
||||||
|
"num-derive 0.4.2",
|
||||||
|
"num-traits",
|
||||||
|
"range-map",
|
||||||
|
"scroll 0.12.0",
|
||||||
|
"smart-default",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minidump-common"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8e6411f30cf59f8a4815820a9b5d0c38962a719f5c43f80f5afbb71e791af16"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"debugid",
|
||||||
|
"num-derive 0.5.1",
|
||||||
|
"num-traits",
|
||||||
|
"range-map",
|
||||||
|
"scroll 0.13.0",
|
||||||
|
"smart-default",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minidump-writer"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f742d7445a9d4c63826d302dadbbcb607745560108e88b21c4014a9724d09dbe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"byteorder",
|
||||||
|
"cfg-if",
|
||||||
|
"crash-context",
|
||||||
|
"error-graph",
|
||||||
|
"failspot",
|
||||||
|
"goblin",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mach2",
|
||||||
|
"memmap2",
|
||||||
|
"minidump-common 0.26.1",
|
||||||
|
"process-backend",
|
||||||
|
"procfs-core",
|
||||||
|
"scroll 0.12.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -2377,6 +2578,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multiversion_no_op"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "30.0.1"
|
version = "30.0.1"
|
||||||
@@ -2467,6 +2674,17 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2966,6 +3184,12 @@ version = "0.3.34"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plain"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -3051,6 +3275,63 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "process-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07c02e259a3522eccc4957ee6e922db4945648fb7730a41192d919518d06a530"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"process-reader",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "process-reader"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3024e1803f4b8c40cca8c1955243410354cb4d67f053af9210147416a7265535"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "procfs-core"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.2",
|
||||||
|
"hex",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"prost-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-derive"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psm"
|
name = "psm"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@@ -3105,6 +3386,15 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "range-map"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.30.2"
|
version = "0.30.2"
|
||||||
@@ -3302,6 +3592,46 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"
|
||||||
|
dependencies = [
|
||||||
|
"scroll_derive 0.12.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
|
||||||
|
dependencies = [
|
||||||
|
"scroll_derive 0.13.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll_derive"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scroll_derive"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1a36a382ed65dbcc0ab47fd5e9a94112417ccd34560a392ef3b7b0f0ec39148"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
@@ -3443,6 +3773,12 @@ version = "0.3.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -3470,6 +3806,17 @@ version = "1.16.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
|
checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smart-default"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stacker"
|
name = "stacker"
|
||||||
version = "0.1.25"
|
version = "0.1.25"
|
||||||
@@ -3681,7 +4028,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"memmem",
|
"memmem",
|
||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
"num-derive",
|
"num-derive 0.4.2",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"pest",
|
"pest",
|
||||||
@@ -3757,6 +4104,7 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3765,6 +4113,16 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -3848,6 +4206,7 @@ version = "0.1.44"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
@@ -3968,6 +4327,26 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vswhom"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"vswhom-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vswhom-sys"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vtparse"
|
name = "vtparse"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -4343,6 +4722,16 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.56.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.57.1"
|
version = "0.57.1"
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ ghostty-core = { path = "crates/ghostty-core" }
|
|||||||
ghostty-apprt-gtk = { path = "crates/ghostty-apprt-gtk" }
|
ghostty-apprt-gtk = { path = "crates/ghostty-apprt-gtk" }
|
||||||
ghostty-apprt = { path = "crates/ghostty-apprt" }
|
ghostty-apprt = { path = "crates/ghostty-apprt" }
|
||||||
ghostty-cli = { path = "crates/ghostty-cli" }
|
ghostty-cli = { path = "crates/ghostty-cli" }
|
||||||
|
ghostty-build-info = { path = "crates/ghostty-build-info" }
|
||||||
ghostty-crash = { path = "crates/ghostty-crash" }
|
ghostty-crash = { path = "crates/ghostty-crash" }
|
||||||
ghostty-bench = { path = "crates/ghostty-bench" }
|
ghostty-bench = { path = "crates/ghostty-bench" }
|
||||||
ghostty-inspector = { path = "crates/ghostty-inspector" }
|
ghostty-inspector = { path = "crates/ghostty-inspector" }
|
||||||
@@ -119,3 +120,6 @@ overflow-checks = false
|
|||||||
lto = true
|
lto = true
|
||||||
incremental = false
|
incremental = false
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
embed-resource = "3.0.12"
|
||||||
@@ -23,6 +23,7 @@ ghostty-config.workspace = true
|
|||||||
ghostty-input.workspace = true
|
ghostty-input.workspace = true
|
||||||
ghostty-datastruct.workspace = true
|
ghostty-datastruct.workspace = true
|
||||||
ghostty-inspector.workspace = true
|
ghostty-inspector.workspace = true
|
||||||
|
ghostty-build-info.workspace = true
|
||||||
ghostty-os.workspace = true
|
ghostty-os.workspace = true
|
||||||
ghostty-vt.workspace = true
|
ghostty-vt.workspace = true
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|||||||
@@ -69,6 +69,17 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=resources");
|
println!("cargo:rerun-if-changed=resources");
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
|
||||||
|
// Source tarballs ship the prebuilt GResource bundle so packagers need
|
||||||
|
// neither blueprint-compiler nor glib-compile-resources (Zig:
|
||||||
|
// `GhosttyDist.Resource` for `ghostty_resources.{c,h}`). As in Zig, it is
|
||||||
|
// never used in a git checkout.
|
||||||
|
println!("cargo:rerun-if-changed=dist/ghostty.gresource");
|
||||||
|
let prebuilt = manifest.join("dist/ghostty.gresource");
|
||||||
|
if prebuilt.is_file() && !manifest.join("../../.git").exists() {
|
||||||
|
std::fs::copy(&prebuilt, out.join("ghostty.gresource")).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut xml = String::new();
|
let mut xml = String::new();
|
||||||
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gresources>\n");
|
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gresources>\n");
|
||||||
|
|
||||||
|
|||||||
@@ -1512,6 +1512,12 @@ impl Surface {
|
|||||||
clipboard::paste(self, text);
|
clipboard::paste(self, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`Surface::paste`] for raw bytes (e.g. dropped file names that are
|
||||||
|
/// not valid UTF-8 must reach the pty unchanged).
|
||||||
|
pub fn paste_bytes(&self, bytes: &[u8]) {
|
||||||
|
clipboard::paste_bytes(self, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
/// Focus this surface. This properly focuses the input part of
|
/// Focus this surface. This properly focuses the input part of
|
||||||
/// our surface. (Shadows `WidgetExt::grab_focus` on purpose, like Zig.)
|
/// our surface. (Shadows `WidgetExt::grab_focus` on purpose, like Zig.)
|
||||||
pub fn grab_focus(&self) {
|
pub fn grab_focus(&self) {
|
||||||
@@ -2622,6 +2628,26 @@ mod clipboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Paste explicit bytes. Valid UTF-8 takes the normal text path; other
|
||||||
|
/// bytes use the byte-preserving paste (the confirmation dialog, which
|
||||||
|
/// is text based, carries them with the reversible byte escape).
|
||||||
|
pub(super) fn paste_bytes(surface: &Surface, bytes: &[u8]) {
|
||||||
|
if let Ok(text) = std::str::from_utf8(bytes) {
|
||||||
|
paste(surface, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(core) = surface.core() else { return };
|
||||||
|
match core.complete_paste_bytes(bytes, false) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(ClipboardError::UnsafePaste | ClipboardError::UnauthorizedPaste) => {
|
||||||
|
let text = ghostty_os::osbytes::bytes_to_config_string(bytes);
|
||||||
|
show_clipboard_confirmation(surface, ClipboardRequest::Paste, &text);
|
||||||
|
}
|
||||||
|
Err(err) => log::warn!("failed to complete clipboard request err={err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the specific type of clipboard for a widget.
|
/// Get the specific type of clipboard for a widget.
|
||||||
fn get(widget: >k::Widget, clipboard: Clipboard) -> gdk::Clipboard {
|
fn get(widget: >k::Widget, clipboard: Clipboard) -> gdk::Clipboard {
|
||||||
match clipboard {
|
match clipboard {
|
||||||
@@ -2661,7 +2687,13 @@ mod clipboard {
|
|||||||
|
|
||||||
// Get our text
|
// Get our text
|
||||||
let text = buf.text(&buf.start_iter(), &buf.end_iter(), false);
|
let text = buf.text(&buf.start_iter(), &buf.end_iter(), false);
|
||||||
if let Err(err) = core.complete_clipboard_request(req, &text, true) {
|
let result = if matches!(req, ClipboardRequest::Paste) {
|
||||||
|
// Undo the byte escape used for non-UTF-8 pastes.
|
||||||
|
core.complete_paste_bytes(&ghostty_os::osbytes::config_str_to_bytes(&text), true)
|
||||||
|
} else {
|
||||||
|
core.complete_clipboard_request(req, &text, true)
|
||||||
|
};
|
||||||
|
if let Err(err) = result {
|
||||||
log::warn!("failed to complete clipboard request: {err}");
|
log::warn!("failed to complete clipboard request: {err}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3375,17 +3407,14 @@ mod imp {
|
|||||||
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
|
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
|
||||||
for file in list.files() {
|
for file in list.files() {
|
||||||
let Some(path) = file.path() else { continue };
|
let Some(path) = file.path() else { continue };
|
||||||
let path = path.to_string_lossy().into_owned();
|
// Raw path bytes: non-UTF-8 names must survive.
|
||||||
if let Err(err) = w
|
let path = ghostty_os::osbytes::os_bytes(path.as_os_str());
|
||||||
.write_all(path.as_bytes())
|
if let Err(err) = w.write_all(&path).and_then(|()| w.write_all(b"\n")) {
|
||||||
.and_then(|()| w.write_all(b"\n"))
|
|
||||||
{
|
|
||||||
log::error!("unable to write path to buffer: {err}");
|
log::error!("unable to write path to buffer: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let string = String::from_utf8_lossy(&escaped).into_owned();
|
this.paste_bytes(&escaped);
|
||||||
this.paste(&string);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3397,7 +3426,7 @@ mod imp {
|
|||||||
{
|
{
|
||||||
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
|
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
|
||||||
if let Err(err) = w
|
if let Err(err) = w
|
||||||
.write_all(path.to_string_lossy().as_bytes())
|
.write_all(&ghostty_os::osbytes::os_bytes(path.as_os_str()))
|
||||||
.and_then(|()| w.write_all(b"\n"))
|
.and_then(|()| w.write_all(b"\n"))
|
||||||
{
|
{
|
||||||
log::error!("unable to write path to buffer: {err}");
|
log::error!("unable to write path to buffer: {err}");
|
||||||
|
|||||||
@@ -113,11 +113,11 @@ mod imp {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if data.exit_code == 0 {
|
if data.exit_code == 0 {
|
||||||
banner.set_title("Command succeeded");
|
banner.set_title(&ghostty_os::i18n::tr("Command succeeded"));
|
||||||
this.add_css_class("normal");
|
this.add_css_class("normal");
|
||||||
this.remove_css_class("abnormal");
|
this.remove_css_class("abnormal");
|
||||||
} else {
|
} else {
|
||||||
banner.set_title("Command failed");
|
banner.set_title(&ghostty_os::i18n::tr("Command failed"));
|
||||||
this.remove_css_class("normal");
|
this.remove_css_class("normal");
|
||||||
this.add_css_class("abnormal");
|
this.add_css_class("abnormal");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1662,7 +1662,7 @@ impl Window {
|
|||||||
.application_name(name)
|
.application_name(name)
|
||||||
.developer_name(tr("Ghostty Developers"))
|
.developer_name(tr("Ghostty Developers"))
|
||||||
.application_icon(icon)
|
.application_icon(icon)
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(ghostty_build_info::VERSION_STRING)
|
||||||
.issue_url("https://github.com/ghostty-org/ghostty/issues")
|
.issue_url("https://github.com/ghostty-org/ghostty/issues")
|
||||||
.website(website)
|
.website(website)
|
||||||
.build();
|
.build();
|
||||||
@@ -1675,7 +1675,7 @@ impl Window {
|
|||||||
("program-name", &name),
|
("program-name", &name),
|
||||||
("logo-icon-name", &icon),
|
("logo-icon-name", &icon),
|
||||||
("title", &tr("About Ghostty")),
|
("title", &tr("About Ghostty")),
|
||||||
("version", &env!("CARGO_PKG_VERSION")),
|
("version", &ghostty_build_info::VERSION_STRING),
|
||||||
("website", &website),
|
("website", &website),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -649,6 +649,11 @@ impl std::fmt::Display for SetTitle {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Pwd {
|
pub struct Pwd {
|
||||||
|
/// The working directory as reported by OSC 7. Zig keeps raw bytes; here
|
||||||
|
/// non-UTF-8 bytes are carried with the reversible escape of
|
||||||
|
/// `ghostty_os::osbytes` (valid UTF-8 is unchanged). Decode with
|
||||||
|
/// `osbytes::config_str_to_bytes` / `config_str_to_os` before using it
|
||||||
|
/// as a path or handing it to C.
|
||||||
pub pwd: String,
|
pub pwd: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,15 @@ pub trait CoreSurface {
|
|||||||
data: &str,
|
data: &str,
|
||||||
confirmed: bool,
|
confirmed: bool,
|
||||||
) -> Result<(), ClipboardError>;
|
) -> Result<(), ClipboardError>;
|
||||||
|
/// [`CoreSurface::complete_clipboard_request`] for a paste of raw bytes
|
||||||
|
/// (Zig data is `[]const u8`). The default converts lossily.
|
||||||
|
fn complete_paste_bytes(&self, data: &[u8], confirmed: bool) -> Result<(), ClipboardError> {
|
||||||
|
self.complete_clipboard_request(
|
||||||
|
ClipboardRequest::Paste,
|
||||||
|
&String::from_utf8_lossy(data),
|
||||||
|
confirmed,
|
||||||
|
)
|
||||||
|
}
|
||||||
/// After the user confirms (`surface.config.clipboard_read = .allow`).
|
/// After the user confirms (`surface.config.clipboard_read = .allow`).
|
||||||
fn set_clipboard_read_allowed(&self, allow: bool);
|
fn set_clipboard_read_allowed(&self, allow: bool);
|
||||||
/// See [`CoreSurface::set_clipboard_read_allowed`] (`clipboard_write`).
|
/// See [`CoreSurface::set_clipboard_read_allowed`] (`clipboard_write`).
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ ghostty-datastruct.workspace = true
|
|||||||
ghostty-font.workspace = true
|
ghostty-font.workspace = true
|
||||||
ghostty-inspector.workspace = true
|
ghostty-inspector.workspace = true
|
||||||
ghostty-input.workspace = true
|
ghostty-input.workspace = true
|
||||||
|
ghostty-build-info.workspace = true
|
||||||
ghostty-os.workspace = true
|
ghostty-os.workspace = true
|
||||||
ghostty-renderer.workspace = true
|
ghostty-renderer.workspace = true
|
||||||
ghostty-terminal.workspace = true
|
ghostty-terminal.workspace = true
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ pub struct GlobalState {
|
|||||||
static STATE: OnceLock<GlobalState> = OnceLock::new();
|
static STATE: OnceLock<GlobalState> = OnceLock::new();
|
||||||
|
|
||||||
/// Options for [`init`].
|
/// Options for [`init`].
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct InitOptions {
|
pub struct InitOptions {
|
||||||
/// A CLI action is being executed: disable stderr logging by default
|
/// A CLI action is being executed: disable stderr logging by default
|
||||||
/// since the action writes to stderr/stdout itself.
|
/// since the action writes to stderr/stdout itself.
|
||||||
@@ -126,6 +126,32 @@ pub struct InitOptions {
|
|||||||
/// `apprt.runtime.resourcesDir`, e.g. GTK's Flatpak host path lookup).
|
/// `apprt.runtime.resourcesDir`, e.g. GTK's Flatpak host path lookup).
|
||||||
/// `None` uses the generic `ghostty_os::resourcesdir::resources_dir`.
|
/// `None` uses the generic `ghostty_os::resourcesdir::resources_dir`.
|
||||||
pub resources_dir: Option<fn() -> std::io::Result<ResourcesDir>>,
|
pub resources_dir: Option<fn() -> std::io::Result<ResourcesDir>>,
|
||||||
|
|
||||||
|
/// Crash reporting metadata (version, build mode, runtime, font backend
|
||||||
|
/// and renderer tags; Zig: `build_config`). `None` uses
|
||||||
|
/// [`default_crash_options`].
|
||||||
|
pub crash: Option<ghostty_crash::Options>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crash metadata for this build when the caller supplies none: the
|
||||||
|
/// version from the shared build info and the compile-time platform
|
||||||
|
/// defaults (Metal + CoreText on Apple targets, fontconfig + OpenGL
|
||||||
|
/// elsewhere).
|
||||||
|
#[must_use]
|
||||||
|
pub fn default_crash_options() -> ghostty_crash::Options {
|
||||||
|
let mut o = ghostty_crash::Options {
|
||||||
|
release: ghostty_build_info::VERSION_STRING.to_owned(),
|
||||||
|
build_mode: ghostty_build_info::BUILD_MODE
|
||||||
|
.trim_start_matches('.')
|
||||||
|
.to_owned(),
|
||||||
|
..ghostty_crash::Options::default()
|
||||||
|
};
|
||||||
|
if cfg!(target_vendor = "apple") {
|
||||||
|
"none".clone_into(&mut o.app_runtime);
|
||||||
|
"coretext".clone_into(&mut o.font_backend);
|
||||||
|
"metal".clone_into(&mut o.renderer);
|
||||||
|
}
|
||||||
|
o
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The global state. If [`init`] was never called, a default state (no
|
/// The global state. If [`init`] was never called, a default state (no
|
||||||
@@ -182,13 +208,17 @@ fn init_with(opts: InitOptions, default_logging: Logging) -> &'static GlobalStat
|
|||||||
let rlimits = ResourceLimits::init();
|
let rlimits = ResourceLimits::init();
|
||||||
|
|
||||||
// Initialize our crash reporting.
|
// Initialize our crash reporting.
|
||||||
if let Err(err) = ghostty_crash::init(&ghostty_crash::Options {
|
let crash_opts = opts.crash.clone().unwrap_or_else(default_crash_options);
|
||||||
release: ghostty_termio::stream_handler::VERSION_STRING.to_owned(),
|
if let Err(err) = ghostty_crash::init(&crash_opts) {
|
||||||
..ghostty_crash::Options::default()
|
|
||||||
}) {
|
|
||||||
log::warn!("sentry init failed, no crash capture available err={err}");
|
log::warn!("sentry init failed, no crash capture available err={err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to make sure the process locale is set properly. Locale
|
||||||
|
// affects a lot of behaviors in a shell.
|
||||||
|
if let Some(locale) = ghostty_os::ensure_locale() {
|
||||||
|
log::debug!("locale set locale={locale}");
|
||||||
|
}
|
||||||
|
|
||||||
// Find our resources directory once for the app so every launch
|
// Find our resources directory once for the app so every launch
|
||||||
// hereafter can use this cached value.
|
// hereafter can use this cached value.
|
||||||
let discover = opts
|
let discover = opts
|
||||||
@@ -352,6 +382,19 @@ mod tests {
|
|||||||
assert_eq!(log_scope("other::x"), "other");
|
assert_eq!(log_scope("other::x"), "other");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crash_defaults_use_shared_version() {
|
||||||
|
let o = default_crash_options();
|
||||||
|
assert_eq!(o.release, ghostty_build_info::VERSION_STRING);
|
||||||
|
if cfg!(target_vendor = "apple") {
|
||||||
|
assert_eq!(o.renderer, "metal");
|
||||||
|
assert_eq!(o.font_backend, "coretext");
|
||||||
|
assert_eq!(o.app_runtime, "none");
|
||||||
|
} else {
|
||||||
|
assert_eq!(o.renderer, "opengl");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn logging_parse() {
|
fn logging_parse() {
|
||||||
assert!(Logging::parse("stderr").stderr);
|
assert!(Logging::parse("stderr").stderr);
|
||||||
|
|||||||
@@ -424,6 +424,7 @@ pub fn write_screen_temp_file(
|
|||||||
loc: WriteScreenLoc,
|
loc: WriteScreenLoc,
|
||||||
emit: WriteScreenFormat,
|
emit: WriteScreenFormat,
|
||||||
) -> Result<String, CoreError> {
|
) -> Result<String, CoreError> {
|
||||||
|
#[cfg(unix)]
|
||||||
use std::os::unix::fs::OpenOptionsExt as _;
|
use std::os::unix::fs::OpenOptionsExt as _;
|
||||||
|
|
||||||
// Create a temporary directory to store our scrollback.
|
// Create a temporary directory to store our scrollback.
|
||||||
@@ -432,12 +433,11 @@ pub fn write_screen_temp_file(
|
|||||||
let file_path = tmp_dir.path().join(&filename);
|
let file_path = tmp_dir.path().join(&filename);
|
||||||
|
|
||||||
let result = (|| -> Result<String, CoreError> {
|
let result = (|| -> Result<String, CoreError> {
|
||||||
let mut file = std::fs::OpenOptions::new()
|
let mut opts = std::fs::OpenOptions::new();
|
||||||
.write(true)
|
opts.write(true).create(true).truncate(true);
|
||||||
.create(true)
|
#[cfg(unix)]
|
||||||
.truncate(true)
|
opts.mode(0o600);
|
||||||
.mode(0o600)
|
let mut file = opts.open(&file_path)?;
|
||||||
.open(&file_path)?;
|
|
||||||
file.write_all(contents.as_bytes())?;
|
file.write_all(contents.as_bytes())?;
|
||||||
file.flush()?;
|
file.flush()?;
|
||||||
Ok(std::fs::canonicalize(&file_path)?
|
Ok(std::fs::canonicalize(&file_path)?
|
||||||
@@ -1272,10 +1272,13 @@ mod tests {
|
|||||||
assert!(path.ends_with("screen.txt"));
|
assert!(path.ends_with("screen.txt"));
|
||||||
let read = std::fs::read_to_string(&path).unwrap();
|
let read = std::fs::read_to_string(&path).unwrap();
|
||||||
assert_eq!(read, s);
|
assert_eq!(read, s);
|
||||||
let mode = std::os::unix::fs::PermissionsExt::mode(
|
#[cfg(unix)]
|
||||||
&std::fs::metadata(&path).unwrap().permissions(),
|
{
|
||||||
);
|
let mode = std::os::unix::fs::PermissionsExt::mode(
|
||||||
assert_eq!(mode & 0o777, 0o600);
|
&std::fs::metadata(&path).unwrap().permissions(),
|
||||||
|
);
|
||||||
|
assert_eq!(mode & 0o777, 0o600);
|
||||||
|
}
|
||||||
let _ = std::fs::remove_dir_all(std::path::Path::new(&path).parent().unwrap());
|
let _ = std::fs::remove_dir_all(std::path::Path::new(&path).parent().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,6 +215,12 @@ impl Surface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`Surface::complete_clipboard_request`] for a paste of raw bytes
|
||||||
|
/// (Zig passes `[]const u8`; e.g. dropped non-UTF-8 file names).
|
||||||
|
pub fn complete_paste_bytes(&self, data: &[u8], confirmed: bool) -> Result<(), ClipboardError> {
|
||||||
|
self.complete_clipboard_paste(data, confirmed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Paste text (also the target of `text_callback`).
|
/// Paste text (also the target of `text_callback`).
|
||||||
pub(crate) fn complete_clipboard_paste(
|
pub(crate) fn complete_clipboard_paste(
|
||||||
&self,
|
&self,
|
||||||
@@ -390,7 +396,13 @@ impl Surface {
|
|||||||
|
|
||||||
/// `textCallback`: text input that is pasted verbatim.
|
/// `textCallback`: text input that is pasted verbatim.
|
||||||
pub fn text_callback(&self, text: &str) -> Result<(), ClipboardError> {
|
pub fn text_callback(&self, text: &str) -> Result<(), ClipboardError> {
|
||||||
|
self.text_callback_bytes(text.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Surface::text_callback`] for raw bytes (Zig takes `[]const u8`;
|
||||||
|
/// the embedded `ghostty_surface_text` forwards the host bytes as is).
|
||||||
|
pub fn text_callback_bytes(&self, text: &[u8]) -> Result<(), ClipboardError> {
|
||||||
let _crash = self.crash_guard();
|
let _crash = self.crash_guard();
|
||||||
self.complete_clipboard_paste(text.as_bytes(), true)
|
self.complete_clipboard_paste(text, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,10 +471,15 @@ impl Surface {
|
|||||||
.and_then(|wd| wd.value())
|
.and_then(|wd| wd.value())
|
||||||
.map(str::to_owned);
|
.map(str::to_owned);
|
||||||
ecfg.resources_dir = global::state().resources_dir.host().map(str::to_owned);
|
ecfg.resources_dir = global::state().resources_dir.host().map(str::to_owned);
|
||||||
ecfg.rt_pre_exec_info = ghostty_termio::command::RtPreExecInfo::init(&config);
|
// The Windows exec backend has no pre-exec/post-fork hooks,
|
||||||
ecfg.rt_post_fork_info = ghostty_termio::command::RtPostForkInfo::init(&config);
|
// cgroup scopes or rlimits (Zig: Linux/POSIX only).
|
||||||
ecfg.create_scope = rt_surface.create_scope_fn();
|
#[cfg(unix)]
|
||||||
ecfg.restore_rlimit = global::state().rlimits.nofile;
|
{
|
||||||
|
ecfg.rt_pre_exec_info = ghostty_termio::command::RtPreExecInfo::init(&config);
|
||||||
|
ecfg.rt_post_fork_info = ghostty_termio::command::RtPostForkInfo::init(&config);
|
||||||
|
ecfg.create_scope = rt_surface.create_scope_fn();
|
||||||
|
ecfg.restore_rlimit = global::state().rlimits.nofile;
|
||||||
|
}
|
||||||
|
|
||||||
let exec = Exec::new(ecfg)?;
|
let exec = Exec::new(ecfg)?;
|
||||||
let args = exec.args().to_vec();
|
let args = exec.args().to_vec();
|
||||||
@@ -935,7 +940,9 @@ impl Surface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Message::PwdChange(w) => {
|
Message::PwdChange(w) => {
|
||||||
let pwd = String::from_utf8_lossy(w.slice()).into_owned();
|
// Keep the OSC 7 bytes (see `Pwd`): non-UTF-8 bytes are
|
||||||
|
// carried with the reversible byte escape.
|
||||||
|
let pwd = ghostty_os::osbytes::bytes_to_config_string(w.slice());
|
||||||
self.perform(RtAction::Pwd(Pwd { pwd }));
|
self.perform(RtAction::Pwd(Pwd { pwd }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1364,7 +1371,14 @@ impl Surface {
|
|||||||
pub fn pwd(&self) -> Option<String> {
|
pub fn pwd(&self) -> Option<String> {
|
||||||
let t = self.lock_terminal();
|
let t = self.lock_terminal();
|
||||||
let pwd = t.get_pwd()?;
|
let pwd = t.get_pwd()?;
|
||||||
Some(String::from_utf8_lossy(pwd).into_owned())
|
Some(ghostty_os::osbytes::bytes_to_config_string(pwd))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The raw bytes of [`Surface::pwd`] (Zig returns the OSC 7 bytes as is).
|
||||||
|
#[must_use]
|
||||||
|
pub fn pwd_bytes(&self) -> Option<Vec<u8>> {
|
||||||
|
let t = self.lock_terminal();
|
||||||
|
t.get_pwd().map(<[u8]>::to_vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the x/y coordinate of where the IME (Input Method Editor)
|
/// Returns the x/y coordinate of where the IME (Input Method Editor)
|
||||||
@@ -2055,6 +2069,10 @@ impl CoreSurface for Surface {
|
|||||||
Surface::complete_clipboard_request(self, req, data, confirmed)
|
Surface::complete_clipboard_request(self, req, data, confirmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complete_paste_bytes(&self, data: &[u8], confirmed: bool) -> Result<(), ClipboardError> {
|
||||||
|
Surface::complete_paste_bytes(self, data, confirmed)
|
||||||
|
}
|
||||||
|
|
||||||
fn set_clipboard_read_allowed(&self, allow: bool) {
|
fn set_clipboard_read_allowed(&self, allow: bool) {
|
||||||
use ghostty_config::types_b::ClipboardAccess;
|
use ghostty_config::types_b::ClipboardAccess;
|
||||||
self.set_config_clipboard(
|
self.set_config_clipboard(
|
||||||
|
|||||||
@@ -1664,7 +1664,7 @@ impl Surface {
|
|||||||
sel: link.selection,
|
sel: link.selection,
|
||||||
trim: false,
|
trim: false,
|
||||||
});
|
});
|
||||||
let pwd = t.get_pwd().map(|p| String::from_utf8_lossy(p).into_owned());
|
let pwd = t.get_pwd().map(ghostty_os::osbytes::bytes_to_config_string);
|
||||||
resolve_path_for_opening(pwd.as_deref(), &str).unwrap_or(str)
|
resolve_path_for_opening(pwd.as_deref(), &str).unwrap_or(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,24 @@ fn pwd_osc7() {
|
|||||||
assert_eq!(h.surface.pwd().as_deref(), Some("/tmp/a b"));
|
assert_eq!(h.surface.pwd().as_deref(), Some("/tmp/a b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pwd_osc7_keeps_non_utf8_bytes() {
|
||||||
|
let h = harness!(Harness::recorder(
|
||||||
|
r"\033]7;file://localhost/tmp/a%%FFb\033\\",
|
||||||
|
&[]
|
||||||
|
));
|
||||||
|
let pwd = h.wait_action("pwd", |a| match a {
|
||||||
|
Action::Pwd(p) if !p.pwd.is_empty() => Some(p.pwd.clone()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
ghostty_os::osbytes::config_str_to_bytes(&pwd),
|
||||||
|
b"/tmp/a\xffb"
|
||||||
|
);
|
||||||
|
assert_eq!(h.surface.pwd_bytes().as_deref(), Some(&b"/tmp/a\xffb"[..]));
|
||||||
|
assert_eq!(h.surface.pwd().as_deref(), Some(pwd.as_str()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bell_rings_once_per_burst() {
|
fn bell_rings_once_per_burst() {
|
||||||
let h = harness!(Harness::recorder(r"\007", &[]));
|
let h = harness!(Harness::recorder(r"\007", &[]));
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ ghostty-bench.workspace = true
|
|||||||
ghostty-cli.workspace = true
|
ghostty-cli.workspace = true
|
||||||
ghostty-config.workspace = true
|
ghostty-config.workspace = true
|
||||||
ghostty-core.workspace = true
|
ghostty-core.workspace = true
|
||||||
|
ghostty-crash.workspace = true
|
||||||
ghostty-input.workspace = true
|
ghostty-input.workspace = true
|
||||||
ghostty-inspector.workspace = true
|
ghostty-inspector.workspace = true
|
||||||
ghostty-os.workspace = true
|
ghostty-os.workspace = true
|
||||||
@@ -31,6 +32,9 @@ ghostty-terminal.workspace = true
|
|||||||
ghostty-vt.workspace = true
|
ghostty-vt.workspace = true
|
||||||
log = "0.4.34"
|
log = "0.4.34"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2.190"
|
||||||
|
|
||||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||||
block2 = "0.6.2"
|
block2 = "0.6.2"
|
||||||
dispatch2 = "0.3.1"
|
dispatch2 = "0.3.1"
|
||||||
@@ -40,6 +44,7 @@ objc2-foundation = "0.3.2"
|
|||||||
objc2-metal = { version = "0.3.2", features = ["block2"] }
|
objc2-metal = { version = "0.3.2", features = ["block2"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
ghostty-build-info.workspace = true
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
# The workspace forbids `unsafe`; this crate is the C ABI boundary, so it
|
# The workspace forbids `unsafe`; this crate is the C ABI boundary, so it
|
||||||
|
|||||||
@@ -36,7 +36,14 @@ pub struct Keep {
|
|||||||
impl Keep {
|
impl Keep {
|
||||||
/// A NUL terminated copy of `s` (interior NULs are dropped).
|
/// A NUL terminated copy of `s` (interior NULs are dropped).
|
||||||
fn cstr(&mut self, s: &str) -> *const c_char {
|
fn cstr(&mut self, s: &str) -> *const c_char {
|
||||||
let c = CString::new(s.replace('\0', "")).unwrap_or_default();
|
self.cstr_bytes(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Keep::cstr`] for raw bytes.
|
||||||
|
fn cstr_bytes(&mut self, s: &[u8]) -> *const c_char {
|
||||||
|
let mut s = s.to_vec();
|
||||||
|
s.retain(|&b| b != 0);
|
||||||
|
let c = CString::new(s).unwrap_or_default();
|
||||||
let ptr = c.as_ptr();
|
let ptr = c.as_ptr();
|
||||||
// The heap buffer of a CString doesn't move when the Vec grows.
|
// The heap buffer of a CString doesn't move when the Vec grows.
|
||||||
self.strings.push(c);
|
self.strings.push(c);
|
||||||
@@ -123,7 +130,9 @@ pub fn action_to_c(action: &Action, keep: &mut Keep) -> ghostty_action_s {
|
|||||||
Action::SetTitle(t) => v.set_title.title = keep.cstr(&t.title),
|
Action::SetTitle(t) => v.set_title.title = keep.cstr(&t.title),
|
||||||
Action::SetTabTitle(t) => v.set_tab_title.title = keep.cstr(&t.title),
|
Action::SetTabTitle(t) => v.set_tab_title.title = keep.cstr(&t.title),
|
||||||
Action::PromptTitle(p) => v.prompt_title = *p as c_int,
|
Action::PromptTitle(p) => v.prompt_title = *p as c_int,
|
||||||
Action::Pwd(p) => v.pwd.pwd = keep.cstr(&p.pwd),
|
Action::Pwd(p) => {
|
||||||
|
v.pwd.pwd = keep.cstr_bytes(&ghostty_os::osbytes::config_str_to_bytes(&p.pwd));
|
||||||
|
}
|
||||||
Action::MouseShape(s) => v.mouse_shape = *s as c_int,
|
Action::MouseShape(s) => v.mouse_shape = *s as c_int,
|
||||||
Action::MouseVisibility(m) => v.mouse_visibility = *m as c_int,
|
Action::MouseVisibility(m) => v.mouse_visibility = *m as c_int,
|
||||||
Action::MouseOverLink(l) => {
|
Action::MouseOverLink(l) => {
|
||||||
|
|||||||
@@ -249,9 +249,10 @@ pub unsafe extern "C" fn ghostty_config_load_file(ptr: ghostty_config_t, path: *
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// SAFETY: `path` is NUL terminated.
|
// SAFETY: `path` is NUL terminated.
|
||||||
let path = unsafe { CStr::from_ptr(path) }
|
// Non-UTF-8 file names are valid on Unix; keep the bytes (reversible
|
||||||
.to_string_lossy()
|
// escape understood by the config file loader).
|
||||||
.into_owned();
|
let path =
|
||||||
|
ghostty_os::osbytes::bytes_to_config_string(unsafe { CStr::from_ptr(path) }.to_bytes());
|
||||||
if let Err(err) = c.config.load_file(&path) {
|
if let Err(err) = c.config.load_file(&path) {
|
||||||
log::error!("error loading config from file path={path} err={err}");
|
log::error!("error loading config from file path={path} err={err}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ use ghostty_input::{
|
|||||||
binding::Action as BindingAction,
|
binding::Action as BindingAction,
|
||||||
keycodes,
|
keycodes,
|
||||||
};
|
};
|
||||||
|
use ghostty_os::osbytes;
|
||||||
use ghostty_terminal::{
|
use ghostty_terminal::{
|
||||||
point::{Coordinate, Point, Tag as PointTag},
|
point::{Coordinate, Point, Tag as PointTag},
|
||||||
selection::Selection,
|
selection::Selection,
|
||||||
@@ -884,17 +885,22 @@ fn string_escape(input: &str) -> String {
|
|||||||
unsafe fn apply_surface_options(config: &mut Config, opts: &ghostty_surface_config_s) {
|
unsafe fn apply_surface_options(config: &mut Config, opts: &ghostty_surface_config_s) {
|
||||||
// If we have a working directory from the options then we set it.
|
// If we have a working directory from the options then we set it.
|
||||||
// SAFETY: per the function contract.
|
// SAFETY: per the function contract.
|
||||||
if let Some(wd) =
|
if let Some(wd) = unsafe { cstr(opts.working_directory) }.map(CStr::to_bytes)
|
||||||
unsafe { cstr(opts.working_directory) }.map(|c| c.to_string_lossy().into_owned())
|
|
||||||
&& !wd.is_empty()
|
&& !wd.is_empty()
|
||||||
{
|
{
|
||||||
match std::fs::metadata(&wd) {
|
// Raw bytes are kept (reversible escape in the String based config).
|
||||||
Err(err) => log::warn!("error opening requested working directory dir={wd} err={err}"),
|
let wd_str = osbytes::bytes_to_config_string(wd);
|
||||||
|
let wd_os = osbytes::os_from_bytes(wd.to_vec());
|
||||||
|
let wd_disp = String::from_utf8_lossy(wd);
|
||||||
|
match std::fs::metadata(&wd_os) {
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("error opening requested working directory dir={wd_disp} err={err}");
|
||||||
|
}
|
||||||
Ok(m) if !m.is_dir() => {
|
Ok(m) if !m.is_dir() => {
|
||||||
log::warn!("requested working directory is not a directory dir={wd}");
|
log::warn!("requested working directory is not a directory dir={wd_disp}");
|
||||||
}
|
}
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let mut wd_val = WorkingDirectory::Path(wd);
|
let mut wd_val = WorkingDirectory::Path(wd_str);
|
||||||
wd_val.finalize();
|
wd_val.finalize();
|
||||||
config.working_directory = Some(wd_val);
|
config.working_directory = Some(wd_val);
|
||||||
}
|
}
|
||||||
@@ -903,10 +909,10 @@ unsafe fn apply_surface_options(config: &mut Config, opts: &ghostty_surface_conf
|
|||||||
|
|
||||||
// If we have a command from the options then we set it.
|
// If we have a command from the options then we set it.
|
||||||
// SAFETY: per the function contract.
|
// SAFETY: per the function contract.
|
||||||
if let Some(cmd) = unsafe { cstr(opts.command) }.map(|c| c.to_string_lossy().into_owned())
|
if let Some(cmd) = unsafe { cstr(opts.command) }.map(CStr::to_bytes)
|
||||||
&& !cmd.is_empty()
|
&& !cmd.is_empty()
|
||||||
{
|
{
|
||||||
config.command = Some(Command::Shell(cmd));
|
config.command = Some(Command::Shell(osbytes::bytes_to_config_string(cmd)));
|
||||||
config.wait_after_command = true;
|
config.wait_after_command = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,7 +925,10 @@ unsafe fn apply_surface_options(config: &mut Config, opts: &ghostty_surface_conf
|
|||||||
let (Some(k), Some(v)) = (unsafe { cstr(var.key) }, unsafe { cstr(var.value) }) else {
|
let (Some(k), Some(v)) = (unsafe { cstr(var.key) }, unsafe { cstr(var.value) }) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
config.env.put(&k.to_string_lossy(), &v.to_string_lossy());
|
config.env.put(
|
||||||
|
&osbytes::bytes_to_config_string(k.to_bytes()),
|
||||||
|
&osbytes::bytes_to_config_string(v.to_bytes()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,10 +938,9 @@ unsafe fn apply_surface_options(config: &mut Config, opts: &ghostty_surface_conf
|
|||||||
// We need to escape the string because the "raw" field expects a
|
// We need to escape the string because the "raw" field expects a
|
||||||
// Zig string.
|
// Zig string.
|
||||||
config.input.list.clear();
|
config.input.list.clear();
|
||||||
config
|
config.input.list.push(ReadableIO::Raw(string_escape(
|
||||||
.input
|
&osbytes::bytes_to_config_string(input.to_bytes()),
|
||||||
.list
|
)));
|
||||||
.push(ReadableIO::Raw(string_escape(&input.to_string_lossy())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait after command
|
// Wait after command
|
||||||
@@ -1107,7 +1115,7 @@ pub unsafe extern "C" fn ghostty_surface_inherited_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if should_inherit_working_directory(context, &config)
|
if should_inherit_working_directory(context, &config)
|
||||||
&& let Some(cwd) = core.pwd()
|
&& let Some(cwd) = core.pwd_bytes()
|
||||||
&& let Ok(c) = CString::new(cwd)
|
&& let Ok(c) = CString::new(cwd)
|
||||||
{
|
{
|
||||||
let mut keep = s.inherited_wd.borrow_mut();
|
let mut keep = s.inherited_wd.borrow_mut();
|
||||||
@@ -1593,7 +1601,7 @@ pub unsafe extern "C" fn ghostty_surface_text(
|
|||||||
// SAFETY: `text` points to `len` readable bytes.
|
// SAFETY: `text` points to `len` readable bytes.
|
||||||
unsafe { std::slice::from_raw_parts(text.cast::<u8>(), len) }
|
unsafe { std::slice::from_raw_parts(text.cast::<u8>(), len) }
|
||||||
};
|
};
|
||||||
if let Err(err) = core.text_callback(&String::from_utf8_lossy(bytes)) {
|
if let Err(err) = core.text_callback_bytes(bytes) {
|
||||||
log::error!("error in key callback err={err}");
|
log::error!("error in key callback err={err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2155,3 +2163,61 @@ mod darwin {
|
|||||||
fill_text(core, t, sel, result)
|
fill_text(core, t, sel, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::types::ghostty_env_var_s;
|
||||||
|
|
||||||
|
/// Surface options are raw C bytes in Zig; non-UTF-8 values must reach
|
||||||
|
/// the config unchanged (reversible byte escape).
|
||||||
|
#[test]
|
||||||
|
fn surface_options_keep_non_utf8_bytes() {
|
||||||
|
let td = tempfile::tempdir().unwrap();
|
||||||
|
let mut dir = td.path().as_os_str().as_bytes().to_vec();
|
||||||
|
dir.extend_from_slice(b"/d\xff");
|
||||||
|
std::fs::create_dir(std::ffi::OsStr::from_bytes(&dir)).unwrap();
|
||||||
|
|
||||||
|
let wd = CString::new(dir.clone()).unwrap();
|
||||||
|
let cmd = CString::new(&b"echo \xfe"[..]).unwrap();
|
||||||
|
let key = CString::new(&b"K\xfd"[..]).unwrap();
|
||||||
|
let val = CString::new(&b"v\xfc"[..]).unwrap();
|
||||||
|
let input = CString::new(&b"in\xff\n"[..]).unwrap();
|
||||||
|
let mut env = [ghostty_env_var_s {
|
||||||
|
key: key.as_ptr(),
|
||||||
|
value: val.as_ptr(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
// SAFETY: all-zero is a valid value (null pointers, 0 enums).
|
||||||
|
let mut opts: ghostty_surface_config_s = unsafe { std::mem::zeroed() };
|
||||||
|
opts.working_directory = wd.as_ptr();
|
||||||
|
opts.command = cmd.as_ptr();
|
||||||
|
opts.env_vars = env.as_mut_ptr();
|
||||||
|
opts.env_var_count = 1;
|
||||||
|
opts.initial_input = input.as_ptr();
|
||||||
|
|
||||||
|
let mut config = Config::default();
|
||||||
|
// SAFETY: the pointers above are valid NUL terminated strings.
|
||||||
|
unsafe { apply_surface_options(&mut config, &opts) };
|
||||||
|
|
||||||
|
let got_wd = config
|
||||||
|
.working_directory
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|w| w.value())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(osbytes::config_str_to_bytes(got_wd), dir);
|
||||||
|
let Some(Command::Shell(c)) = &config.command else {
|
||||||
|
panic!("no command");
|
||||||
|
};
|
||||||
|
assert_eq!(osbytes::config_str_to_bytes(c), b"echo \xfe");
|
||||||
|
let (k, v) = config.env.iter().next().unwrap();
|
||||||
|
assert_eq!(osbytes::config_str_to_bytes(k), b"K\xfd");
|
||||||
|
assert_eq!(osbytes::config_str_to_bytes(v), b"v\xfc");
|
||||||
|
assert!(matches!(
|
||||||
|
config.input.list.first(),
|
||||||
|
Some(ReadableIO::Raw(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ffi::{CStr, CString, c_char, c_int},
|
ffi::{CStr, CString, OsString, c_char, c_int},
|
||||||
sync::{Mutex, OnceLock},
|
sync::{Mutex, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ghostty_cli::build_info::{self, BuildInfo};
|
use ghostty_cli::build_info::{self, BuildInfo};
|
||||||
use ghostty_core::global::{self, InitOptions};
|
use ghostty_core::global::{self, InitOptions};
|
||||||
|
use ghostty_os::osbytes;
|
||||||
|
|
||||||
use crate::types::{build_mode, ghostty_info_s, ghostty_string_s};
|
use crate::types::{build_mode, ghostty_info_s, ghostty_string_s};
|
||||||
|
|
||||||
@@ -77,7 +78,8 @@ pub(crate) fn alloc_c_text(bytes: &[u8]) -> *const c_char {
|
|||||||
/// `argv` must be null or point to `argc` valid NUL terminated strings.
|
/// `argv` must be null or point to `argc` valid NUL terminated strings.
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn ghostty_init(argc: usize, argv: *mut *mut c_char) -> c_int {
|
pub unsafe extern "C" fn ghostty_init(argc: usize, argv: *mut *mut c_char) -> c_int {
|
||||||
let mut args = Vec::with_capacity(argc);
|
// Raw argument bytes (Zig: `std.os.argv = argv[0..argc]`).
|
||||||
|
let mut raw: Vec<OsString> = Vec::with_capacity(argc);
|
||||||
if !argv.is_null() {
|
if !argv.is_null() {
|
||||||
for i in 0..argc {
|
for i in 0..argc {
|
||||||
// SAFETY: the caller guarantees `argv[0..argc]` are valid.
|
// SAFETY: the caller guarantees `argv[0..argc]` are valid.
|
||||||
@@ -87,10 +89,18 @@ pub unsafe extern "C" fn ghostty_init(argc: usize, argv: *mut *mut c_char) -> c_
|
|||||||
}
|
}
|
||||||
// SAFETY: each argv entry is a NUL terminated string.
|
// SAFETY: each argv entry is a NUL terminated string.
|
||||||
let c = unsafe { CStr::from_ptr(p) };
|
let c = unsafe { CStr::from_ptr(p) };
|
||||||
args.push(c.to_string_lossy().into_owned());
|
raw.push(osbytes::os_from_bytes(c.to_bytes().to_vec()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Later config CLI loading (`ghostty_config_load_cli_args`) must see
|
||||||
|
// these arguments rather than the process arguments.
|
||||||
|
ghostty_os::args::set_argv(raw.clone());
|
||||||
|
let args: Vec<String> = raw
|
||||||
|
.iter()
|
||||||
|
.map(|a| osbytes::os_to_config_string(a))
|
||||||
|
.collect();
|
||||||
|
|
||||||
// We first try to parse any action that we may be executing.
|
// We first try to parse any action that we may be executing.
|
||||||
let action = match ghostty_cli::ghostty::detect(&args) {
|
let action = match ghostty_cli::ghostty::detect(&args) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
@@ -101,16 +111,48 @@ pub unsafe extern "C" fn ghostty_init(argc: usize, argv: *mut *mut c_char) -> c_
|
|||||||
};
|
};
|
||||||
let _ = ARGS.set(args);
|
let _ = ARGS.set(args);
|
||||||
|
|
||||||
|
// `+version` and friends report the library build.
|
||||||
|
build_info::set(lib_build_info());
|
||||||
|
|
||||||
|
// Setup our signal handlers before logging (Zig: `initSignals`). A C or
|
||||||
|
// Swift host does not get the Rust runtime's SIGPIPE ignore.
|
||||||
|
init_signals();
|
||||||
|
|
||||||
global::init_lib(InitOptions {
|
global::init_lib(InitOptions {
|
||||||
action: action.is_some(),
|
action: action.is_some(),
|
||||||
resources_dir: None,
|
resources_dir: None,
|
||||||
|
crash: Some(lib_crash_options()),
|
||||||
});
|
});
|
||||||
|
|
||||||
// `+version` and friends report the library build.
|
|
||||||
build_info::set(lib_build_info());
|
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore SIGPIPE: it is common with how termio shuts down read threads
|
||||||
|
/// (Zig: `GlobalState.initSignals`; not on Windows).
|
||||||
|
fn init_signals() {
|
||||||
|
#[cfg(unix)]
|
||||||
|
// SAFETY: installing SIG_IGN for SIGPIPE has no memory safety impact.
|
||||||
|
unsafe {
|
||||||
|
libc::signal(libc::SIGPIPE, libc::SIG_IGN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crash metadata for the library artifact: no app runtime, CoreText +
|
||||||
|
/// Metal on Apple targets (so the GTK / fontconfig / OpenGL defaults are
|
||||||
|
/// not reported for the macOS app).
|
||||||
|
fn lib_crash_options() -> ghostty_crash::Options {
|
||||||
|
let info = build_info::get();
|
||||||
|
let tag = |s: &str| s.trim_start_matches('.').to_owned();
|
||||||
|
let mut o = global::default_crash_options();
|
||||||
|
o.release.clone_from(&info.version_string);
|
||||||
|
o.app_runtime = tag(&lib_build_info().app_runtime);
|
||||||
|
if cfg!(target_vendor = "apple") {
|
||||||
|
"coretext".clone_into(&mut o.font_backend);
|
||||||
|
"metal".clone_into(&mut o.renderer);
|
||||||
|
}
|
||||||
|
o
|
||||||
|
}
|
||||||
|
|
||||||
/// The build info of the library (Zig: `build_config` for the lib artifact:
|
/// The build info of the library (Zig: `build_config` for the lib artifact:
|
||||||
/// no app runtime, CoreText + Metal on Apple targets).
|
/// no app runtime, CoreText + Metal on Apple targets).
|
||||||
fn lib_build_info() -> BuildInfo {
|
fn lib_build_info() -> BuildInfo {
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ fn host_program_passes() {
|
|||||||
"host failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
|
"host failed\nstdout:\n{stdout}\nstderr:\n{stderr}"
|
||||||
);
|
);
|
||||||
assert!(stdout.contains("OK"), "stdout:\n{stdout}");
|
assert!(stdout.contains("OK"), "stdout:\n{stdout}");
|
||||||
assert!(stdout.contains("version=1.3.1"), "stdout:\n{stdout}");
|
assert!(
|
||||||
|
stdout.contains(&format!("version={}", ghostty_build_info::VERSION_STRING)),
|
||||||
|
"stdout:\n{stdout}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -55,7 +58,10 @@ fn cli_try_action_runs_version_and_exits() {
|
|||||||
"stderr: {}",
|
"stderr: {}",
|
||||||
String::from_utf8_lossy(&out.stderr)
|
String::from_utf8_lossy(&out.stderr)
|
||||||
);
|
);
|
||||||
assert!(stdout.starts_with("Ghostty 1.3.1"), "stdout:\n{stdout}");
|
assert!(
|
||||||
|
stdout.starts_with(&format!("Ghostty {}", ghostty_build_info::VERSION_STRING)),
|
||||||
|
"stdout:\n{stdout}"
|
||||||
|
);
|
||||||
// The action ran instead of the rest of the program.
|
// The action ran instead of the rest of the program.
|
||||||
assert!(!stdout.contains("OK"));
|
assert!(!stdout.contains("OK"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//! `ghostty_init(argc, argv)` semantics from `main_c.zig`: the supplied
|
||||||
|
//! argument vector (not the process arguments) feeds the later config CLI
|
||||||
|
//! loading, and SIGPIPE is ignored for C / Swift hosts.
|
||||||
|
#![cfg(unix)]
|
||||||
|
#![allow(
|
||||||
|
unsafe_code,
|
||||||
|
clippy::unwrap_used,
|
||||||
|
clippy::expect_used,
|
||||||
|
clippy::undocumented_unsafe_blocks
|
||||||
|
)]
|
||||||
|
|
||||||
|
use std::ffi::{CString, c_char};
|
||||||
|
|
||||||
|
use ghostty::{config_capi::*, main_c::ghostty_init, types::ghostty_config_t};
|
||||||
|
|
||||||
|
fn get_bool(cfg: ghostty_config_t, key: &str) -> bool {
|
||||||
|
let mut out = false;
|
||||||
|
let ok =
|
||||||
|
unsafe { ghostty_config_get(cfg, (&raw mut out).cast(), key.as_ptr().cast(), key.len()) };
|
||||||
|
assert!(ok);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_argv_feeds_config_cli_and_ignores_sigpipe() {
|
||||||
|
// Start from the default disposition so that the check is meaningful.
|
||||||
|
unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL) };
|
||||||
|
|
||||||
|
// Not the process argv: the test harness argv has no `--maximize`.
|
||||||
|
let owned: Vec<CString> = [&b"ghostty"[..], b"--maximize=true"]
|
||||||
|
.iter()
|
||||||
|
.map(|a| CString::new(*a).unwrap())
|
||||||
|
.collect();
|
||||||
|
let mut argv: Vec<*mut c_char> = owned.iter().map(|c| c.as_ptr().cast_mut()).collect();
|
||||||
|
assert_eq!(unsafe { ghostty_init(argv.len(), argv.as_mut_ptr()) }, 0);
|
||||||
|
|
||||||
|
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
|
||||||
|
unsafe { libc::sigaction(libc::SIGPIPE, std::ptr::null(), &mut sa) };
|
||||||
|
assert_eq!(sa.sa_sigaction, libc::SIG_IGN);
|
||||||
|
|
||||||
|
let cfg = ghostty_config_new();
|
||||||
|
unsafe { ghostty_config_load_cli_args(cfg) };
|
||||||
|
assert!(get_bool(cfg, "maximize"));
|
||||||
|
unsafe { ghostty_config_free(cfg) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_load_file_accepts_non_utf8_path() {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut name = dir.path().as_os_str().as_bytes().to_vec();
|
||||||
|
name.extend_from_slice(b"/cfg\xff");
|
||||||
|
let path = std::ffi::OsStr::from_bytes(&name);
|
||||||
|
std::fs::write(path, "maximize = true\n").unwrap();
|
||||||
|
|
||||||
|
let cfg = ghostty_config_new();
|
||||||
|
let c = CString::new(name).unwrap();
|
||||||
|
unsafe { ghostty_config_load_file(cfg, c.as_ptr()) };
|
||||||
|
assert!(get_bool(cfg, "maximize"));
|
||||||
|
unsafe { ghostty_config_free(cfg) };
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ ghostty-apprt.workspace = true
|
|||||||
ghostty-config.workspace = true
|
ghostty-config.workspace = true
|
||||||
ghostty-crash.workspace = true
|
ghostty-crash.workspace = true
|
||||||
ghostty-datastruct.workspace = true
|
ghostty-datastruct.workspace = true
|
||||||
|
ghostty-build-info.workspace = true
|
||||||
ghostty-os.workspace = true
|
ghostty-os.workspace = true
|
||||||
ghostty-renderer.workspace = true
|
ghostty-renderer.workspace = true
|
||||||
ghostty-terminal.workspace = true
|
ghostty-terminal.workspace = true
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use std::{
|
|||||||
panic::{AssertUnwindSafe, catch_unwind},
|
panic::{AssertUnwindSafe, catch_unwind},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ghostty_os::file::Rlimit;
|
use ghostty_os::{file::Rlimit, osbytes::config_str_to_bytes};
|
||||||
use rustix::{
|
use rustix::{
|
||||||
io::Errno,
|
io::Errno,
|
||||||
process::{self, WaitOptions},
|
process::{self, WaitOptions},
|
||||||
@@ -264,13 +264,13 @@ impl Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&self) -> Result<Prepared, NulError> {
|
fn prepare(&self) -> Result<Prepared, NulError> {
|
||||||
let path = CString::new(self.path.as_str())?;
|
let path = CString::new(config_str_to_bytes(&self.path))?;
|
||||||
let argv = if self.args.is_empty() {
|
let argv = if self.args.is_empty() {
|
||||||
vec![path.clone()]
|
vec![path.clone()]
|
||||||
} else {
|
} else {
|
||||||
self.args
|
self.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| CString::new(a.as_str()))
|
.map(|a| CString::new(config_str_to_bytes(a)))
|
||||||
.collect::<Result<_, _>>()?
|
.collect::<Result<_, _>>()?
|
||||||
};
|
};
|
||||||
let envp = match &self.env {
|
let envp = match &self.env {
|
||||||
@@ -297,7 +297,7 @@ impl Command {
|
|||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
let candidates = exec_candidates(&self.path, std::env::var("PATH").ok().as_deref())
|
let candidates = exec_candidates(&self.path, std::env::var("PATH").ok().as_deref())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(CString::new)
|
.map(|c| CString::new(config_str_to_bytes(&c)))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
Ok(Prepared {
|
Ok(Prepared {
|
||||||
path,
|
path,
|
||||||
@@ -315,7 +315,10 @@ impl Command {
|
|||||||
pub fn start(&mut self) -> Result<(), StartError> {
|
pub fn start(&mut self) -> Result<(), StartError> {
|
||||||
// IMPORTANT: do all allocation prior to the fork().
|
// IMPORTANT: do all allocation prior to the fork().
|
||||||
let prep = self.prepare()?;
|
let prep = self.prepare()?;
|
||||||
let cwd = self.cwd.as_deref().and_then(|c| CString::new(c).ok());
|
let cwd = self
|
||||||
|
.cwd
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|c| CString::new(config_str_to_bytes(c)).ok());
|
||||||
|
|
||||||
let mut argv_ptrs: Vec<*const c_char> = prep.argv.iter().map(|c| c.as_ptr()).collect();
|
let mut argv_ptrs: Vec<*const c_char> = prep.argv.iter().map(|c| c.as_ptr()).collect();
|
||||||
argv_ptrs.push(std::ptr::null());
|
argv_ptrs.push(std::ptr::null());
|
||||||
@@ -876,6 +879,35 @@ mod tests {
|
|||||||
assert_eq!("/tmp\n", read_all(stdout));
|
assert_eq!("/tmp\n", read_all(stdout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_utf8_cwd_and_args_are_preserved() {
|
||||||
|
use ghostty_os::osbytes::{bytes_to_config_string, os_from_bytes};
|
||||||
|
let td = TempDir::init().unwrap();
|
||||||
|
let stdout = test_file(&td, "stdout.txt");
|
||||||
|
|
||||||
|
// A directory whose name is not valid UTF-8, handed over with the
|
||||||
|
// reversible byte escape used by the String based layers.
|
||||||
|
let mut raw = ghostty_os::osbytes::os_bytes(td.path().as_os_str());
|
||||||
|
raw.extend_from_slice(b"/d\xff");
|
||||||
|
std::fs::create_dir(os_from_bytes(raw.clone())).unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::new("/bin/sh", &["/bin/sh", "-c", "pwd -P; printf %s \"$0\""]);
|
||||||
|
cmd.args.push(bytes_to_config_string(b"a\xffb"));
|
||||||
|
cmd.stdout = Some(stdout.try_clone().unwrap().into());
|
||||||
|
cmd.cwd = Some(bytes_to_config_string(&raw));
|
||||||
|
cmd.start().unwrap();
|
||||||
|
assert_eq!(cmd.wait(true).unwrap(), Exit::Exited(0));
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
std::io::Read::read_to_end(
|
||||||
|
&mut std::fs::File::open(td.path().join("stdout.txt")).unwrap(),
|
||||||
|
&mut bytes,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(bytes.windows(2).any(|w| w == b"d\xff"), "{bytes:?}");
|
||||||
|
assert!(bytes.ends_with(b"a\xffb"), "{bytes:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_cwd_is_ignored() {
|
fn invalid_cwd_is_ignored() {
|
||||||
let td = TempDir::init().unwrap();
|
let td = TempDir::init().unwrap();
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ impl Exec {
|
|||||||
// set that on the terminal now. This allows rapidly initializing
|
// set that on the terminal now. This allows rapidly initializing
|
||||||
// new surfaces to use the proper pwd.
|
// new surfaces to use the proper pwd.
|
||||||
if let Some(cwd) = &self.subprocess.cwd
|
if let Some(cwd) = &self.subprocess.cwd
|
||||||
&& let Err(err) = term.set_pwd(cwd.as_bytes())
|
&& let Err(err) = term.set_pwd(&ghostty_os::osbytes::config_str_to_bytes(cwd))
|
||||||
{
|
{
|
||||||
log::warn!("error setting initial pwd err={err:?}");
|
log::warn!("error setting initial pwd err={err:?}");
|
||||||
}
|
}
|
||||||
@@ -907,7 +907,10 @@ impl Subprocess {
|
|||||||
|
|
||||||
// Add the environment variables that override any others.
|
// Add the environment variables that override any others.
|
||||||
for (k, v) in cfg.env_override.iter() {
|
for (k, v) in cfg.env_override.iter() {
|
||||||
env.insert(k, v);
|
env.insert(
|
||||||
|
ghostty_os::osbytes::config_str_to_os(k),
|
||||||
|
ghostty_os::osbytes::config_str_to_os(v),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build our args list
|
// Build our args list
|
||||||
@@ -925,7 +928,7 @@ impl Subprocess {
|
|||||||
// https://github.com/kovidgoyal/kitty/issues/1595
|
// https://github.com/kovidgoyal/kitty/issues/1595
|
||||||
// https://github.com/ghostty-org/ghostty/discussions/7769
|
// https://github.com/ghostty-org/ghostty/discussions/7769
|
||||||
if let Some(pwd) = &cwd {
|
if let Some(pwd) = &cwd {
|
||||||
env.insert("PWD", pwd.clone());
|
env.insert("PWD", ghostty_os::osbytes::config_str_to_os(pwd));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Subprocess {
|
Ok(Subprocess {
|
||||||
@@ -1037,7 +1040,7 @@ impl Subprocess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(proposed) => {
|
Some(proposed) => {
|
||||||
if std::path::Path::new(proposed).exists() {
|
if std::path::Path::new(&ghostty_os::osbytes::config_str_to_os(proposed)).exists() {
|
||||||
Some(proposed.clone())
|
Some(proposed.clone())
|
||||||
} else {
|
} else {
|
||||||
log::warn!("cannot access cwd, ignoring: {proposed}");
|
log::warn!("cannot access cwd, ignoring: {proposed}");
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// `build_config.version_string`.
|
/// `build_config.version_string`.
|
||||||
pub const VERSION_STRING: &str = "1.3.1";
|
pub const VERSION_STRING: &str = ghostty_build_info::VERSION_STRING;
|
||||||
|
|
||||||
/// The long-lived state of the handler (everything but the terminal).
|
/// The long-lived state of the handler (everything but the terminal).
|
||||||
pub struct HandlerState {
|
pub struct HandlerState {
|
||||||
@@ -1693,7 +1693,11 @@ mod tests {
|
|||||||
fn xtversion() {
|
fn xtversion() {
|
||||||
let mut h = Harness::new();
|
let mut h = Harness::new();
|
||||||
h.feed(b"\x1b[>q");
|
h.feed(b"\x1b[>q");
|
||||||
assert_eq!(b"\x1bP>|ghostty 1.3.1\x1b\\".as_slice(), h.written());
|
let want = format!(
|
||||||
|
"\x1bP>|ghostty {}\x1b\\",
|
||||||
|
ghostty_build_info::VERSION_STRING
|
||||||
|
);
|
||||||
|
assert_eq!(want.as_bytes(), h.written());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ impl ThreadEnterState {
|
|||||||
let mut out = Vec::with_capacity(self.input.len());
|
let mut out = Vec::with_capacity(self.input.len());
|
||||||
for item in &self.input {
|
for item in &self.input {
|
||||||
match item {
|
match item {
|
||||||
ReadableIO::Raw(v) => out.push(v.clone().into_bytes()),
|
ReadableIO::Raw(v) => out.push(ghostty_os::osbytes::config_str_to_bytes(v)),
|
||||||
ReadableIO::Path(path) => {
|
ReadableIO::Path(path) => {
|
||||||
let f = std::fs::File::open(path).map_err(|err| {
|
let f = std::fs::File::open(path).map_err(|err| {
|
||||||
log::warn!("failed to open input file={path} err={err}");
|
log::warn!("failed to open input file={path} err={err}");
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Z2 final notes (core / lib / termio / apprt-gtk)
|
||||||
|
|
||||||
|
- Windows: `cargo check --target x86_64-pc-windows-gnu -p ghostty-core --tests` passes. Surface init gates `rt_pre_exec_info`, `rt_post_fork_info`, `create_scope` and `restore_rlimit` on `cfg(unix)`. Screen-file writing only sets mode 0600 on unix. The test that read the file mode is unix-gated.
|
||||||
|
- Embedded init: `global::init` calls `ensure_locale`. `InitOptions.crash` carries crash metadata, with `default_crash_options()` (shared version; Metal/CoreText/`none` tags on Apple). `ghostty_init` ignores SIGPIPE and passes crash options for the lib build. It also stores the raw argv with `ghostty_os::args::set_argv`, so `ghostty_config_load_cli_args` uses it (I added `set_argv` and a vec-returning `iterator()` in ghostty-os/args.rs). `ghostty_config_load_file` keeps non-UTF-8 path bytes (`file_load::open` decodes them).
|
||||||
|
- Bytes: `Pwd.pwd` and `Surface::pwd()` carry OSC 7 bytes with the reversible `osbytes` escape. `pwd_bytes()` returns the raw bytes. Sinks decode: C `Action::Pwd`, inherited working dir, termio cwd/args/env/PWD/`Raw` input. Embedded surface working-directory/command/env/initial_input and `ghostty_surface_text` (`text_callback_bytes`) pass raw bytes. GTK file drop uses `paste_bytes` and a new `CoreSurface::complete_paste_bytes`.
|
||||||
|
- Version: XTVERSION, `TERM_PROGRAM_VERSION`, both GTK About branches and crash metadata use `ghostty_build_info::VERSION_STRING`.
|
||||||
|
- Tests added: e2e OSC 7 non-UTF-8, `command.rs` non-UTF-8 cwd/args, `embedded` surface-options bytes, `tests/init_argv.rs` (argv, SIGPIPE, non-UTF-8 load_file), crash defaults.
|
||||||
|
- Gate: workspace build, tests and clippy (`--no-deps -D warnings`) for my crates pass, and `cargo +nightly fmt` is applied. Not done: macOS cross clippy (the wrapper setup was declined at the tool prompt). No GTK drop UI test (needs a display). The paste confirmation dialog is text-based, so it carries non-UTF-8 bytes via the escape.
|
||||||
+6
-4
@@ -20,16 +20,18 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn real_main() -> ExitCode {
|
fn real_main() -> ExitCode {
|
||||||
// Byte-safe argv (`std::env::args()` panics on non-UTF-8 arguments).
|
// Raw argv bytes (Zig `[]const u8`); `std::env::args()` would panic on
|
||||||
let args: Vec<String> = ghostty_os::args::collect_lossy();
|
// non-UTF-8 arguments and a lossy conversion would change them.
|
||||||
|
let args: Vec<std::ffi::OsString> = ghostty_os::args::collect_os();
|
||||||
|
|
||||||
// We first start by initializing our global state. This sets up
|
// We first start by initializing our global state. This sets up
|
||||||
// process-level state we need to run the terminal. We detect whether a
|
// process-level state we need to run the terminal. We detect whether a
|
||||||
// CLI action was requested so logging can be silenced for it.
|
// CLI action was requested so logging can be silenced for it.
|
||||||
let action_requested = !matches!(ghostty_cli::ghostty::detect(&args), Ok(None));
|
let action_requested = !matches!(ghostty_cli::ghostty::detect_os(&args), Ok(None));
|
||||||
global::init(InitOptions {
|
global::init(InitOptions {
|
||||||
action: action_requested,
|
action: action_requested,
|
||||||
resources_dir: Some(ghostty_apprt_gtk::flatpak::resources_dir),
|
resources_dir: Some(ghostty_apprt_gtk::flatpak::resources_dir),
|
||||||
|
..InitOptions::default()
|
||||||
});
|
});
|
||||||
// Zig `global.init` runs `ensureLocale` before anything locale dependent.
|
// Zig `global.init` runs `ensureLocale` before anything locale dependent.
|
||||||
ghostty_apprt_gtk::class::application::init_locale();
|
ghostty_apprt_gtk::class::application::init_locale();
|
||||||
@@ -38,7 +40,7 @@ fn real_main() -> ExitCode {
|
|||||||
// Execute our action if we have one.
|
// Execute our action if we have one.
|
||||||
if action_requested {
|
if action_requested {
|
||||||
ghostty_cli::new_window::set_ipc_handler(new_window_ipc);
|
ghostty_cli::new_window::set_ipc_handler(new_window_ipc);
|
||||||
if let Some(code) = ghostty_cli::run_from_args(&args) {
|
if let Some(code) = ghostty_cli::ghostty::run_from_os_args(&args) {
|
||||||
return ExitCode::from(code);
|
return ExitCode::from(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user