diff --git a/.gitattributes b/.gitattributes --- a/.gitattributes +++ b/.gitattributes @@ -12,3 +12,4 @@ src/font/nerd_font_codepoint_tables.py linguist-generated=true src/font/res/** linguist-vendored src/terminal/res/** linguist-vendored +src/terminal/res/rgb.txt -text diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ zig-cache/ .zig-cache/ zig-out/ +build-cmake/ +CMakeCache.txt +CMakeFiles/ /build.zig.zon.bak /result* /.nixos-test-history @@ -25,3 +28,4 @@ /ghostty.qcow2 vgcore.* + diff --git a/.prettierignore b/.prettierignore --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,9 @@ # macos is managed by XCode GUI macos/ +# Xcode asset catalogs +**/*.xcassets/ + # produced by Icon Composer on macOS images/Ghostty.icon/icon.json diff --git a/AGENTS.md b/AGENTS.md --- a/AGENTS.md +++ b/AGENTS.md @@ -5,16 +5,20 @@ ## Commands - **Build:** `zig build` + - If you're on macOS and don't need to build the macOS app, use + `-Demit-macos-app=false` to skip building the app bundle and speed up + compilation. - **Test (Zig):** `zig build test` + - Prefer to run targeted tests with `-Dtest-filter` because the full + test suite is slow to run. - **Test filter (Zig)**: `zig build test -Dtest-filter=` - **Formatting (Zig)**: `zig fmt .` -- **Formatting (Swift)**: `swiftlint lint --fix` +- **Formatting (Swift)**: `swiftlint lint --strict --fix` - **Formatting (other)**: `prettier -w .` ## Directory Structure - Shared Zig core: `src/` -- C API: `include` - macOS app: `macos/` - GTK (Linux and FreeBSD) app: `src/apprt/gtk` diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,230 @@ +# CMake wrapper for libghostty-vt +# +# This file delegates to `zig build -Demit-lib-vt` to produce the shared library, +# headers, and pkg-config file. It exists so that CMake-based projects can +# consume libghostty-vt without interacting with the Zig build system +# directly. However, downstream users do still require `zig` on the PATH. +# Please consult the Ghostty docs for the required Zig version: +# +# https://ghostty.org/docs/install/build +# +# Building within the Ghostty repo +# --------------------------------- +# +# cmake -B build +# cmake --build build +# cmake --install build --prefix /usr/local +# +# Pass extra flags to the Zig build with GHOSTTY_ZIG_BUILD_FLAGS: +# +# cmake -B build -DGHOSTTY_ZIG_BUILD_FLAGS="-Demit-macos-app=false" +# +# Integrating into a downstream CMake project +# --------------------------------------------- +# +# Option 1 — FetchContent (recommended, no manual install step): +# +# include(FetchContent) +# FetchContent_Declare(ghostty +# GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git +# GIT_TAG main +# ) +# FetchContent_MakeAvailable(ghostty) +# +# target_link_libraries(myapp PRIVATE ghostty-vt) # shared +# target_link_libraries(myapp PRIVATE ghostty-vt-static) # static +# +# To use a local checkout instead of fetching: +# +# cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=/path/to/ghostty +# +# Option 2 — find_package (after installing to a prefix): +# +# find_package(ghostty-vt REQUIRED) +# target_link_libraries(myapp PRIVATE ghostty-vt::ghostty-vt) # shared +# target_link_libraries(myapp PRIVATE ghostty-vt::ghostty-vt-static) # static +# +# See dist/cmake/README.md for more details and example/c-vt-cmake/ for a +# complete working example. + +cmake_minimum_required(VERSION 3.19) +project(ghostty-vt VERSION 0.1.0 LANGUAGES C) + +# --- Options ---------------------------------------------------------------- + +set(GHOSTTY_ZIG_BUILD_FLAGS "" CACHE STRING "Additional flags to pass to zig build") + +# Map CMake build types to Zig optimization levels. +if(CMAKE_BUILD_TYPE) + string(TOUPPER "${CMAKE_BUILD_TYPE}" _bt) + if(_bt STREQUAL "RELEASE" OR _bt STREQUAL "MINSIZEREL" OR _bt STREQUAL "RELWITHDEBINFO") + list(APPEND GHOSTTY_ZIG_BUILD_FLAGS "-Doptimize=ReleaseFast") + endif() + unset(_bt) +endif() + +# --- Find Zig ---------------------------------------------------------------- + +find_program(ZIG_EXECUTABLE zig REQUIRED) +message(STATUS "Found zig: ${ZIG_EXECUTABLE}") + +# --- Build via zig build ----------------------------------------------------- + +# The zig build installs into zig-out/ relative to the source tree. +set(ZIG_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zig-out") + +# Shared library names (zig build produces both shared and static). +if(APPLE) + set(GHOSTTY_VT_LIBNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(GHOSTTY_VT_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt.0${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(GHOSTTY_VT_REALNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt.0.1.0${CMAKE_SHARED_LIBRARY_SUFFIX}") +elseif(WIN32) + set(GHOSTTY_VT_LIBNAME "ghostty-vt.dll") + set(GHOSTTY_VT_REALNAME "ghostty-vt.dll") + set(GHOSTTY_VT_IMPLIB "ghostty-vt.lib") +else() + set(GHOSTTY_VT_LIBNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(GHOSTTY_VT_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}.0") + set(GHOSTTY_VT_REALNAME "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}.0.1.0") +endif() + +if(WIN32) + set(GHOSTTY_VT_SHARED_LIBRARY "${ZIG_OUT_DIR}/bin/${GHOSTTY_VT_REALNAME}") +else() + set(GHOSTTY_VT_SHARED_LIBRARY "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_REALNAME}") +endif() + +# Static library name. +# On Windows, the static lib is named "ghostty-vt-static.lib" to avoid +# colliding with the DLL import library "ghostty-vt.lib". +if(WIN32) + set(GHOSTTY_VT_STATIC_REALNAME "ghostty-vt-static.lib") +else() + set(GHOSTTY_VT_STATIC_REALNAME "libghostty-vt.a") +endif() +set(GHOSTTY_VT_STATIC_LIBRARY "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_STATIC_REALNAME}") + +# Ensure the output directories exist so CMake doesn't reject the +# INTERFACE_INCLUDE_DIRECTORIES before the zig build has run. +file(MAKE_DIRECTORY "${ZIG_OUT_DIR}/include") + +# Custom command: run zig build -Demit-lib-vt (produces both shared and static) +add_custom_command( + OUTPUT "${GHOSTTY_VT_SHARED_LIBRARY}" "${GHOSTTY_VT_STATIC_LIBRARY}" "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" + COMMAND "${ZIG_EXECUTABLE}" build -Demit-lib-vt ${GHOSTTY_ZIG_BUILD_FLAGS} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Building libghostty-vt via zig build..." + USES_TERMINAL +) + +add_custom_target(zig_build_lib_vt ALL + DEPENDS "${GHOSTTY_VT_SHARED_LIBRARY}" "${GHOSTTY_VT_STATIC_LIBRARY}" +) + +# Tell CMake's clean target to also remove Zig's output directory. +set_property(DIRECTORY APPEND PROPERTY + ADDITIONAL_CLEAN_FILES "${ZIG_OUT_DIR}" +) + +# --- IMPORTED library targets ------------------------------------------------ + +# Shared +add_library(ghostty-vt SHARED IMPORTED GLOBAL) +set_target_properties(ghostty-vt PROPERTIES + IMPORTED_LOCATION "${GHOSTTY_VT_SHARED_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ZIG_OUT_DIR}/include" +) +if(APPLE) + set_target_properties(ghostty-vt PROPERTIES + IMPORTED_SONAME "@rpath/${GHOSTTY_VT_SONAME}" + ) +elseif(WIN32) + set_target_properties(ghostty-vt PROPERTIES + IMPORTED_IMPLIB "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" + ) +else() + set_target_properties(ghostty-vt PROPERTIES + IMPORTED_SONAME "${GHOSTTY_VT_SONAME}" + ) +endif() +add_dependencies(ghostty-vt zig_build_lib_vt) + +# Static +# +# When linking the static library, consumers must also link its transitive +# dependencies. By default (with SIMD enabled), these are: +# - libc +# - libc++ (or libstdc++ on Linux) +# - highway +# - simdutf +# +# Building with -Dsimd=false removes the C++ / highway / simdutf +# dependencies, leaving only libc. +add_library(ghostty-vt-static STATIC IMPORTED GLOBAL) +set_target_properties(ghostty-vt-static PROPERTIES + IMPORTED_LOCATION "${GHOSTTY_VT_STATIC_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ZIG_OUT_DIR}/include" +) +if(WIN32) + # On Windows, the Zig standard library uses NT API functions + # (NtClose, NtCreateSection, etc.) and kernel32 functions that + # consumers must link when using the static library. + set_target_properties(ghostty-vt-static PROPERTIES + INTERFACE_LINK_LIBRARIES "ntdll;kernel32" + ) +endif() +add_dependencies(ghostty-vt-static zig_build_lib_vt) + +# --- Install ------------------------------------------------------------------ + +include(GNUInstallDirs) + +# Install shared library +if(WIN32) + # On Windows, install the DLL and PDB to bin/ and the import library to lib/ + install(FILES "${GHOSTTY_VT_SHARED_LIBRARY}" "${ZIG_OUT_DIR}/bin/ghostty-vt.pdb" TYPE BIN) + install(FILES "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" TYPE LIB) +else() + install(FILES "${GHOSTTY_VT_SHARED_LIBRARY}" TYPE LIB) + # Install symlinks + install(CODE " + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink + \"${GHOSTTY_VT_REALNAME}\" + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${GHOSTTY_VT_SONAME}\") + execute_process(COMMAND \${CMAKE_COMMAND} -E create_symlink + \"${GHOSTTY_VT_SONAME}\" + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${GHOSTTY_VT_LIBNAME}\") + ") +endif() + +# Install static library +install(FILES "${GHOSTTY_VT_STATIC_LIBRARY}" TYPE LIB) + +# Install headers +install(DIRECTORY "${ZIG_OUT_DIR}/include/ghostty" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + +# --- CMake package config for find_package() ---------------------------------- + +include(CMakePackageConfigHelpers) + +# Generate the config file +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/dist/cmake/ghostty-vt-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/ghostty-vt-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ghostty-vt" +) + +# Generate the version file +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ghostty-vt-config-version.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY SameMajorVersion +) + +# Install the config files +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/ghostty-vt-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/ghostty-vt-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ghostty-vt" +) diff --git a/CODEOWNERS b/CODEOWNERS --- a/CODEOWNERS +++ b/CODEOWNERS @@ -169,6 +169,7 @@ /po/de.po @ghostty-org/de_DE /po/es_AR.po @ghostty-org/es_AR /po/es_BO.po @ghostty-org/es_BO +/po/es_ES.po @ghostty-org/es_ES /po/fr.po @ghostty-org/fr_FR /po/ga.po @ghostty-org/ga_IE /po/he.po @ghostty-org/he_IL @@ -189,6 +190,7 @@ /po/ru.po @ghostty-org/ru_RU /po/tr.po @ghostty-org/tr_TR /po/uk.po @ghostty-org/uk_UA +/po/vi.po @ghostty-org/vi_VN /po/zh_CN.po @ghostty-org/zh_CN /po/zh_TW.po @ghostty-org/zh_TW diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@

Fast, native, feature-rich terminal emulator pushing modern features.
+ A native GUI or embeddable library via libghostty. +
About · Download @@ -26,20 +28,13 @@ emulators available, they all force you to choose between speed, features, or native UIs. Ghostty provides all three. -In all categories, I am not trying to claim that Ghostty is the -best (i.e. the fastest, most feature-rich, or most native). But -Ghostty is competitive in all three categories and Ghostty -doesn't make you choose between them. - -Ghostty also intends to push the boundaries of what is possible with a -terminal emulator by exposing modern, opt-in features that enable CLI tool -developers to build more feature rich, interactive applications. - -While aiming for this ambitious goal, our first step is to make Ghostty -one of the best fully standards compliant terminal emulator, remaining -compatible with all existing shells and software while supporting all of -the latest terminal innovations in the ecosystem. You can use Ghostty -as a drop-in replacement for your existing terminal emulator. +**`libghostty`** is a cross-platform, zero-dependency C and Zig library +for building terminal emulators or utilizing terminal functionality +(such as style parsing). Anyone can use `libghostty` to build a terminal +emulator or embed a terminal into their own applications. See +[Ghostling](https://github.com/ghostty-org/ghostling) for a minimal complete project +example or the [`examples` directory](https://github.com/ghostty-org/ghostty/tree/main/example) +for smaller examples of using `libghostty` in C and Zig. For more details, see [About Ghostty](https://ghostty.org/docs/about). @@ -61,30 +56,37 @@ ## Roadmap and Status +Ghostty is stable and in use by millions of people and machines daily. + The high-level ambitious plan for the project, in order: -| # | Step | Status | -| :-: | --------------------------------------------------------- | :----: | -| 1 | Standards-compliant terminal emulation | ✅ | -| 2 | Competitive performance | ✅ | -| 3 | Basic customizability -- fonts, bg colors, etc. | ✅ | -| 4 | Richer windowing features -- multi-window, tabbing, panes | ✅ | -| 5 | Native Platform Experiences (i.e. Mac Preference Panel) | ⚠️ | -| 6 | Cross-platform `libghostty` for Embeddable Terminals | ⚠️ | -| 7 | Windows Terminals (including PowerShell, Cmd, WSL) | ❌ | -| N | Fancy features (to be expanded upon later) | ❌ | +| # | Step | Status | +| :-: | ------------------------------------------------------- | :----: | +| 1 | Standards-compliant terminal emulation | ✅ | +| 2 | Competitive performance | ✅ | +| 3 | Rich windowing features -- multi-window, tabbing, panes | ✅ | +| 4 | Native Platform Experiences | ✅ | +| 5 | Cross-platform `libghostty` for Embeddable Terminals | ✅ | +| 6 | Ghostty-only Terminal Control Sequences | ❌ | Additional details for each step in the big roadmap below: #### Standards-Compliant Terminal Emulation -Ghostty implements enough control sequences to be used by hundreds of -testers daily for over the past year. Further, we've done a -[comprehensive xterm audit](https://github.com/ghostty-org/ghostty/issues/632) +Ghostty implements all of the regularly used control sequences and +can run every mainstream terminal program without issue. For legacy sequences, +we've done a [comprehensive xterm audit](https://github.com/ghostty-org/ghostty/issues/632) comparing Ghostty's behavior to xterm and building a set of conformance test cases. -We believe Ghostty is one of the most compliant terminal emulators available. +In addition to legacy sequences (what you'd call real "terminal" emulation), +Ghostty also supports more modern sequences than almost any other terminal +emulator. These features include things like the Kitty graphics protocol, +Kitty image protocol, clipboard sequences, synchronized rendering, +light/dark mode notifications, and many, many more. + +We believe Ghostty is one of the most compliant and feature-rich terminal +emulators available. Terminal behavior is partially a de jure standard (i.e. [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/)) @@ -96,33 +98,30 @@ #### Competitive Performance -We need better benchmarks to continuously verify this, but Ghostty is -generally in the same performance category as the other highest performing -terminal emulators. +Ghostty is generally in the same performance category as the other highest +performing terminal emulators. -For rendering, we have a multi-renderer architecture that uses OpenGL on -Linux and Metal on macOS. As far as I'm aware, we're the only terminal -emulator other than iTerm that uses Metal directly. And we're the only -terminal emulator that has a Metal renderer that supports ligatures (iTerm -uses a CPU renderer if ligatures are enabled). We can maintain around 60fps -under heavy load and much more generally -- though the terminal is -usually rendering much lower due to little screen changes. +"The same performance category" means that Ghostty is much faster than +traditional or "slow" terminals and is within an unnoticeable margin of the +well-known "fast" terminals. For example, Ghostty and Alacritty are usually within +a few percentage points of each other on various benchmarks, but are both +something like 100x faster than Terminal.app and iTerm. However, Ghostty +is much more feature rich than Alacritty and has a much more native app +experience. -For IO, we have a dedicated IO thread that maintains very little jitter -under heavy IO load (i.e. `cat .txt`). On benchmarks for IO, -we're usually within a small margin of other fast terminal emulators. -For example, reading a dump of plain text is 4x faster compared to iTerm and -Kitty, and 2x faster than Terminal.app. Alacritty is very fast but we're still -around the same speed (give or take) and our app experience is much more -feature rich. +This performance is achieved through high-level architectural decisions and +low-level optimizations. At a high-level, Ghostty has a multi-threaded +architecture with a dedicated read thread, write thread, and render thread +per terminal. Our renderer uses OpenGL on Linux and Metal on macOS. +Our read thread has a heavily optimized terminal parser that leverages +CPU-specific SIMD instructions. Etc. -> [!NOTE] -> Despite being _very fast_, there is a lot of room for improvement here. - -#### Richer Windowing Features +#### Rich Windowing Features The Mac and Linux (build with GTK) apps support multi-window, tabbing, and -splits. +splits with additional features such as tab renaming, coloring, etc. These +features allow for a higher degree of organization and customization than +single-window terminals. #### Native Platform Experiences @@ -133,10 +132,15 @@ - The macOS app is a true SwiftUI-based application with all the things you would expect such as real windowing, menu bars, a settings GUI, etc. - macOS uses a true Metal renderer with CoreText for font discovery. +- macOS supports AppleScript, Apple Shortcuts (AppIntents), etc. - The Linux app is built with GTK. +- The Linux app integrates deeply with systemd if available for things + like always-on, new windows in a single instance, cgroup isolation, etc. -There are more improvements to be made. The macOS settings window is still -a work-in-progress. Similar improvements will follow with Linux. +Our goal with Ghostty is for users of whatever platform they run Ghostty +on to think that Ghostty was built for their platform first and maybe even +exclusively. We want Ghostty to feel like a native app on every platform, +for the best definition of "native" on each platform. #### Cross-platform `libghostty` for Embeddable Terminals @@ -151,15 +155,34 @@ [blog post](https://mitchellh.com/writing/libghostty-is-coming). `libghostty-vt` is already available and usable today for Zig and C and -is compatible for macOS, Linux, Windows, and WebAssembly. At the time of -writing this, the API isn't stable yet and we haven't tagged an official -release, but the core logic is well proven (since Ghostty uses it) and -we're working hard on it now. +is compatible for macOS, Linux, Windows, and WebAssembly. The functionality +is extremely stable (since its been proven in Ghostty GUI for a long time), +but the API signatures are still in flux. -The ultimate goal is not hypothetical! The macOS app is a `libghostty` consumer. -The macOS app is a native Swift app developed in Xcode and `main()` is -within Swift. The Swift app links to `libghostty` and uses the C API to -render terminals. +`libghostty` is already heavily in use. See [`examples`](https://github.com/ghostty-org/ghostty/tree/main/example) +for small examples of using `libghostty` in C and Zig or the +[Ghostling](https://github.com/ghostty-org/ghostling) project for a +complete example. See [awesome-libghostty](https://github.com/Uzaaft/awesome-libghostty) +for a list of projects and resources related to `libghostty`. + +We haven't tagged libghostty with a version yet and we're still working +on a better docs experience, but our [Doxygen website](https://libghostty.tip.ghostty.org/) +is a good resource for the C API. + +#### Ghostty-only Terminal Control Sequences + +We want and believe that terminal applications can and should be able +to do so much more. We've worked hard to support a wide variety of modern +sequences created by other terminal emulators towards this end, but we also +want to fill the gaps by creating our own sequences. + +We've been hesitant to do this up until now because we don't want to create +more fragmentation in the terminal ecosystem by creating sequences that only +work in Ghostty. But, we do want to balance that with the desire to push the +terminal forward with stagnant standards and the slow pace of change in the +terminal ecosystem. + +We haven't done any of this yet. ## Crash Reports diff --git a/build.zig b/build.zig --- a/build.zig +++ b/build.zig @@ -35,7 +35,6 @@ // All our steps which we'll hook up later. The steps are shown // up here just so that they are more self-documenting. - const libvt_step = b.step("lib-vt", "Build libghostty-vt"); const run_step = b.step("run", "Run the app"); const run_valgrind_step = b.step( "run-valgrind", @@ -91,16 +90,6 @@ check_step.dependOn(dist.install_step); } - // libghostty (internal, big) - const libghostty_shared = try buildpkg.GhosttyLib.initShared( - b, - &deps, - ); - const libghostty_static = try buildpkg.GhosttyLib.initStatic( - b, - &deps, - ); - // libghostty-vt const libghostty_vt_shared = shared: { if (config.target.result.cpu.arch.isWasm()) { @@ -115,8 +104,30 @@ &mod, ); }; - libghostty_vt_shared.install(libvt_step); libghostty_vt_shared.install(b.getInstallStep()); + + // libghostty-vt static lib + const libghostty_vt_static = try buildpkg.GhosttyLibVt.initStatic( + b, + &mod, + ); + if (config.is_dep) { + // If we're a dependency, we need to install everything as-is + // so that dep.artifact("ghostty-vt-static") works. + libghostty_vt_static.install(b.getInstallStep()); + } else { + // If we're not a dependency, we rename the static lib to + // be idiomatic. On Windows, we use a distinct name to avoid + // colliding with the DLL import library (ghostty-vt.lib). + const static_lib_name = if (config.target.result.os.tag == .windows) + "ghostty-vt-static.lib" + else + "libghostty-vt.a"; + b.getInstallStep().dependOn(&b.addInstallLibFile( + libghostty_vt_static.output, + static_lib_name, + ).step); + } // Helpgen if (config.emit_helpgen) deps.help_strings.install(); @@ -128,26 +139,34 @@ resources.install(); if (i18n) |v| v.install(); } - } else { - // Libghostty + } else if (!config.emit_lib_vt) { + // The macOS Ghostty Library // - // Note: libghostty is not stable for general purpose use. It is used - // heavily by Ghostty on macOS but it isn't built to be reusable yet. - // As such, these build steps are lacking. For example, the Darwin - // build only produces an xcframework. + // This is NOT libghostty (even though its named that for historical + // reasons). It is just the glue between Ghostty GUI on macOS and + // the full Ghostty GUI core. + const lib_shared = try buildpkg.GhosttyLib.initShared(b, &deps); + const lib_static = try buildpkg.GhosttyLib.initStatic(b, &deps); // We shouldn't have this guard but we don't currently // build on macOS this way ironically so we need to fix that. if (!config.target.result.os.tag.isDarwin()) { - libghostty_shared.installHeader(); // Only need one header - libghostty_shared.install("libghostty.so"); - libghostty_static.install("libghostty.a"); + lib_shared.installHeader(); // Only need one header + if (config.target.result.os.tag == .windows) { + lib_shared.install("ghostty.dll"); + lib_static.install("ghostty-static.lib"); + } else { + lib_shared.install("libghostty.so"); + lib_static.install("libghostty.a"); + } } } // macOS only artifacts. These will error if they're initialized for // other targets. - if (config.target.result.os.tag.isDarwin()) { + if (config.target.result.os.tag.isDarwin() and + (config.emit_xcframework or config.emit_macos_app)) + { // Ghostty xcframework const xcframework = try buildpkg.GhosttyXCFramework.init( b, @@ -202,7 +221,9 @@ // On macOS we can run the macOS app. For "run" we always force // a native-only build so that we can run as quickly as possible. - if (config.target.result.os.tag.isDarwin()) { + if (config.target.result.os.tag.isDarwin() and + (config.emit_xcframework or config.emit_macos_app)) + { const xcframework_native = try buildpkg.GhosttyXCFramework.init( b, &deps, diff --git a/build.zig.zon b/build.zig.zon --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ghostty, - .version = "1.3.0-dev", + .version = "1.3.2-dev", .paths = .{""}, .fingerprint = 0x64407a2a0b4147e5, .minimum_zig_version = "0.15.2", @@ -91,8 +91,8 @@ .lazy = true, }, .wayland_protocols = .{ - .url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", - .hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", + .url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", + .hash = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA", .lazy = true, }, .plasma_wayland_protocols = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -139,6 +139,11 @@ "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", "hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=" }, + "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": { + "name": "wayland_protocols", + "url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", + "hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=" + }, "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": { "name": "wuffs", "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", diff --git a/build.zig.zon.nix b/build.zig.zon.nix --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -307,6 +307,14 @@ }; } { + name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA"; + path = fetchZigArtifact { + name = "wayland_protocols"; + url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz"; + hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="; + }; + } + { name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs"; path = fetchZigArtifact { name = "wuffs"; diff --git a/build.zig.zon.txt b/build.zig.zon.txt --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -34,3 +34,4 @@ https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz +https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz diff --git a/flake.lock b/flake.lock --- a/flake.lock +++ b/flake.lock @@ -16,24 +16,6 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "home-manager": { "inputs": { "nixpkgs": [ @@ -70,14 +52,15 @@ "root": { "inputs": { "flake-compat": "flake-compat", - "flake-utils": "flake-utils", "home-manager": "home-manager", "nixpkgs": "nixpkgs", + "systems": "systems", "zig": "zig", "zon2nix": "zon2nix" } }, "systems": { + "flake": false, "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -97,19 +80,19 @@ "flake-compat": [ "flake-compat" ], - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" + ], + "systems": [ + "systems" ] }, "locked": { - "lastModified": 1763295135, - "narHash": "sha256-sGv/NHCmEnJivguGwB5w8LRmVqr1P72OjS+NzcJsssE=", + "lastModified": 1773145353, + "narHash": "sha256-dE8zx8WA54TRmFFQBvA48x/sXGDTP7YaDmY6nNKMAYw=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "64f8b42cfc615b2cf99144adf2b7728c7847c72a", + "rev": "8666155d83bf792956a7c40915508e6d4b2b8716", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,6 @@ # Gnome 49/Gtk 4.20. # nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; - flake-utils.url = "github:numtide/flake-utils"; # Used for shell.nix flake-compat = { @@ -18,12 +17,17 @@ flake = false; }; + systems = { + url = "github:nix-systems/default"; + flake = false; + }; + zig = { url = "github:mitchellh/zig-overlay"; inputs = { nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; flake-compat.follows = "flake-compat"; + systems.follows = "systems"; }; }; diff --git a/typos.toml b/typos.toml --- a/typos.toml +++ b/typos.toml @@ -46,6 +46,9 @@ "\"hel\\\\x", # Ignore long hex-like IDs such as 815E26BA2EF1E00F005C67B1 "[0-9A-F]{12,}", + # Ignore Apple four char codes + 'code="[A-Za-z]{4,8}"', + '"[A-Za-z]{4}"\.fourCharCode', ] [default.extend-words] diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -19,6 +19,7 @@ # discussion by the author. Maintainers can denounce users by commenting # "!denounce" or "!denounce [username]" on a discussion. 00-kat +04cb aalhendi abdurrahmanski abudvytis @@ -28,8 +29,12 @@ alanmoyano alexfeijoo44 alexjuca +alosarjos amadeus andrejdaskalov +anhthang +anmitalidev +anthonyzhoon atomk balazs-szucs bennettp123 @@ -40,24 +45,36 @@ bkircher bo2themax brentschroeter +brianc442 cespare charliie-dev chernetskyi +chronologos cmwetherell +crayxt craziestowl curtismoncoq d-dudas daiimus damyanbogoev danulqua +dariogriffo +davidsanchez222 +deblasis dervedro +devsunb diaaeddin +dmehala doprz douglance +douglas drepper +dzhlobo +ekaterinepapava elias8 ephemera eriksremess +faukah filip7 flou francescarpi @@ -69,12 +86,20 @@ guilhermetk hakonhagland halosatrio +heaths +heddxh heddxh +-highimpact-dev Disrespectful AI user +hlcfan hqnna +hulet icodesign +j0hnm4r5 jacobsandlund jake-stewart jcollie +jesusvazquez jguthmiller +jmcgover johnslavik josephmart jparise @@ -85,6 +110,7 @@ kirwiisp kjvdven kloneets +-kody-w koranir kristina8888 kristofersoler @@ -92,12 +118,18 @@ liby linustalacko lonsagisawa +luisnquin +lynicis +mac0ne mahnokropotkinvich marijagjorgjieva markdorison markhuot +marler8997 marrocco-simone matkotiric +micaeljarniac +michielvk miguelelgallo mihi314 mikailmm @@ -105,6 +137,7 @@ mischief mitchellh miupa +molechowski mrmage mtak natesmyth @@ -113,17 +146,23 @@ nmggithub noib3 nwehg +ocean6954 oshdubh +paaloeye pan93412 pangoraw +pauley-unsaturated peilingjiang peterdavehello +philocalyst phush0 piedrahitac pluiedev pouwerkerk +poweruser64 prakhar54-byte priyans-hu +puzza007 qwerasd205 reo101 rgehan @@ -133,18 +172,26 @@ rockorager rpfaeffle secrus +seruman silveirapf slsrepo sunshine-syz +tdgroot tdslot ticclick tnagatomi trag1c tristan957 +turbolent tweedbeetle uhojin +unphased uzaaft +vaughanandrews +viruslobster vlsi +wyounas yamshta +ydah zenyr zeshi09 diff --git a/example/.gitignore b/example/.gitignore --- a/example/.gitignore +++ b/example/.gitignore @@ -2,3 +2,4 @@ dist/ node_modules/ example.wasm* +build/ diff --git a/example/AGENTS.md b/example/AGENTS.md new file mode 100644 --- /dev/null +++ b/example/AGENTS.md @@ -0,0 +1,39 @@ +# Example Libghostty Projects + +Each example is a standalone project with its own `build.zig`, +`build.zig.zon`, `README.md`, and `src/main.c` (or `.zig`). Examples are +auto-discovered by CI via `example/*/build.zig.zon`, so no workflow file +edits are needed when adding a new example. + +## Adding a New Example + +1. Copy an existing example directory (e.g., `c-vt-encode-focus/`) as a + starting point. +2. Update `build.zig.zon`: change `.name`, generate a **new unique** + `.fingerprint` value (a random `u64` hex literal), and keep + `.minimum_zig_version` matching the others. +3. Update `build.zig`: change the executable `.name` to match the directory. +4. Write a `README.md` following the existing format. + +## Doxygen Snippet Tags + +Example source files use Doxygen `@snippet` tags so the corresponding +header in `include/ghostty/vt/` can reference them. Wrap the relevant +code with `//! [snippet-name]` markers: + +```c +//! [my-snippet] +int main() { ... } +//! [my-snippet] +``` + +The header then uses `@snippet

/src/main.c my-snippet` instead of +inline `@code` blocks. Never duplicate example code inline in the +headers — always use `@snippet`. When modifying example code, keep the +snippet markers in sync with the headers in `include/ghostty/vt/`. + +## Conventions + +- Executable names use underscores: `c_vt_encode_focus` (not hyphens). +- All C examples link `ghostty-vt` via `lazyDependency("ghostty", ...)`. +- `build.zig` files follow a common template — keep them consistent. diff --git a/example/README.md b/example/README.md new file mode 100644 --- /dev/null +++ b/example/README.md @@ -0,0 +1,17 @@ +# Examples + +Standalone projects demonstrating the Ghostty library APIs. +The directories starting with `c-` use the C API and the directories +starting with `zig-` use the Zig API. + +Every example can be built and run using `zig build` and `zig build run` +from within the respective example directory. +Even the C API examples use the Zig build system (not the language) to +build the project. + +## Running an Example + +```shell-session +cd example/ +zig build run +``` diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -169,6 +169,12 @@ }, { "type": "archive", + "url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", + "dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA", + "sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223" + }, + { + "type": "archive", "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "dest": "vendor/p/N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs", "sha256": "9e4cd20abe96e6c4c6ede9c3057108860126e7be2e2c3e35515476c250be1c13" diff --git a/include/ghostty.h b/include/ghostty.h --- a/include/ghostty.h +++ b/include/ghostty.h @@ -17,6 +17,11 @@ #include #include +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif + //------------------------------------------------------------------- // Macros @@ -889,6 +894,7 @@ GHOSTTY_ACTION_RENDER_INSPECTOR, GHOSTTY_ACTION_DESKTOP_NOTIFICATION, GHOSTTY_ACTION_SET_TITLE, + GHOSTTY_ACTION_SET_TAB_TITLE, GHOSTTY_ACTION_PROMPT_TITLE, GHOSTTY_ACTION_PWD, GHOSTTY_ACTION_MOUSE_SHAPE, @@ -937,6 +943,7 @@ ghostty_action_inspector_e inspector; ghostty_action_desktop_notification_s desktop_notification; ghostty_action_set_title_s set_title; + ghostty_action_set_title_s set_tab_title; ghostty_action_prompt_title_e prompt_title; ghostty_action_pwd_s pwd; ghostty_action_mouse_shape_e mouse_shape; @@ -968,7 +975,7 @@ } ghostty_action_s; typedef void (*ghostty_runtime_wakeup_cb)(void*); -typedef void (*ghostty_runtime_read_clipboard_cb)(void*, +typedef bool (*ghostty_runtime_read_clipboard_cb)(void*, ghostty_clipboard_e, void*); typedef void (*ghostty_runtime_confirm_read_clipboard_cb)( diff --git a/macos/AGENTS.md b/macos/AGENTS.md --- a/macos/AGENTS.md +++ b/macos/AGENTS.md @@ -1,9 +1,34 @@ # macOS Ghostty Application - Use `swiftlint` for formatting and linting Swift code. -- If code outside of this directory is modified, use +- If code outside of `macos/` directory is modified, use `zig build -Demit-macos-app=false` before building the macOS app to update the underlying Ghostty library. -- Use `xcodebuild` to build the macOS app, do not use `zig build` +- Use `macos/build.nu` to build the macOS app, do not use `zig build` (except to build the underlying library as mentioned above). -- Run unit tests directly with `xcodebuild` + - Build: `macos/build.nu [--scheme Ghostty] [--configuration Debug] [--action build]` + - Output: `macos/build//Ghostty.app` (e.g. `macos/build/Debug/Ghostty.app`) +- Run unit tests directly with `macos/build.nu --action test` + +## AppleScript + +- The AppleScript scripting definition is in `macos/Ghostty.sdef`. +- Guard AppleScript entry points and object accessors with the + `macos-applescript` configuration (use `NSApp.isAppleScriptEnabled` + and `NSApp.validateScript(command:)` where applicable). +- In `macos/Ghostty.sdef`, keep top-level definitions in this order: + 1. Classes + 2. Records + 3. Enums + 4. Commands +- Test AppleScript support: + (1) Build with `macos/build.nu` + (2) Launch and activate the app via osascript using the absolute path + to the built app bundle: + `osascript -e 'tell application "" to activate'` + (3) Wait a few seconds for the app to fully launch and open a terminal. + (4) Run test scripts with `osascript`, always targeting the app by + its absolute path (not by name) to avoid calling the wrong + application. + (5) When done, quit via: + `osascript -e 'tell application "" to quit'` diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist --- a/macos/Ghostty-Info.plist +++ b/macos/Ghostty-Info.plist @@ -2,6 +2,8 @@ + NSAutoFillRequiresTextContentTypeForOneTimeCodeOnMac + NSDockTilePlugIn DockTilePlugin.plugin CFBundleDocumentTypes @@ -55,8 +57,12 @@ MDItemKeywords Terminal + NSAppleScriptEnabled + NSHighResolutionCapable + OSAScriptingDefinition + Ghostty.sdef NSServices diff --git a/macos/Ghostty.sdef b/macos/Ghostty.sdef new file mode 100644 --- /dev/null +++ b/macos/Ghostty.sdef @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/build.nu b/macos/build.nu new file mode 100644 --- /dev/null +++ b/macos/build.nu @@ -0,0 +1,32 @@ +#!/usr/bin/env nu + +# Build the macOS Ghostty app using xcodebuild with a clean environment +# to avoid Nix shell interference (NIX_LDFLAGS, NIX_CFLAGS_COMPILE, etc.). + +def main [ + --scheme: string = "Ghostty" # Xcode scheme (Ghostty, Ghostty-iOS, DockTilePlugin) + --configuration: string = "Debug" # Build configuration (Debug, Release, ReleaseLocal) + --action: string = "build" # xcodebuild action (build, test, clean, etc.) +] { + let project = ($env.FILE_PWD | path join "Ghostty.xcodeproj") + let build_dir = ($env.FILE_PWD | path join "build") + + # Skip UI tests for CLI-based invocations because it requires + # special permissions. + let skip_testing = if $action == "test" { + [-skip-testing GhosttyUITests] + } else { + [] + } + + (^env -i + $"HOME=($env.HOME)" + "PATH=/usr/bin:/bin:/usr/sbin:/sbin" + xcodebuild + -project $project + -scheme $scheme + -configuration $configuration + $"SYMROOT=($build_dir)" + ...$skip_testing + $action) +} diff --git a/nix/devShell.nix b/nix/devShell.nix --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -8,6 +8,7 @@ appstream, flatpak-builder, gdb, + cmake, #, glxinfo # unused ncurses, nodejs, @@ -91,6 +92,7 @@ packages = [ # For builds + cmake doxygen jq llvmPackages_latest.llvm diff --git a/nix/package.nix b/nix/package.nix --- a/nix/package.nix +++ b/nix/package.nix @@ -30,7 +30,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "ghostty"; - version = "1.3.0-dev"; + version = "1.3.2-dev"; # We limit source like this to try and reduce the amount of rebuilds as possible # thus we only provide the source that is needed for the build diff --git a/po/bg.po b/po/bg.po --- a/po/bg.po +++ b/po/bg.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-09 22:07+0200\n" "Last-Translator: reo101 \n" "Language-Team: Bulgarian \n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Инспектор на терминала" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "За Ghostty" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Текущият процес в това разделяне ще бъде прекратен." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Командата завърши" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Командата завърши успешно" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Командата завърши неуспешно" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Конфигурацията е презаредена" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Копирано в клипборда" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Клипбордът е изчистен" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Разработчици на Ghostty" diff --git a/po/ca.po b/po/ca.po --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2025-08-24 19:22+0200\n" "Last-Translator: Kristofer Soler " "<31729650+KristoferSoler@users.noreply.github.com>\n" @@ -244,7 +244,7 @@ msgid "Terminal Inspector" msgstr "Inspector de terminal" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Sobre Ghostty" @@ -316,15 +316,15 @@ msgid "The currently running process in this split will be terminated." msgstr "El procés actualment en execució en aquesta divisió es tancarà." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Comanda finalitzada" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Comanda completada amb èxit" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Comanda fallida" @@ -348,14 +348,14 @@ msgid "Reloaded the configuration" msgstr "S'ha tornat a carregar la configuració" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Copiat al porta-retalls" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Porta-retalls netejat" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Desenvolupadors de Ghostty" diff --git a/po/com.mitchellh.ghostty.pot b/po/com.mitchellh.ghostty.pot --- a/po/com.mitchellh.ghostty.pot +++ b/po/com.mitchellh.ghostty.pot @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -235,7 +235,7 @@ msgid "Terminal Inspector" msgstr "" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "" @@ -301,15 +301,15 @@ msgid "The currently running process in this split will be terminated." msgstr "" -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "" @@ -333,14 +333,14 @@ msgid "Reloaded the configuration" msgstr "" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "" diff --git a/po/de.po b/po/de.po --- a/po/de.po +++ b/po/de.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-13 08:05+0100\n" "Last-Translator: Klaus Hipp \n" "Language-Team: German \n" @@ -247,7 +247,7 @@ msgid "Terminal Inspector" msgstr "Terminalinspektor" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Über Ghostty" @@ -319,15 +319,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Der aktuell laufende Prozess in diesem geteilten Fenster wird beendet." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Befehl abgeschlossen" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Befehl erfolgreich" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Befehl fehlgeschlagen" @@ -351,14 +351,14 @@ msgid "Reloaded the configuration" msgstr "Konfiguration wurde neu geladen" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "In die Zwischenablage kopiert" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Zwischenablage geleert" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty-Entwickler" diff --git a/po/es_AR.po b/po/es_AR.po --- a/po/es_AR.po +++ b/po/es_AR.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-19 13:34-0300\n" "Last-Translator: Alan Moyano \n" "Language-Team: Argentinian \n" @@ -108,7 +108,7 @@ #: src/apprt/gtk/ui/1.2/surface.blp:7 msgid "Unable to acquire an OpenGL context for rendering." -msgstr "No se puedo obtener un contexto de OpenGL para el renderizado" +msgstr "No se pudo obtener un contexto de OpenGL para el renderizado" #: src/apprt/gtk/ui/1.2/surface.blp:97 msgid "" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Inspector de la terminal" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Acerca de Ghostty" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "El proceso actualmente en ejecución en esta división será terminado." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Comando finalizado" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Comando ejecutado correctamente" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Comando fallido" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Configuración recargada" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Copiado al portapapeles" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Portapapeles limpiado" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Desarrolladores de Ghostty" diff --git a/po/es_BO.po b/po/es_BO.po --- a/po/es_BO.po +++ b/po/es_BO.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-12 17:46+0200\n" "Last-Translator: Miguel Peredo \n" "Language-Team: Spanish \n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Inspector de la terminal" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Acerca de Ghostty" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "El proceso actualmente en ejecución en esta división será terminado." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Comando finalizado" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Comando exitoso" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Comando fallido" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Configuración recargada" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Copiado al portapapeles" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "El portapapeles está limpio" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Desarrolladores de Ghostty" diff --git a/po/es_ES.po b/po/es_ES.po new file mode 100644 --- /dev/null +++ b/po/es_ES.po @@ -0,0 +1,360 @@ +# Spanish translations for com.mitchellh.ghostty package +# Traducciones al español para el paquete com.mitchellh.ghostty. +# Copyright (C) 2026 "Mitchell Hashimoto, Ghostty contributors" +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# José Miguel Sarasola , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" +"PO-Revision-Date: 2026-02-18 10:57+0100\n" +"Last-Translator: José Miguel Sarasola \n" +"Language-Team: \n" +"Language: es_ES\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" + +#: dist/linux/ghostty_nautilus.py:53 +msgid "Open in Ghostty" +msgstr "Abrir en Ghostty" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:197 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:201 +msgid "Authorize Clipboard Access" +msgstr "Autorizar acceso al portapapeles" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:17 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:17 +msgid "Deny" +msgstr "Denegar" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:18 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:18 +msgid "Allow" +msgstr "Permitir" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92 +msgid "Remember choice for this split" +msgstr "Recordar elección para esta división" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 +msgid "Reload configuration to show this prompt again" +msgstr "Recargar configuración para volver a mostrar este aviso" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 +#: src/apprt/gtk/ui/1.5/title-dialog.blp:8 +msgid "Cancel" +msgstr "Cancelar" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:8 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:85 +#: src/apprt/gtk/ui/1.3/surface-child-exited.blp:17 +msgid "Close" +msgstr "Cerrar" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6 +msgid "Configuration Errors" +msgstr "Errores en la configuración" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:7 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Se detectaron uno o más errores de configuración. Por favor, revisa los " +"errores más abajo y recarga la configuración o ignora estos errores." + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +msgid "Ignore" +msgstr "Ignorar" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:11 +#: src/apprt/gtk/ui/1.2/surface.blp:371 src/apprt/gtk/ui/1.5/window.blp:300 +msgid "Reload Configuration" +msgstr "Recargar configuración" + +#: src/apprt/gtk/ui/1.2/debug-warning.blp:7 +#: src/apprt/gtk/ui/1.3/debug-warning.blp:6 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ ¡Estás ejecutando una versión de depuración de Ghostty! El rendimiento se " +"verá perjudicado." + +#: src/apprt/gtk/ui/1.5/inspector-window.blp:5 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspector de terminal" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:29 +msgid "Find…" +msgstr "Buscar…" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:64 +msgid "Previous Match" +msgstr "Resultado previo" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:74 +msgid "Next Match" +msgstr "Resultado siguiente" + +#: src/apprt/gtk/ui/1.2/surface.blp:6 +msgid "Oh, no." +msgstr "Oh, no." + +#: src/apprt/gtk/ui/1.2/surface.blp:7 +msgid "Unable to acquire an OpenGL context for rendering." +msgstr "No se pudo obtener un contexto de OpenGL para el renderizado." + +#: src/apprt/gtk/ui/1.2/surface.blp:97 +msgid "" +"This terminal is in read-only mode. You can still view, select, and scroll " +"through the content, but no input events will be sent to the running " +"application." +msgstr "" +"Este terminal está en modo de solo lectura. Puedes ver, seleccionar y hacer " +"scroll del contenido, pero no se enviarán eventos de entrada a la aplicación " +"en ejecución." + +#: src/apprt/gtk/ui/1.2/surface.blp:107 +msgid "Read-only" +msgstr "Solo lectura" + +#: src/apprt/gtk/ui/1.2/surface.blp:260 src/apprt/gtk/ui/1.5/window.blp:200 +msgid "Copy" +msgstr "Copiar" + +#: src/apprt/gtk/ui/1.2/surface.blp:265 src/apprt/gtk/ui/1.5/window.blp:205 +msgid "Paste" +msgstr "Pegar" + +#: src/apprt/gtk/ui/1.2/surface.blp:270 +msgid "Notify on Next Command Finish" +msgstr "Notificar al finalizar el siguiente comando" + +#: src/apprt/gtk/ui/1.2/surface.blp:277 src/apprt/gtk/ui/1.5/window.blp:273 +msgid "Clear" +msgstr "Limpiar" + +#: src/apprt/gtk/ui/1.2/surface.blp:282 src/apprt/gtk/ui/1.5/window.blp:278 +msgid "Reset" +msgstr "Reiniciar" + +#: src/apprt/gtk/ui/1.2/surface.blp:289 src/apprt/gtk/ui/1.5/window.blp:242 +msgid "Split" +msgstr "Dividir" + +#: src/apprt/gtk/ui/1.2/surface.blp:292 src/apprt/gtk/ui/1.5/window.blp:245 +msgid "Change Title…" +msgstr "Cambiar título…" + +#: src/apprt/gtk/ui/1.2/surface.blp:297 src/apprt/gtk/ui/1.5/window.blp:177 +#: src/apprt/gtk/ui/1.5/window.blp:250 +msgid "Split Up" +msgstr "Dividir arriba" + +#: src/apprt/gtk/ui/1.2/surface.blp:303 src/apprt/gtk/ui/1.5/window.blp:182 +#: src/apprt/gtk/ui/1.5/window.blp:255 +msgid "Split Down" +msgstr "Dividir abajo" + +#: src/apprt/gtk/ui/1.2/surface.blp:309 src/apprt/gtk/ui/1.5/window.blp:187 +#: src/apprt/gtk/ui/1.5/window.blp:260 +msgid "Split Left" +msgstr "Dividir a la izquierda" + +#: src/apprt/gtk/ui/1.2/surface.blp:315 src/apprt/gtk/ui/1.5/window.blp:192 +#: src/apprt/gtk/ui/1.5/window.blp:265 +msgid "Split Right" +msgstr "Dividir a la derecha" + +#: src/apprt/gtk/ui/1.2/surface.blp:321 +msgid "Close Split" +msgstr "" + +#: src/apprt/gtk/ui/1.2/surface.blp:327 +msgid "Tab" +msgstr "Pestaña" + +#: src/apprt/gtk/ui/1.2/surface.blp:330 src/apprt/gtk/ui/1.5/window.blp:224 +#: src/apprt/gtk/ui/1.5/window.blp:320 +msgid "Change Tab Title…" +msgstr "Cambiar título de la pestaña…" + +#: src/apprt/gtk/ui/1.2/surface.blp:335 src/apprt/gtk/ui/1.5/window.blp:57 +#: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 +msgid "New Tab" +msgstr "Nueva pestaña" + +#: src/apprt/gtk/ui/1.2/surface.blp:340 src/apprt/gtk/ui/1.5/window.blp:234 +msgid "Close Tab" +msgstr "Cerrar pestaña" + +#: src/apprt/gtk/ui/1.2/surface.blp:347 +msgid "Window" +msgstr "Ventana" + +#: src/apprt/gtk/ui/1.2/surface.blp:350 src/apprt/gtk/ui/1.5/window.blp:212 +msgid "New Window" +msgstr "Nueva ventana" + +#: src/apprt/gtk/ui/1.2/surface.blp:355 src/apprt/gtk/ui/1.5/window.blp:217 +msgid "Close Window" +msgstr "Cerrar ventana" + +#: src/apprt/gtk/ui/1.2/surface.blp:363 +msgid "Config" +msgstr "Configuración" + +#: src/apprt/gtk/ui/1.2/surface.blp:366 src/apprt/gtk/ui/1.5/window.blp:295 +msgid "Open Configuration" +msgstr "Abrir configuración" + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:5 +msgid "Leave blank to restore the default title." +msgstr "Dejar en blanco para restaurar el título predeterminado." + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:9 +msgid "OK" +msgstr "Aceptar" + +#: src/apprt/gtk/ui/1.5/window.blp:58 src/apprt/gtk/ui/1.5/window.blp:108 +msgid "New Split" +msgstr "Nueva división" + +#: src/apprt/gtk/ui/1.5/window.blp:68 src/apprt/gtk/ui/1.5/window.blp:126 +msgid "View Open Tabs" +msgstr "Ver pestañas abiertas" + +#: src/apprt/gtk/ui/1.5/window.blp:78 src/apprt/gtk/ui/1.5/window.blp:140 +msgid "Main Menu" +msgstr "Menú principal" + +#: src/apprt/gtk/ui/1.5/window.blp:285 +msgid "Command Palette" +msgstr "Paleta de comandos" + +#: src/apprt/gtk/ui/1.5/window.blp:290 +msgid "Terminal Inspector" +msgstr "Inspector de terminal" + +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 +msgid "About Ghostty" +msgstr "Acerca de Ghostty" + +#: src/apprt/gtk/ui/1.5/window.blp:312 +msgid "Quit" +msgstr "Salir" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:17 +msgid "Execute a command…" +msgstr "Ejecutar un comando…" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicación está intentando escribir en el portapapeles. El contenido " +"actual del portapapeles se muestra a continuación." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:202 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Una aplicación está intentando leer desde el portapapeles. El contenido " +"actual del portapapeles se muestra a continuación." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:205 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Advertencia: Pegado potencialmente inseguro" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:206 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Pegar este texto en el terminal puede ser peligroso ya que parece que " +"algunos comandos podrían ejecutarse." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:184 +msgid "Quit Ghostty?" +msgstr "¿Salir de Ghostty?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:185 +msgid "Close Tab?" +msgstr "¿Cerrar pestaña?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:186 +msgid "Close Window?" +msgstr "¿Cerrar ventana?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:187 +msgid "Close Split?" +msgstr "¿Cerrar división?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:193 +msgid "All terminal sessions will be terminated." +msgstr "Todas las sesiones del terminal serán finalizadas." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:194 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Todas las sesiones del terminal en esta pestaña serán finalizadas." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:195 +msgid "All terminal sessions in this window will be terminated." +msgstr "Todas las sesiones del terminal en esta ventana serán finalizadas." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:196 +msgid "The currently running process in this split will be terminated." +msgstr "El proceso ejecutándose en esta división será finalizado." + +#: src/apprt/gtk/class/surface.zig:1141 +msgid "Command Finished" +msgstr "Comando finalizado" + +#: src/apprt/gtk/class/surface.zig:1142 +msgid "Command Succeeded" +msgstr "Comando completado" + +#: src/apprt/gtk/class/surface.zig:1143 +msgid "Command Failed" +msgstr "Comando fallido" + +#: src/apprt/gtk/class/surface_child_exited.zig:109 +msgid "Command succeeded" +msgstr "Comando completado" + +#: src/apprt/gtk/class/surface_child_exited.zig:113 +msgid "Command failed" +msgstr "Comando fallido" + +#: src/apprt/gtk/class/title_dialog.zig:225 +msgid "Change Terminal Title" +msgstr "Cambiar el título del terminal" + +#: src/apprt/gtk/class/title_dialog.zig:226 +msgid "Change Tab Title" +msgstr "Cambiar el título de la pestaña" + +#: src/apprt/gtk/class/window.zig:1067 +msgid "Reloaded the configuration" +msgstr "Configuración recargada" + +#: src/apprt/gtk/class/window.zig:1611 +msgid "Copied to clipboard" +msgstr "Copiado al portapapeles" + +#: src/apprt/gtk/class/window.zig:1613 +msgid "Cleared clipboard" +msgstr "Portapapeles vaciado" + +#: src/apprt/gtk/class/window.zig:1753 +msgid "Ghostty Developers" +msgstr "Desarrolladores de Ghostty" diff --git a/po/fr.po b/po/fr.po --- a/po/fr.po +++ b/po/fr.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 15:03+0200\n" "Last-Translator: Pangoraw \n" "Language-Team: French \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Inspecteur de terminal" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "À propos de Ghostty" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Le processus en cours dans ce panneau va être arrêté." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Commande terminée" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Commande réussie" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "La commande a échoué" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "Configuration rechargée" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Copié dans le presse-papiers" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Presse-papiers vidé" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Les développeurs de Ghostty" diff --git a/po/ga.po b/po/ga.po --- a/po/ga.po +++ b/po/ga.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 14:32+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin \n" "Language-Team: Irish \n" @@ -241,7 +241,7 @@ msgid "Terminal Inspector" msgstr "Cigire teirminéil" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Maidir le Ghostty" @@ -314,15 +314,15 @@ msgstr "" "Cuirfear deireadh leis an bpróiseas atá ar siúl faoi láthair sa scoilt seo." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Ordú críochnaithe" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "D’éirigh leis an ordú" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Theip ar an ordú" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Tá an chumraíocht athlódáilte" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Cóipeáilte chuig an ghearrthaisce" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Gearrthaisce glanta" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Forbróirí Ghostty" diff --git a/po/he.po b/po/he.po --- a/po/he.po +++ b/po/he.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 18:14+0300\n" "Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd " "\n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "בודק המסוף" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "אודות Ghostty" @@ -311,15 +311,15 @@ msgid "The currently running process in this split will be terminated." msgstr "התהליך שרץ כרגע בפיצול זה יסתיים." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "הפקודה הסתיימה" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "הפקודה הצליחה" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "הפקודה נכשלה" @@ -343,14 +343,14 @@ msgid "Reloaded the configuration" msgstr "ההגדרות הוטענו מחדש" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "הועתק ללוח ההעתקה" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "לוח ההעתקה רוקן" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "המפתחים של Ghostty" diff --git a/po/hr.po b/po/hr.po --- a/po/hr.po +++ b/po/hr.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 21:00+0200\n" "Last-Translator: Filip7 \n" "Language-Team: Croatian \n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Inspektor terminala" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "O Ghosttyju" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Pokrenuti procesi u ovoj podjeli će biti prekinuti." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Naredba je završena" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Naredba je uspjela" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Naredba nije uspjela" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Ponovno učitane postavke" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Kopirano u međuspremnik" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Očišćen međuspremnik" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Razvijatelji Ghosttyja" diff --git a/po/hu.po b/po/hu.po --- a/po/hu.po +++ b/po/hu.po @@ -7,8 +7,8 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" -"PO-Revision-Date: 2026-02-10 18:32+0200\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" +"PO-Revision-Date: 2026-02-26 21:00+0100\n" "Last-Translator: Balázs Szücs \n" "Language-Team: Hungarian \n" "Language: hu\n" @@ -19,7 +19,7 @@ #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" -msgstr "" +msgstr "Megnyitás a Ghostty alkalmazásban" #: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12 #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12 @@ -183,7 +183,7 @@ #: src/apprt/gtk/ui/1.2/surface.blp:330 src/apprt/gtk/ui/1.5/window.blp:224 #: src/apprt/gtk/ui/1.5/window.blp:320 msgid "Change Tab Title…" -msgstr "" +msgstr "Fül címének módosítása…" #: src/apprt/gtk/ui/1.2/surface.blp:335 src/apprt/gtk/ui/1.5/window.blp:57 #: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Terminálvizsgáló" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "A Ghostty névjegye" @@ -314,15 +314,15 @@ 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/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Parancs befejeződött" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Parancs sikeres" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Parancs sikertelen" @@ -340,20 +340,20 @@ #: src/apprt/gtk/class/title_dialog.zig:226 msgid "Change Tab Title" -msgstr "" +msgstr "Fül címének módosítása" #: src/apprt/gtk/class/window.zig:1067 msgid "Reloaded the configuration" msgstr "Konfiguráció frissítve" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Vágólapra másolva" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Vágólap törölve" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty fejlesztők" diff --git a/po/id.po b/po/id.po --- a/po/id.po +++ b/po/id.po @@ -1,25 +1,26 @@ # Indonesian 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. -# Satrio Bayu Aji , 2025. -# Mikail Muzakki , 2025. +# Satrio Bayu Aji , 2026. +# Mikail Muzakki , 2026. # msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" -"PO-Revision-Date: 2025-08-01 10:15+0700\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" +"PO-Revision-Date: 2026-03-08 20:23+0700\n" "Last-Translator: Mikail Muzakki \n" "Language-Team: Indonesian \n" "Language: id\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" #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" -msgstr "" +msgstr "Buka di Ghostty" #: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12 #: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12 @@ -91,23 +92,23 @@ #: src/apprt/gtk/ui/1.2/search-overlay.blp:29 msgid "Find…" -msgstr "" +msgstr "Cari…" #: src/apprt/gtk/ui/1.2/search-overlay.blp:64 msgid "Previous Match" -msgstr "" +msgstr "Hasil sebelumnya" #: src/apprt/gtk/ui/1.2/search-overlay.blp:74 msgid "Next Match" -msgstr "" +msgstr "Hasil berikutnya" #: src/apprt/gtk/ui/1.2/surface.blp:6 msgid "Oh, no." -msgstr "" +msgstr "Oh, tidak." #: src/apprt/gtk/ui/1.2/surface.blp:7 msgid "Unable to acquire an OpenGL context for rendering." -msgstr "" +msgstr "Tidak dapat memperoleh konteks OpenGL untuk penampilan grafis." #: src/apprt/gtk/ui/1.2/surface.blp:97 msgid "" @@ -115,10 +116,13 @@ "through the content, but no input events will be sent to the running " "application." msgstr "" +"Terminal ini sedang dalam model hanya baca. Anda hanya bisa melihat, " +"memilih, dan menggulir konten, tetapi peristiwa input tidak akan dikirim ke " +"aplikasi berjalan." #: src/apprt/gtk/ui/1.2/surface.blp:107 msgid "Read-only" -msgstr "" +msgstr "Hanya baca" #: src/apprt/gtk/ui/1.2/surface.blp:260 src/apprt/gtk/ui/1.5/window.blp:200 msgid "Copy" @@ -130,7 +134,7 @@ #: src/apprt/gtk/ui/1.2/surface.blp:270 msgid "Notify on Next Command Finish" -msgstr "" +msgstr "Beri tahu saat perintah berikutnya selesai" #: src/apprt/gtk/ui/1.2/surface.blp:277 src/apprt/gtk/ui/1.5/window.blp:273 msgid "Clear" @@ -179,7 +183,7 @@ #: src/apprt/gtk/ui/1.2/surface.blp:330 src/apprt/gtk/ui/1.5/window.blp:224 #: src/apprt/gtk/ui/1.5/window.blp:320 msgid "Change Tab Title…" -msgstr "" +msgstr "Ubah judul tab…" #: src/apprt/gtk/ui/1.2/surface.blp:335 src/apprt/gtk/ui/1.5/window.blp:57 #: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 @@ -238,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Inspektur terminal" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Tentang Ghostty" @@ -310,17 +314,17 @@ msgid "The currently running process in this split will be terminated." msgstr "Proses yang sedang berjalan dalam belahan ini akan diakhiri." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" -msgstr "" +msgstr "Perintah selesai" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" -msgstr "" +msgstr "Perintah berhasil" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" -msgstr "" +msgstr "Perintah gagal" #: src/apprt/gtk/class/surface_child_exited.zig:109 msgid "Command succeeded" @@ -336,20 +340,20 @@ #: src/apprt/gtk/class/title_dialog.zig:226 msgid "Change Tab Title" -msgstr "" +msgstr "Ubah judul tab" #: src/apprt/gtk/class/window.zig:1067 msgid "Reloaded the configuration" msgstr "Memuat ulang konfigurasi" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Disalin ke papan klip" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Papan klip dibersihkan" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Pengembang Ghostty" diff --git a/po/it.po b/po/it.po --- a/po/it.po +++ b/po/it.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2025-09-06 19:40+0200\n" "Last-Translator: Giacomo Bettini \n" "Language-Team: Italian \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Ispettore del terminale" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Informazioni su Ghostty" @@ -316,15 +316,15 @@ msgstr "" "Il processo attualmente in esecuzione in questa divisione sarà terminato." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Comando terminato" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Comando riuscito" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Comando fallito" @@ -348,14 +348,14 @@ msgid "Reloaded the configuration" msgstr "Configurazione ricaricata" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Copiato negli Appunti" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Appunti svuotati" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Sviluppatori di Ghostty" diff --git a/po/ja.po b/po/ja.po --- a/po/ja.po +++ b/po/ja.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-11 12:02+0900\n" "Last-Translator: Takayuki Nagatomi \n" "Language-Team: Japanese\n" @@ -241,7 +241,7 @@ msgid "Terminal Inspector" msgstr "端末インスペクター" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Ghostty について" @@ -313,15 +313,15 @@ msgid "The currently running process in this split will be terminated." msgstr "分割ウィンドウ内のすべてのプロセスが終了します。" -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "コマンド実行終了" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "コマンド実行成功" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "コマンド実行失敗" @@ -345,14 +345,14 @@ msgid "Reloaded the configuration" msgstr "設定を再読み込みしました" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "クリップボードにコピーしました" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "クリップボードを空にしました" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty 開発者" diff --git a/po/kk.po b/po/kk.po --- a/po/kk.po +++ b/po/kk.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-03-04 21:16+0500\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: Kazakh \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Терминал инспекторы" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Ghostty туралы" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Осы бөлудегі ағымдағы орындалып жатқан процесс тоқтатылады." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Команда аяқталды" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Команда сәтті орындалды" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Команда сәтсіз аяқталды" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "Конфигурация қайта жүктелді" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Алмасу буферіне көшірілді" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Алмасу буфері тазартылды" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty әзірлеушілері" diff --git a/po/ko_KR.po b/po/ko_KR.po --- a/po/ko_KR.po +++ b/po/ko_KR.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-11 12:50+0900\n" "Last-Translator: GyuYong Jung \n" "Language-Team: Korean \n" @@ -239,7 +239,7 @@ msgid "Terminal Inspector" msgstr "터미널 인스펙터" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Ghostty 정보" @@ -311,15 +311,15 @@ msgid "The currently running process in this split will be terminated." msgstr "이 분할에서 현재 실행 중인 프로세스가 종료됩니다." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "명령 완료" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "명령 성공" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "명령 실패" @@ -343,14 +343,14 @@ msgid "Reloaded the configuration" msgstr "설정값을 다시 불러왔습니다" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "클립보드에 복사됨" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "클립보드 지워짐" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty 개발자들" diff --git a/po/lt.po b/po/lt.po --- a/po/lt.po +++ b/po/lt.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-20 12:13+0100\n" "Last-Translator: Tadas Lotuzas \n" "Language-Team: Language LT\n" @@ -15,6 +15,8 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"(n%100<10 || n%100>=20) ? 1 : 2);\n" #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" @@ -240,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Terminalo inspektorius" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Apie Ghostty" @@ -312,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Šiuo metu vykdomas procesas šiame padalijime bus nutrauktas." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Komanda užbaigta" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Komanda sėkminga" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Komanda nepavyko" @@ -344,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Konfigūracija įkelta iš naujo" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Nukopijuota į iškarpinę" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Iškarpinė išvalyta" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty kūrėjai" diff --git a/po/lv.po b/po/lv.po --- a/po/lv.po +++ b/po/lv.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-09 03:24+0200\n" "Last-Translator: Ēriks Remess \n" "Language-Team: Latvian\n" @@ -239,7 +239,7 @@ msgid "Terminal Inspector" msgstr "Termināļa inspektors" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Par Ghostty" @@ -311,15 +311,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Pašlaik palaistais process šajā sadalījumā tiks pārtraukts." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Komanda izpildīta" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Komanda izdevās" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Komanda neizdevās" @@ -343,14 +343,14 @@ msgid "Reloaded the configuration" msgstr "Konfigurācija pārlādēta" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Nokopēts starpliktuvē" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Starpliktuve notīrīta" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty izstrādātāji" diff --git a/po/mk.po b/po/mk.po --- a/po/mk.po +++ b/po/mk.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-12 17:00+0100\n" "Last-Translator: Andrej Daskalov \n" "Language-Team: Macedonian\n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Инспектор на терминал" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "За Ghostty" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Процесот кој моментално се извршува во оваа поделба ќе биде прекинат." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Командата заврши" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Командата успеа" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Командата не успеа" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Конфигурацијата е одново вчитана" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Копирано во привремена меморија" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Исчистена привремена меморија" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Развивачи на Ghostty" diff --git a/po/nb.po b/po/nb.po --- a/po/nb.po +++ b/po/nb.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-12 15:50+0000\n" "Last-Translator: Hanna Rose \n" "Language-Team: Norwegian Bokmal \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Terminalinspektør" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Om Ghostty" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Den kjørende prosessen for denne splitten vil bli avsluttet." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Kommandoen fullført" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Kommandoen lyktes" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Kommandoen mislyktes" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "Konfigurasjonen ble lastet på nytt" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Kopiert til utklippstavlen" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Utklippstavle tømt" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty-utviklere" diff --git a/po/nl.po b/po/nl.po --- a/po/nl.po +++ b/po/nl.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 20:59+0100\n" "Last-Translator: Nico Geesink \n" "Language-Team: Dutch \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Terminalinspecteur" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Over Ghostty" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Alle processen in deze splitsing zullen worden beëindigd." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Commando afgerond" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Commando succesvol afgerond" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Commando onsuccesvol afgerond" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "De configuratie is herladen" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Gekopieerd naar klembord" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Klembord geleegd" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty-ontwikkelaars" diff --git a/po/pl.po b/po/pl.po --- a/po/pl.po +++ b/po/pl.po @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-11 14:12+0100\n" "Last-Translator: trag1c \n" "Language-Team: Polish \n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Inspektor terminala" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "O Ghostty" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Wszyskie trwające procesy w obecnym podziale zostaną zakończone." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Komenda zakończona" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Komenda wykonana pomyślnie" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Komenda nie powiodła się" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "Przeładowano konfigurację" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Skopiowano do schowka" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Wyczyszczono schowek" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Twórcy Ghostty" diff --git a/po/pt_BR.po b/po/pt_BR.po --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 10:50-0300\n" "Last-Translator: Guilherme Tiscoski \n" "Language-Team: Brazilian Portuguese \n" "Language-Team: Russian \n" @@ -244,7 +244,7 @@ msgid "Terminal Inspector" msgstr "Инспектор терминала" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "О Ghostty" @@ -316,15 +316,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Процесс, работающий в этой сплит-области, будет остановлен." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Команда завершилась" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Команда выполнена успешно" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Команда завершилась с ошибкой" @@ -348,14 +348,14 @@ msgid "Reloaded the configuration" msgstr "Конфигурация перезагружена" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Скопировано в буфер обмена" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Буфер обмена очищен" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Разработчики Ghostty" diff --git a/po/tr.po b/po/tr.po --- a/po/tr.po +++ b/po/tr.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-09 22:18+0300\n" "Last-Translator: Emir SARI \n" "Language-Team: Turkish\n" @@ -243,7 +243,7 @@ msgid "Terminal Inspector" msgstr "Uçbirim Denetçisi" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Ghostty Hakkında" @@ -315,15 +315,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Bu bölmedeki şu anda çalışan süreç sonlandırılacaktır." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Komut Bitti" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Komut Başarılı" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Komut Başarısız" @@ -347,14 +347,14 @@ msgid "Reloaded the configuration" msgstr "Yapılandırma yeniden yüklendi" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Panoya kopyalandı" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Pano temizlendi" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty Geliştiricileri" diff --git a/po/uk.po b/po/uk.po --- a/po/uk.po +++ b/po/uk.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 13:14+0100\n" "Last-Translator: Volodymyr Chernetskyi " "<19735328+chernetskyi@users.noreply.github.com>\n" @@ -242,7 +242,7 @@ msgid "Terminal Inspector" msgstr "Інспектор терміналу" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "Про Ghostty" @@ -314,15 +314,15 @@ msgid "The currently running process in this split will be terminated." msgstr "Процес, що виконується в цій панелі, буде завершено." -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "Команда завершилась" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "Команда завершилась успішно" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "Команда завершилась з помилкою" @@ -346,14 +346,14 @@ msgid "Reloaded the configuration" msgstr "Налаштування перезавантажено" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "Скопійовано до буферa обміну" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "Буфер обміну очищено" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Розробники Ghostty" diff --git a/po/vi.po b/po/vi.po new file mode 100644 --- /dev/null +++ b/po/vi.po @@ -0,0 +1,358 @@ +# Vietnamese translations for com.mitchellh.ghostty package. +# Copyright (C) 2026 "Mitchell Hashimoto, Ghostty contributors" +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Anh Thang , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" +"PO-Revision-Date: 2026-03-04 09:32+0700\n" +"Last-Translator: Anh Thang \n" +"Language-Team: Vietnamese \n" +"Language: vi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: dist/linux/ghostty_nautilus.py:53 +msgid "Open in Ghostty" +msgstr "Mở Ghostty" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:12 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:197 +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:201 +msgid "Authorize Clipboard Access" +msgstr "Cho phép Truy cập Bảng tạm" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:17 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:17 +msgid "Deny" +msgstr "Từ chối" + +#: src/apprt/gtk/ui/1.0/clipboard-confirmation-dialog.blp:18 +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:18 +msgid "Allow" +msgstr "Cho phép" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:92 +msgid "Remember choice for this split" +msgstr "Ghi nhớ lựa chọn cho chia màn hình này" + +#: src/apprt/gtk/ui/1.4/clipboard-confirmation-dialog.blp:93 +msgid "Reload configuration to show this prompt again" +msgstr "Tải lại cấu hình để hiển thị lại thông báo này" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:7 +#: src/apprt/gtk/ui/1.5/title-dialog.blp:8 +msgid "Cancel" +msgstr "Hủy" + +#: src/apprt/gtk/ui/1.2/close-confirmation-dialog.blp:8 +#: src/apprt/gtk/ui/1.2/search-overlay.blp:85 +#: src/apprt/gtk/ui/1.3/surface-child-exited.blp:17 +msgid "Close" +msgstr "Đóng" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6 +msgid "Configuration Errors" +msgstr "Lỗi cấu hình" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:7 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "" +"Phát hiện một hoặc nhiều lỗi cấu hình. Vui lòng xem xét các lỗi bên dưới, " +"sau đó tải lại cấu hình hoặc bỏ qua các lỗi này." + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +msgid "Ignore" +msgstr "Bỏ qua" + +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:11 +#: src/apprt/gtk/ui/1.2/surface.blp:371 src/apprt/gtk/ui/1.5/window.blp:300 +msgid "Reload Configuration" +msgstr "Tải lại cấu hình" + +#: src/apprt/gtk/ui/1.2/debug-warning.blp:7 +#: src/apprt/gtk/ui/1.3/debug-warning.blp:6 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Bạn đang chạy bản build thử nghiệm (debug) của Ghostty! Hiệu năng sẽ bị " +"giảm." + +#: src/apprt/gtk/ui/1.5/inspector-window.blp:5 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Bộ kiểm tra Terminal" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:29 +msgid "Find…" +msgstr "Tìm kiếm…" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:64 +msgid "Previous Match" +msgstr "Kết quả trước" + +#: src/apprt/gtk/ui/1.2/search-overlay.blp:74 +msgid "Next Match" +msgstr "Kết quả tiếp theo" + +#: src/apprt/gtk/ui/1.2/surface.blp:6 +msgid "Oh, no." +msgstr "Ôi hỏng rồi." + +#: src/apprt/gtk/ui/1.2/surface.blp:7 +msgid "Unable to acquire an OpenGL context for rendering." +msgstr "Không thể lấy ngữ cảnh OpenGL để kết xuất đồ họa." + +#: src/apprt/gtk/ui/1.2/surface.blp:97 +msgid "" +"This terminal is in read-only mode. You can still view, select, and scroll " +"through the content, but no input events will be sent to the running " +"application." +msgstr "" +"Terminal này đang ở chế độ chỉ đọc. Bạn vẫn có thể xem, chọn và cuộn nội " +"dung, nhưng các sự kiện nhập liệu sẽ không được gửi đến ứng dụng." + +#: src/apprt/gtk/ui/1.2/surface.blp:107 +msgid "Read-only" +msgstr "Chỉ đọc" + +#: src/apprt/gtk/ui/1.2/surface.blp:260 src/apprt/gtk/ui/1.5/window.blp:200 +msgid "Copy" +msgstr "Sao chép" + +#: src/apprt/gtk/ui/1.2/surface.blp:265 src/apprt/gtk/ui/1.5/window.blp:205 +msgid "Paste" +msgstr "Dán" + +#: src/apprt/gtk/ui/1.2/surface.blp:270 +msgid "Notify on Next Command Finish" +msgstr "Thông báo khi lệnh tiếp theo kết thúc" + +#: src/apprt/gtk/ui/1.2/surface.blp:277 src/apprt/gtk/ui/1.5/window.blp:273 +msgid "Clear" +msgstr "Xóa" + +#: src/apprt/gtk/ui/1.2/surface.blp:282 src/apprt/gtk/ui/1.5/window.blp:278 +msgid "Reset" +msgstr "Đặt lại" + +#: src/apprt/gtk/ui/1.2/surface.blp:289 src/apprt/gtk/ui/1.5/window.blp:242 +msgid "Split" +msgstr "Chia màn hình" + +#: src/apprt/gtk/ui/1.2/surface.blp:292 src/apprt/gtk/ui/1.5/window.blp:245 +msgid "Change Title…" +msgstr "Đổi tiêu đề…" + +#: src/apprt/gtk/ui/1.2/surface.blp:297 src/apprt/gtk/ui/1.5/window.blp:177 +#: src/apprt/gtk/ui/1.5/window.blp:250 +msgid "Split Up" +msgstr "Chia lên trên" + +#: src/apprt/gtk/ui/1.2/surface.blp:303 src/apprt/gtk/ui/1.5/window.blp:182 +#: src/apprt/gtk/ui/1.5/window.blp:255 +msgid "Split Down" +msgstr "Chia xuống dưới" + +#: src/apprt/gtk/ui/1.2/surface.blp:309 src/apprt/gtk/ui/1.5/window.blp:187 +#: src/apprt/gtk/ui/1.5/window.blp:260 +msgid "Split Left" +msgstr "Chia sang trái" + +#: src/apprt/gtk/ui/1.2/surface.blp:315 src/apprt/gtk/ui/1.5/window.blp:192 +#: src/apprt/gtk/ui/1.5/window.blp:265 +msgid "Split Right" +msgstr "Chia sang phải" + +#: src/apprt/gtk/ui/1.2/surface.blp:321 +msgid "Close Split" +msgstr "" + +#: src/apprt/gtk/ui/1.2/surface.blp:327 +msgid "Tab" +msgstr "Tab" + +#: src/apprt/gtk/ui/1.2/surface.blp:330 src/apprt/gtk/ui/1.5/window.blp:224 +#: src/apprt/gtk/ui/1.5/window.blp:320 +msgid "Change Tab Title…" +msgstr "Đổi tiêu đề Tab…" + +#: src/apprt/gtk/ui/1.2/surface.blp:335 src/apprt/gtk/ui/1.5/window.blp:57 +#: src/apprt/gtk/ui/1.5/window.blp:107 src/apprt/gtk/ui/1.5/window.blp:229 +msgid "New Tab" +msgstr "Tab mới" + +#: src/apprt/gtk/ui/1.2/surface.blp:340 src/apprt/gtk/ui/1.5/window.blp:234 +msgid "Close Tab" +msgstr "Đóng Tab" + +#: src/apprt/gtk/ui/1.2/surface.blp:347 +msgid "Window" +msgstr "Cửa sổ" + +#: src/apprt/gtk/ui/1.2/surface.blp:350 src/apprt/gtk/ui/1.5/window.blp:212 +msgid "New Window" +msgstr "Cửa sổ mới" + +#: src/apprt/gtk/ui/1.2/surface.blp:355 src/apprt/gtk/ui/1.5/window.blp:217 +msgid "Close Window" +msgstr "Đóng cửa sổ" + +#: src/apprt/gtk/ui/1.2/surface.blp:363 +msgid "Config" +msgstr "Cấu hình" + +#: src/apprt/gtk/ui/1.2/surface.blp:366 src/apprt/gtk/ui/1.5/window.blp:295 +msgid "Open Configuration" +msgstr "Mở tệp cấu hình" + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:5 +msgid "Leave blank to restore the default title." +msgstr "Để trống để khôi phục tiêu đề mặc định." + +#: src/apprt/gtk/ui/1.5/title-dialog.blp:9 +msgid "OK" +msgstr "Đồng ý" + +#: src/apprt/gtk/ui/1.5/window.blp:58 src/apprt/gtk/ui/1.5/window.blp:108 +msgid "New Split" +msgstr "Chia màn hình mới" + +#: src/apprt/gtk/ui/1.5/window.blp:68 src/apprt/gtk/ui/1.5/window.blp:126 +msgid "View Open Tabs" +msgstr "Xem các Tab đang mở" + +#: src/apprt/gtk/ui/1.5/window.blp:78 src/apprt/gtk/ui/1.5/window.blp:140 +msgid "Main Menu" +msgstr "Trình đơn chính" + +#: src/apprt/gtk/ui/1.5/window.blp:285 +msgid "Command Palette" +msgstr "Bảng lệnh" + +#: src/apprt/gtk/ui/1.5/window.blp:290 +msgid "Terminal Inspector" +msgstr "Bộ kiểm tra Terminal" + +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 +msgid "About Ghostty" +msgstr "Về Ghostty" + +#: src/apprt/gtk/ui/1.5/window.blp:312 +msgid "Quit" +msgstr "Thoát" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:17 +msgid "Execute a command…" +msgstr "Chạy một lệnh…" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:198 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Một ứng dụng đang cố gắng ghi vào bảng tạm. Nội dung hiện tại của bảng tạm " +"được hiển thị bên dưới." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:202 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "" +"Một ứng dụng đang cố gắng đọc từ bảng tạm. Nội dung hiện tại của bảng tạm " +"được hiển thị bên dưới." + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:205 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Cảnh báo: Thao tác Dán có thể không an toàn" + +#: src/apprt/gtk/class/clipboard_confirmation_dialog.zig:206 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "" +"Dán văn bản này vào terminal có thể nguy hiểm vì có vẻ như một số lệnh sẽ bị " +"thực thi." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:184 +msgid "Quit Ghostty?" +msgstr "Thoát Ghostty?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:185 +msgid "Close Tab?" +msgstr "Đóng Tab?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:186 +msgid "Close Window?" +msgstr "Đóng cửa sổ?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:187 +msgid "Close Split?" +msgstr "Đóng phần chia màn hình?" + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:193 +msgid "All terminal sessions will be terminated." +msgstr "Tất cả các phiên làm việc terminal sẽ bị chấm dứt." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:194 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Tất cả các phiên làm việc terminal trong tab này sẽ bị chấm dứt." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:195 +msgid "All terminal sessions in this window will be terminated." +msgstr "Tất cả các phiên làm việc terminal trong cửa sổ này sẽ bị chấm dứt." + +#: src/apprt/gtk/class/close_confirmation_dialog.zig:196 +msgid "The currently running process in this split will be terminated." +msgstr "Tiến trình đang chạy trong phần chia này sẽ bị chấm dứt." + +#: src/apprt/gtk/class/surface.zig:1141 +msgid "Command Finished" +msgstr "Lệnh đã kết thúc" + +#: src/apprt/gtk/class/surface.zig:1142 +msgid "Command Succeeded" +msgstr "Lệnh thành công" + +#: src/apprt/gtk/class/surface.zig:1143 +msgid "Command Failed" +msgstr "Lệnh thất bại" + +#: src/apprt/gtk/class/surface_child_exited.zig:109 +msgid "Command succeeded" +msgstr "Lệnh thành công" + +#: src/apprt/gtk/class/surface_child_exited.zig:113 +msgid "Command failed" +msgstr "Lệnh thất bại" + +#: src/apprt/gtk/class/title_dialog.zig:225 +msgid "Change Terminal Title" +msgstr "Đổi tiêu đề Terminal" + +#: src/apprt/gtk/class/title_dialog.zig:226 +msgid "Change Tab Title" +msgstr "Đổi tiêu đề Tab" + +#: src/apprt/gtk/class/window.zig:1067 +msgid "Reloaded the configuration" +msgstr "Đã tải lại cấu hình" + +#: src/apprt/gtk/class/window.zig:1611 +msgid "Copied to clipboard" +msgstr "Đã sao chép vào bảng tạm" + +#: src/apprt/gtk/class/window.zig:1613 +msgid "Cleared clipboard" +msgstr "Đã xóa sạch bảng tạm" + +#: src/apprt/gtk/class/window.zig:1753 +msgid "Ghostty Developers" +msgstr "Các nhà phát triển Ghostty" diff --git a/po/zh_CN.po b/po/zh_CN.po --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-12 01:56+0800\n" "Last-Translator: Leah \n" "Language-Team: Chinese (simplified) \n" @@ -16,6 +16,7 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" @@ -238,7 +239,7 @@ msgid "Terminal Inspector" msgstr "终端调试器" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "关于 Ghostty" @@ -304,15 +305,15 @@ msgid "The currently running process in this split will be terminated." msgstr "分屏内正在运行中的进程将被终止。" -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "命令已完成" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "命令执行成功" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "命令执行失败" @@ -336,14 +337,14 @@ msgid "Reloaded the configuration" msgstr "已重新加载配置" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "已复制至剪贴板" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "已清空剪贴板" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty 开发团队" diff --git a/po/zh_TW.po b/po/zh_TW.po --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2026-03-04 21:13-0800\n" +"POT-Creation-Date: 2026-03-25 16:24-0700\n" "PO-Revision-Date: 2026-02-18 13:58+0800\n" "Last-Translator: Yi-Jyun Pan \n" "Language-Team: Chinese (traditional)\n" @@ -15,6 +15,7 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" #: dist/linux/ghostty_nautilus.py:53 msgid "Open in Ghostty" @@ -236,7 +237,7 @@ msgid "Terminal Inspector" msgstr "終端機檢查工具" -#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1787 +#: src/apprt/gtk/ui/1.5/window.blp:307 src/apprt/gtk/class/window.zig:1772 msgid "About Ghostty" msgstr "關於 Ghostty" @@ -302,15 +303,15 @@ msgid "The currently running process in this split will be terminated." msgstr "此窗格中目前執行的處理程序將被終止。" -#: src/apprt/gtk/class/surface.zig:1131 +#: src/apprt/gtk/class/surface.zig:1141 msgid "Command Finished" msgstr "命令執行完成" -#: src/apprt/gtk/class/surface.zig:1132 +#: src/apprt/gtk/class/surface.zig:1142 msgid "Command Succeeded" msgstr "命令執行成功" -#: src/apprt/gtk/class/surface.zig:1133 +#: src/apprt/gtk/class/surface.zig:1143 msgid "Command Failed" msgstr "命令執行失敗" @@ -334,14 +335,14 @@ msgid "Reloaded the configuration" msgstr "已重新載入設定" -#: src/apprt/gtk/class/window.zig:1626 +#: src/apprt/gtk/class/window.zig:1611 msgid "Copied to clipboard" msgstr "已複製到剪貼簿" -#: src/apprt/gtk/class/window.zig:1628 +#: src/apprt/gtk/class/window.zig:1613 msgid "Cleared clipboard" msgstr "已清除剪貼簿" -#: src/apprt/gtk/class/window.zig:1768 +#: src/apprt/gtk/class/window.zig:1753 msgid "Ghostty Developers" msgstr "Ghostty 開發者" diff --git a/src/Command.zig b/src/Command.zig --- a/src/Command.zig +++ b/src/Command.zig @@ -725,6 +725,11 @@ .path = "C:\\Windows\\System32\\whoami.exe", .args = &.{"C:\\Windows\\System32\\whoami.exe"}, .stdout = stdout, + .os_pre_exec = null, + .rt_pre_exec = null, + .rt_post_fork = null, + .rt_pre_exec_info = undefined, + .rt_post_fork_info = undefined, } else .{ .path = "/bin/sh", .args = &.{ "/bin/sh", "-c", "echo hello" }, diff --git a/src/Surface.zig b/src/Surface.zig --- a/src/Surface.zig +++ b/src/Surface.zig @@ -36,6 +36,7 @@ const internal_os = @import("os/main.zig"); const inspectorpkg = @import("inspector/main.zig"); const SurfaceMouse = @import("surface_mouse.zig"); +const ProcessInfo = @import("pty.zig").ProcessInfo; const log = std.log.scoped(.surface); @@ -324,7 +325,7 @@ window_padding_bottom: u32, window_padding_left: u32, window_padding_right: u32, - window_padding_balance: bool, + window_padding_balance: configpkg.Config.WindowPaddingBalance, window_height: u32, window_width: u32, title: ?[:0]const u8, @@ -536,8 +537,8 @@ x_dpi, y_dpi, ); - if (derived_config.window_padding_balance) { - size.balancePadding(explicit); + if (derived_config.window_padding_balance != .false) { + size.balancePadding(explicit, derived_config.window_padding_balance); } else { size.padding = explicit; } @@ -639,7 +640,7 @@ .shell_integration = config.@"shell-integration", .shell_integration_features = config.@"shell-integration-features", .cursor_blink = config.@"cursor-style-blink", - .working_directory = config.@"working-directory", + .working_directory = if (config.@"working-directory") |wd| wd.value() else null, .resources_dir = global_state.resources_dir.host(), .term = config.term, .rt_pre_exec_info = .init(config), @@ -2462,11 +2463,11 @@ /// Recalculate the balanced padding if needed. fn balancePaddingIfNeeded(self: *Surface) void { - if (!self.config.window_padding_balance) return; + if (self.config.window_padding_balance == .false) return; const content_scale = try self.rt_surface.getContentScale(); const x_dpi = content_scale.x * font.face.default_dpi; const y_dpi = content_scale.y * font.face.default_dpi; - self.size.balancePadding(self.config.scaledPadding(x_dpi, y_dpi)); + self.size.balancePadding(self.config.scaledPadding(x_dpi, y_dpi), self.config.window_padding_balance); } /// Called to set the preedit state for character input. Preedit is used @@ -3518,7 +3519,7 @@ if (self.isMouseReporting()) { for (0..@abs(y.delta)) |_| { const pos = try self.rt_surface.getCursorPos(); - try self.mouseReport(switch (y.direction()) { + self.mouseReport(switch (y.direction()) { .up_right => .four, .down_left => .five, }, .press, self.mouse.mods, pos); @@ -3526,7 +3527,7 @@ for (0..@abs(x.delta)) |_| { const pos = try self.rt_surface.getCursorPos(); - try self.mouseReport(switch (x.direction()) { + self.mouseReport(switch (x.direction()) { .up_right => .six, .down_left => .seven, }, .press, self.mouse.mods, pos); @@ -3576,7 +3577,7 @@ // Update our padding which is dependent on DPI. We only do this for // unbalanced padding since balanced padding is not dependent on DPI. - if (!self.config.window_padding_balance) { + if (self.config.window_padding_balance == .false) { self.size.padding = self.config.scaledPadding(x_dpi, y_dpi); } @@ -3584,9 +3585,6 @@ // pixel-level changes to the renderer and viewport. try self.resize(self.size.screen); } - -/// The type of action to report for a mouse event. -const MouseReportAction = enum { press, release, motion }; /// Returns true if mouse reporting is enabled both in the config and /// the terminal state. @@ -3598,228 +3596,65 @@ fn mouseReport( self: *Surface, button: ?input.MouseButton, - action: MouseReportAction, + action: input.MouseAction, mods: input.Mods, pos: apprt.CursorPos, -) !void { +) void { // Mouse reporting must be enabled by both config and terminal state assert(self.config.mouse_reporting); assert(self.io.terminal.flags.mouse_event != .none); - // Depending on the event, we may do nothing at all. - switch (self.io.terminal.flags.mouse_event) { - .none => unreachable, // checked by assert above + // Build our encoding options. + const encoding_opts: input.mouse_encode.Options = opts: { + // Terminal and size state. + var opts: input.mouse_encode.Options = .fromTerminal( + &self.io.terminal, + self.size, + ); - // X10 only reports clicks with mouse button 1, 2, 3. We verify - // the button later. - .x10 => if (action != .press or - button == null or - !(button.? == .left or - button.? == .right or - button.? == .middle)) return, - - // Doesn't report motion - .normal => if (action == .motion) return, - - // Button must be pressed - .button => if (button == null) return, - - // Everything - .any => {}, - } - - // Handle scenarios where the mouse position is outside the viewport. - // We always report release events no matter where they happen. - if (action != .release) { - const pos_out_viewport = pos_out_viewport: { - const max_x: f32 = @floatFromInt(self.size.screen.width); - const max_y: f32 = @floatFromInt(self.size.screen.height); - break :pos_out_viewport pos.x < 0 or pos.y < 0 or - pos.x > max_x or pos.y > max_y; - }; - if (pos_out_viewport) outside_viewport: { - // If we don't have a motion-tracking event mode, do nothing. - if (!self.io.terminal.flags.mouse_event.motion()) return; - - // If any button is pressed, we still do the report. Otherwise, - // we do not do the report. + // Whether any button is pressed at all. + opts.any_button_pressed = pressed: { for (self.mouse.click_state) |state| { - if (state != .release) break :outside_viewport; + if (state != .release) break :pressed true; } - return; - } - } + break :pressed false; + }; - // This format reports X/Y - const viewport_point = self.posToViewport(pos.x, pos.y); + // Keep track of our last reported viewport cell for event + // deduplication. + opts.last_cell = &self.mouse.event_point; - // Record our new point. We only want to send a mouse event if the - // cell changed, unless we're tracking raw pixels. - if (action == .motion and self.io.terminal.flags.mouse_format != .sgr_pixels) { - if (self.mouse.event_point) |last_point| { - if (last_point.eql(viewport_point)) return; - } - } - self.mouse.event_point = viewport_point; - - // Get the code we'll actually write - const button_code: u8 = code: { - var acc: u8 = 0; - - // Determine our initial button value - if (button == null) { - // Null button means motion without a button pressed - acc = 3; - } else if (action == .release and - self.io.terminal.flags.mouse_format != .sgr and - self.io.terminal.flags.mouse_format != .sgr_pixels) - { - // Release is 3. It is NOT 3 in SGR mode because SGR can tell - // the application what button was released. - acc = 3; - } else { - acc = switch (button.?) { - .left => 0, - .middle => 1, - .right => 2, - .four => 64, - .five => 65, - .six => 66, - .seven => 67, - .eight => 128, - .nine => 129, - else => return, // unsupported - }; - } - - // X10 doesn't have modifiers - if (self.io.terminal.flags.mouse_event != .x10) { - if (mods.shift) acc += 4; - if (mods.alt) acc += 8; - if (mods.ctrl) acc += 16; - } - - // Motion adds another bit - if (action == .motion) acc += 32; - - break :code acc; + break :opts opts; }; - switch (self.io.terminal.flags.mouse_format) { - .x10 => { - if (viewport_point.x > 222 or viewport_point.y > 222) { - log.info("X10 mouse format can only encode X/Y up to 223", .{}); - return; - } - - // + 1 below is because our x/y is 0-indexed and the protocol wants 1 - var data: termio.Message.WriteReq.Small.Array = undefined; - assert(data.len >= 6); - data[0] = '\x1b'; - data[1] = '['; - data[2] = 'M'; - data[3] = 32 + button_code; - data[4] = 32 + @as(u8, @intCast(viewport_point.x)) + 1; - data[5] = 32 + @as(u8, @intCast(viewport_point.y)) + 1; - - // Ask our IO thread to write the data - self.queueIo(.{ .write_small = .{ - .data = data, - .len = 6, - } }, .locked); + var data: termio.Message.WriteReq.Small.Array = undefined; + var writer: std.Io.Writer = .fixed(&data); + input.mouse_encode.encode(&writer, .{ + .button = button, + .action = action, + .mods = mods, + .pos = .{ + .x = pos.x, + .y = pos.y, }, - - .utf8 => { - // Maximum of 12 because at most we have 2 fully UTF-8 encoded chars - var data: termio.Message.WriteReq.Small.Array = undefined; - assert(data.len >= 12); - data[0] = '\x1b'; - data[1] = '['; - data[2] = 'M'; - - // The button code will always fit in a single u8 - data[3] = 32 + button_code; - - // UTF-8 encode the x/y - var i: usize = 4; - i += try std.unicode.utf8Encode(@intCast(32 + viewport_point.x + 1), data[i..]); - i += try std.unicode.utf8Encode(@intCast(32 + viewport_point.y + 1), data[i..]); - - // Ask our IO thread to write the data - self.queueIo(.{ .write_small = .{ - .data = data, - .len = @intCast(i), - } }, .locked); + }, encoding_opts) catch |err| switch (err) { + error.WriteFailed => { + // This should never happen since mouse events should never + // be able to overflow the size of our small array. But if it + // does, let's log it and return. No need to crash upstreams. + // In the future we may want to fall back to allocation. + log.warn("failed to encode mouse event err={}", .{err}); + return; }, + }; + const written = writer.buffered(); + if (written.len == 0) return; - .sgr => { - // Final character to send in the CSI - const final: u8 = if (action == .release) 'm' else 'M'; - - // Response always is at least 4 chars, so this leaves the - // remainder for numbers which are very large... - var data: termio.Message.WriteReq.Small.Array = undefined; - const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{ - button_code, - viewport_point.x + 1, - viewport_point.y + 1, - final, - }); - - // Ask our IO thread to write the data - self.queueIo(.{ .write_small = .{ - .data = data, - .len = @intCast(resp.len), - } }, .locked); - }, - - .urxvt => { - // Response always is at least 4 chars, so this leaves the - // remainder for numbers which are very large... - var data: termio.Message.WriteReq.Small.Array = undefined; - const resp = try std.fmt.bufPrint(&data, "\x1B[{d};{d};{d}M", .{ - 32 + button_code, - viewport_point.x + 1, - viewport_point.y + 1, - }); - - // Ask our IO thread to write the data - self.queueIo(.{ .write_small = .{ - .data = data, - .len = @intCast(resp.len), - } }, .locked); - }, - - .sgr_pixels => { - // Final character to send in the CSI - const final: u8 = if (action == .release) 'm' else 'M'; - - // The position has to be adjusted to the terminal space. - const coord: rendererpkg.Coordinate.Terminal = (rendererpkg.Coordinate{ - .surface = .{ - .x = pos.x, - .y = pos.y, - }, - }).convert(.terminal, self.size).terminal; - - // Response always is at least 4 chars, so this leaves the - // remainder for numbers which are very large... - var data: termio.Message.WriteReq.Small.Array = undefined; - const resp = try std.fmt.bufPrint(&data, "\x1B[<{d};{d};{d}{c}", .{ - button_code, - @as(i32, @intFromFloat(@round(coord.x))), - @as(i32, @intFromFloat(@round(coord.y))), - final, - }); - - // Ask our IO thread to write the data - self.queueIo(.{ .write_small = .{ - .data = data, - .len = @intCast(resp.len), - } }, .locked); - }, - } + self.queueIo(.{ .write_small = .{ + .data = data, + .len = @intCast(written.len), + } }, .locked); } /// Returns true if the shift modifier is allowed to be captured by modifier @@ -4003,12 +3838,12 @@ const pos = try self.rt_surface.getCursorPos(); - const report_action: MouseReportAction = switch (action) { + const report_action: input.MouseAction = switch (action) { .press => .press, .release => .release, }; - try self.mouseReport( + self.mouseReport( button, report_action, self.mouse.mods, @@ -4740,7 +4575,7 @@ break :button @enumFromInt(i); } else null; - try self.mouseReport(button, .motion, self.mouse.mods, pos); + self.mouseReport(button, .motion, self.mouse.mods, pos); // If we're doing mouse motion tracking, we do not support text // selection. @@ -4968,14 +4803,14 @@ break :ebs drag_pin.before(click_pin); }; - // Whether or not the the click pin cell + // Whether or not the click pin cell // should be included in the selection. const include_click_cell = if (end_before_start) click_x_frac >= threshold_point else click_x_frac < threshold_point; - // Whether or not the the drag pin cell + // Whether or not the drag pin cell // should be included in the selection. const include_drag_cell = if (end_before_start) drag_x_frac < threshold_point @@ -5481,6 +5316,26 @@ .prompt_title, .tab, ), + + .set_surface_title => |v| { + const title = try self.alloc.dupeZ(u8, v); + defer self.alloc.free(title); + return try self.rt_app.performAction( + .{ .surface = self }, + .set_title, + .{ .title = title }, + ); + }, + + .set_tab_title => |v| { + const title = try self.alloc.dupeZ(u8, v); + defer self.alloc.free(title); + return try self.rt_app.performAction( + .{ .surface = self }, + .set_tab_title, + .{ .title = title }, + ); + }, .clear_screen => { // This is a duplicate of some of the logic in termio.clearScreen @@ -6486,6 +6341,13 @@ size, ), ); +} + +/// Get information about the process(es) running within the surface. Returns +/// `null` if there was an error getting the information or the information is +/// not available on a particular platform. +pub fn getProcessInfo(self: *Surface, comptime info: ProcessInfo) ?ProcessInfo.Type(info) { + return self.io.getProcessInfo(info); } test "Surface: selection logic" { diff --git a/src/config.zig b/src/config.zig --- a/src/config.zig +++ b/src/config.zig @@ -2,6 +2,7 @@ const file_load = @import("config/file_load.zig"); const formatter = @import("config/formatter.zig"); +const formatter_file = @import("config/formatter_file.zig"); pub const Config = @import("config/Config.zig"); pub const conditional = @import("config/conditional.zig"); pub const io = @import("config/io.zig"); @@ -10,7 +11,7 @@ pub const url = @import("config/url.zig"); pub const ConditionalState = conditional.State; -pub const FileFormatter = formatter.FileFormatter; +pub const FileFormatter = formatter_file.FileFormatter; pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; pub const preferredDefaultFilePath = file_load.preferredDefaultFilePath; @@ -44,6 +45,7 @@ pub const BackgroundImagePosition = Config.BackgroundImagePosition; pub const BackgroundImageFit = Config.BackgroundImageFit; pub const LinkPreviews = Config.LinkPreviews; +pub const WorkingDirectory = Config.WorkingDirectory; // Alternate APIs pub const CApi = @import("config/CApi.zig"); diff --git a/src/helpgen.zig b/src/helpgen.zig --- a/src/helpgen.zig +++ b/src/helpgen.zig @@ -12,7 +12,7 @@ const alloc = gpa.allocator(); var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout = std.fs.File.stdout().writerStreaming(&buf); const writer = &stdout.interface; try writer.writeAll( \\// THIS FILE IS AUTO GENERATED diff --git a/src/input.zig b/src/input.zig --- a/src/input.zig +++ b/src/input.zig @@ -12,6 +12,7 @@ pub const keycodes = @import("input/keycodes.zig"); pub const key_encode = @import("input/key_encode.zig"); pub const kitty = @import("input/kitty.zig"); +pub const mouse_encode = @import("input/mouse_encode.zig"); pub const paste = @import("input/paste.zig"); pub const ctrlOrSuper = key.ctrlOrSuper; @@ -25,6 +26,7 @@ pub const KeyRemapSet = key_mods.RemapSet; pub const InspectorMode = Binding.Action.InspectorMode; pub const Mods = key_mods.Mods; +pub const MouseAction = mouse.Action; pub const MouseButton = mouse.Button; pub const MouseButtonState = mouse.ButtonState; pub const MousePressureStage = mouse.PressureStage; diff --git a/src/lib_vt.zig b/src/lib_vt.zig --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -53,14 +53,14 @@ pub const Screen = terminal.Screen; pub const ScreenSet = terminal.ScreenSet; pub const Selection = terminal.Selection; +pub const size_report = terminal.size_report; pub const SizeReportStyle = terminal.SizeReportStyle; pub const StringMap = terminal.StringMap; pub const Style = terminal.Style; pub const Terminal = terminal.Terminal; +pub const TerminalStream = terminal.TerminalStream; pub const Stream = terminal.Stream; pub const StreamAction = terminal.StreamAction; -pub const ReadonlyStream = terminal.ReadonlyStream; -pub const ReadonlyHandler = terminal.ReadonlyHandler; pub const Cursor = Screen.Cursor; pub const CursorStyle = Screen.CursorStyle; pub const CursorStyleReq = terminal.CursorStyle; @@ -81,9 +81,16 @@ // We have to be careful to only import targeted files within // the input package because the full package brings in too many // other dependencies. + const focus = terminal.focus; const paste = @import("input/paste.zig"); const key = @import("input/key.zig"); const key_encode = @import("input/key_encode.zig"); + const mouse_encode = @import("input/mouse_encode.zig"); + + // Focus-related APIs + pub const max_focus_encode_size = focus.max_encode_size; + pub const FocusEvent = focus.Event; + pub const encodeFocus = focus.encode; // Paste-related APIs pub const PasteError = paste.Error; @@ -98,6 +105,13 @@ pub const KeyMods = key.Mods; pub const KeyEncodeOptions = key_encode.Options; pub const encodeKey = key_encode.encode; + + // Mouse encoding + pub const MouseAction = @import("input/mouse.zig").Action; + pub const MouseButton = @import("input/mouse.zig").Button; + pub const MouseEncodeOptions = mouse_encode.Options; + pub const MouseEncodeEvent = mouse_encode.Event; + pub const encodeMouse = mouse_encode.encode; }; comptime { @@ -124,7 +138,25 @@ @export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" }); @export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" }); @export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" }); + @export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" }); @export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" }); + @export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" }); + @export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" }); + @export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" }); + @export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" }); + @export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" }); + @export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" }); + @export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" }); + @export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" }); + @export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" }); + @export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" }); + @export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" }); + @export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" }); + @export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" }); + @export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" }); + @export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" }); + @export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" }); + @export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" }); @export(&c.osc_new, .{ .name = "ghostty_osc_new" }); @export(&c.osc_free, .{ .name = "ghostty_osc_free" }); @export(&c.osc_next, .{ .name = "ghostty_osc_next" }); @@ -132,7 +164,14 @@ @export(&c.osc_end, .{ .name = "ghostty_osc_end" }); @export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" }); @export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" }); + @export(&c.focus_encode, .{ .name = "ghostty_focus_encode" }); + @export(&c.mode_report_encode, .{ .name = "ghostty_mode_report_encode" }); @export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" }); + @export(&c.size_report_encode, .{ .name = "ghostty_size_report_encode" }); + @export(&c.style_default, .{ .name = "ghostty_style_default" }); + @export(&c.style_is_default, .{ .name = "ghostty_style_is_default" }); + @export(&c.cell_get, .{ .name = "ghostty_cell_get" }); + @export(&c.row_get, .{ .name = "ghostty_row_get" }); @export(&c.color_rgb_get, .{ .name = "ghostty_color_rgb_get" }); @export(&c.sgr_new, .{ .name = "ghostty_sgr_new" }); @export(&c.sgr_free, .{ .name = "ghostty_sgr_free" }); @@ -143,6 +182,44 @@ @export(&c.sgr_unknown_partial, .{ .name = "ghostty_sgr_unknown_partial" }); @export(&c.sgr_attribute_tag, .{ .name = "ghostty_sgr_attribute_tag" }); @export(&c.sgr_attribute_value, .{ .name = "ghostty_sgr_attribute_value" }); + @export(&c.formatter_terminal_new, .{ .name = "ghostty_formatter_terminal_new" }); + @export(&c.formatter_format_buf, .{ .name = "ghostty_formatter_format_buf" }); + @export(&c.formatter_format_alloc, .{ .name = "ghostty_formatter_format_alloc" }); + @export(&c.formatter_free, .{ .name = "ghostty_formatter_free" }); + @export(&c.render_state_new, .{ .name = "ghostty_render_state_new" }); + @export(&c.render_state_update, .{ .name = "ghostty_render_state_update" }); + @export(&c.render_state_get, .{ .name = "ghostty_render_state_get" }); + @export(&c.render_state_set, .{ .name = "ghostty_render_state_set" }); + @export(&c.render_state_colors_get, .{ .name = "ghostty_render_state_colors_get" }); + @export(&c.render_state_row_iterator_new, .{ .name = "ghostty_render_state_row_iterator_new" }); + @export(&c.render_state_row_iterator_next, .{ .name = "ghostty_render_state_row_iterator_next" }); + @export(&c.render_state_row_get, .{ .name = "ghostty_render_state_row_get" }); + @export(&c.render_state_row_set, .{ .name = "ghostty_render_state_row_set" }); + @export(&c.render_state_row_iterator_free, .{ .name = "ghostty_render_state_row_iterator_free" }); + @export(&c.render_state_row_cells_new, .{ .name = "ghostty_render_state_row_cells_new" }); + @export(&c.render_state_row_cells_next, .{ .name = "ghostty_render_state_row_cells_next" }); + @export(&c.render_state_row_cells_select, .{ .name = "ghostty_render_state_row_cells_select" }); + @export(&c.render_state_row_cells_get, .{ .name = "ghostty_render_state_row_cells_get" }); + @export(&c.render_state_row_cells_free, .{ .name = "ghostty_render_state_row_cells_free" }); + @export(&c.render_state_free, .{ .name = "ghostty_render_state_free" }); + @export(&c.terminal_new, .{ .name = "ghostty_terminal_new" }); + @export(&c.terminal_free, .{ .name = "ghostty_terminal_free" }); + @export(&c.terminal_reset, .{ .name = "ghostty_terminal_reset" }); + @export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" }); + @export(&c.terminal_set, .{ .name = "ghostty_terminal_set" }); + @export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" }); + @export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" }); + @export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" }); + @export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" }); + @export(&c.terminal_get, .{ .name = "ghostty_terminal_get" }); + @export(&c.terminal_grid_ref, .{ .name = "ghostty_terminal_grid_ref" }); + @export(&c.grid_ref_cell, .{ .name = "ghostty_grid_ref_cell" }); + @export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" }); + @export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" }); + @export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" }); + @export(&c.build_info, .{ .name = "ghostty_build_info" }); + @export(&c.alloc_alloc, .{ .name = "ghostty_alloc" }); + @export(&c.alloc_free, .{ .name = "ghostty_free" }); // On Wasm we need to export our allocator convenience functions. if (builtin.target.cpu.arch.isWasm()) { diff --git a/src/main_build_data.zig b/src/main_build_data.zig --- a/src/main_build_data.zig +++ b/src/main_build_data.zig @@ -34,7 +34,7 @@ // Our output always goes to stdout. var buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer); const writer = &stdout_writer.interface; switch (action) { .bash => try writer.writeAll(@import("extra/bash.zig").completions), diff --git a/src/pty.c b/src/pty.c new file mode 100644 --- /dev/null +++ b/src/pty.c @@ -0,0 +1,40 @@ +#if defined(__FreeBSD__) + + #include // ioctl and constants + #include // openpty + #include // ptsname_r + #include // tcgetpgrp + +#elif defined(__linux__) + + #define _GNU_SOURCE // ptsname_r + #include // openpty + #include // ptsname_r + #include // ioctl and constants + #include // tcgetpgrp, setsid + +#elif defined(__APPLE__) + + #include // ioctl and constants + #include // ioctl and constants for TIOCPTYGNAME + #include + #include // tcgetpgrp + #include // openpty + + #ifndef tiocsctty + #define tiocsctty 536900705 + #endif + + #ifndef tiocswinsz + #define tiocswinsz 2148037735 + #endif + + #ifndef tiocgwinsz + #define tiocgwinsz 1074295912 + #endif + +#else + + #error "unsupported platform" + +#endif diff --git a/src/pty.zig b/src/pty.zig --- a/src/pty.zig +++ b/src/pty.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); const windows = @import("os/main.zig").windows; const posix = std.posix; +const assert = @import("quirks.zig").inlineAssert; const log = std.log.scoped(.pty); @@ -33,6 +34,21 @@ /// ECHO on POSIX echo: bool = true, +}; + +pub const ProcessInfo = enum { + /// The PID of the process that controls the PTY. + foreground_pid, + /// Gets the name of the slave PTY. Returned name points to an internal buffer + /// so it should not be modified or freed. + tty_name, + + pub fn Type(comptime info: ProcessInfo) type { + return switch (info) { + .foreground_pid => u64, + .tty_name => [:0]const u8, + }; + } }; // A pty implementation that does nothing. @@ -78,36 +94,24 @@ pub fn childPreExec(self: Pty) ChildPreExecError!void { _ = self; } + + /// Get information about the process(es) attached to the PTY. Returns + /// `null` if there was an error getting the information or the information + /// is not available on a particular platform. + pub fn getProcessInfo(_: *Pty, comptime info: ProcessInfo) ?ProcessInfo.Type(info) { + return null; + } }; -/// Linux PTY creation and management. This is just a thin layer on top -/// of Linux syscalls. The caller is responsible for detail-oriented handling +/// Posix PTY creation and management. This is just a thin layer on top +/// of Posix syscalls. The caller is responsible for detail-oriented handling /// of the returned file handles. const PosixPty = struct { pub const Error = OpenError || GetModeError || GetSizeError || SetSizeError || ChildPreExecError; pub const Fd = posix.fd_t; - // https://github.com/ziglang/zig/issues/13277 - // Once above is fixed, use `c.TIOCSCTTY` - const TIOCSCTTY = if (builtin.os.tag == .macos) 536900705 else c.TIOCSCTTY; - const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ; - const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ; - extern "c" fn setsid() std.c.pid_t; - const c = switch (builtin.os.tag) { - .macos => @cImport({ - @cInclude("sys/ioctl.h"); // ioctl and constants - @cInclude("util.h"); // openpty() - }), - .freebsd => @cImport({ - @cInclude("termios.h"); // ioctl and constants - @cInclude("libutil.h"); // openpty() - }), - else => @cImport({ - @cInclude("sys/ioctl.h"); // ioctl and constants - @cInclude("pty.h"); - }), - }; + const c = @import("pty-c"); /// The file descriptors for the master and slave side of the pty. /// The slave side is never closed automatically by this struct @@ -115,6 +119,14 @@ /// go wrong. master: Fd, slave: Fd, + + /// Buffer for storage of slave tty name so that we don't have to recompute + /// it every time we need it. + tty_name_buf: [std.fs.max_path_bytes:0]u8 = undefined, + /// The name of slave tty. If `null` it has not yet been computed or + /// may not be available. Should not be accessed directly, but through + /// `self.getProcessInfo(.tty_name)` + tty_name: ?[:0]const u8 = null, pub const OpenError = error{OpenptyFailed}; @@ -141,15 +153,15 @@ // Set CLOEXEC on the master fd, only the slave fd should be inherited // by the child process (shell/command). cloexec: { - const flags = std.posix.fcntl(master_fd, std.posix.F.GETFD, 0) catch |err| { + const flags = posix.fcntl(master_fd, posix.F.GETFD, 0) catch |err| { log.warn("error getting flags for master fd err={}", .{err}); break :cloexec; }; - _ = std.posix.fcntl( + _ = posix.fcntl( master_fd, - std.posix.F.SETFD, - flags | std.posix.FD_CLOEXEC, + posix.F.SETFD, + flags | posix.FD_CLOEXEC, ) catch |err| { log.warn("error setting CLOEXEC on master fd err={}", .{err}); break :cloexec; @@ -168,6 +180,8 @@ return .{ .master = master_fd, .slave = slave_fd, + .tty_name_buf = undefined, + .tty_name = null, }; } @@ -194,7 +208,7 @@ /// Return the size of the pty. pub fn getSize(self: Pty) GetSizeError!winsize { var ws: winsize = undefined; - if (c.ioctl(self.master, TIOCGWINSZ, @intFromPtr(&ws)) < 0) + if (c.ioctl(self.master, c.TIOCGWINSZ, @intFromPtr(&ws)) < 0) return error.IoctlFailed; return ws; @@ -204,7 +218,7 @@ /// Set the size of the pty. pub fn setSize(self: *Pty, size: winsize) SetSizeError!void { - if (c.ioctl(self.master, TIOCSWINSZ, @intFromPtr(&size)) < 0) + if (c.ioctl(self.master, c.TIOCSWINSZ, @intFromPtr(&size)) < 0) return error.IoctlFailed; } @@ -234,10 +248,10 @@ posix.sigaction(posix.SIG.QUIT, &sa, null); // Create a new process group - if (setsid() < 0) return error.ProcessGroupFailed; + if (c.setsid() < 0) return error.ProcessGroupFailed; // Set controlling terminal - switch (posix.errno(c.ioctl(self.slave, TIOCSCTTY, @as(c_ulong, 0)))) { + switch (posix.errno(c.ioctl(self.slave, c.TIOCSCTTY, @as(c_ulong, 0)))) { .SUCCESS => {}, else => |err| { log.err("error setting controlling terminal errno={}", .{err}); @@ -248,6 +262,62 @@ // Can close master/slave pair now posix.close(self.slave); posix.close(self.master); + } + + /// Get information about the process(es) attached to the PTY. Returns + /// `null` if there was an error getting the information or the information + /// is not available on a particular platform. + pub fn getProcessInfo(self: *PosixPty, comptime info: ProcessInfo) ?ProcessInfo.Type(info) { + return switch (info) { + .foreground_pid => { + switch (builtin.os.tag) { + .linux => { + const linux = std.os.linux; + var pgrp: i32 = undefined; + const rc = linux.tcgetpgrp(self.master, &pgrp); + switch (linux.E.init(rc)) { + .SUCCESS => return @intCast(pgrp), + else => return null, + } + }, + else => { + const rc = c.tcgetpgrp(self.master); + if (rc < 0) return null; + return @intCast(rc); + }, + } + }, + .tty_name => { + if (self.tty_name) |tty_name| return tty_name; + + switch (builtin.os.tag) { + .macos => { + // The macOS TIOCPTYGNAME ioctl does not allow us to + // specify the length of the buffer passed to it, but + // expects it to be at least 128 bytes long. + assert(self.tty_name_buf.len >= 128); + switch (posix.errno(c.ioctl(self.master, c.TIOCPTYGNAME, @intFromPtr(&self.tty_name_buf)))) { + .SUCCESS => { + const tty_name: [:0]const u8 = std.mem.sliceTo(&self.tty_name_buf, 0); + self.tty_name = tty_name; + return tty_name; + }, + else => |err| { + log.err("error getting name of slave PTY errno={t}", .{err}); + return null; + }, + } + }, + .linux => { + if (c.ptsname_r(self.master, &self.tty_name_buf, self.tty_name_buf.len) != 0) return null; + const tty_name: [:0]const u8 = std.mem.sliceTo(&self.tty_name_buf, 0); + self.tty_name = tty_name; + return tty_name; + }, + else => return null, + } + }, + }; } }; @@ -398,6 +468,13 @@ if (result != windows.S_OK) return error.ResizeFailed; self.size = size; } + + /// Get information about the process(es) attached to the PTY. Returns + /// `null` if there was an error getting the information or the information + /// is not available on a particular platform. + pub fn getProcessInfo(_: *WindowsPty, comptime info: ProcessInfo) ?ProcessInfo.Type(info) { + return null; + } }; test { @@ -419,4 +496,11 @@ ws.ws_row *= 2; try pty.setSize(ws); try testing.expectEqual(ws, try pty.getSize()); + + switch (builtin.os.tag) { + .freebsd => try testing.expect(std.mem.startsWith(u8, pty.getProcessInfo(.tty_name).?, "/dev/")), + .linux => try testing.expect(std.mem.startsWith(u8, pty.getProcessInfo(.tty_name).?, "/dev/pts/")), + .macos => try testing.expect(std.mem.startsWith(u8, pty.getProcessInfo(.tty_name).?, "/dev/")), + else => try testing.expect(pty.getProcessInfo(.tty_name) == null), + } } diff --git a/src/surface_mouse.zig b/src/surface_mouse.zig --- a/src/surface_mouse.zig +++ b/src/surface_mouse.zig @@ -9,14 +9,14 @@ const builtin = @import("builtin"); const input = @import("input.zig"); const terminal = @import("terminal/main.zig"); -const MouseShape = @import("terminal/mouse_shape.zig").MouseShape; +const MouseShape = terminal.MouseShape; /// For processing key events; the key that was physically pressed on the /// keyboard. physical_key: input.Key, /// The mouse event tracking mode, if any. -mouse_event: terminal.Terminal.MouseEvents, +mouse_event: terminal.MouseEvent, /// The current terminal's mouse shape. mouse_shape: MouseShape, diff --git a/.agents/commands/gh-issue b/.agents/commands/gh-issue deleted file mode 100644 --- a/.agents/commands/gh-issue +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env nu - -# A command to generate an agent prompt to diagnose and formulate -# a plan for resolving a GitHub issue. -# -# IMPORTANT: This command is prompted to NOT write any code and to ONLY -# produce a plan. You should still be vigilant when running this but that -# is the expected behavior. -# -# The `` parameter can be either an issue number or a full GitHub -# issue URL. -def main [ - issue: any, # Ghostty issue number or URL - --repo: string = "ghostty-org/ghostty" # GitHub repository in the format "owner/repo" -] { - # TODO: This whole script doesn't handle errors very well. I actually - # don't know Nu well enough to know the proper way to handle it all. - - let issueData = gh issue view $issue --json author,title,number,body,comments | from json - let comments = $issueData.comments | each { |comment| - $" -### Comment by ($comment.author.login) -($comment.body) -" | str trim - } | str join "\n\n" - - $" -Deep-dive on this GitHub issue. Find the problem and generate a plan. -Do not write code. Explain the problem clearly and propose a comprehensive plan -to solve it. - -# ($issueData.title) \(($issueData.number)\) - -## Description -($issueData.body) - -## Comments -($comments) - -## Your Tasks - -You are an experienced software developer tasked with diagnosing issues. - -1. Review the issue context and details. -2. Examine the relevant parts of the codebase. Analyze the code thoroughly - until you have a solid understanding of how it works. -3. Explain the issue in detail, including the problem and its root cause. -4. Create a comprehensive plan to solve the issue. The plan should include: - - Required code changes - - Potential impacts on other parts of the system - - Necessary tests to be written or updated - - Documentation updates - - Performance considerations - - Security implications - - Backwards compatibility \(if applicable\) - - Include the reference link to the source issue and any related discussions -4. Think deeply about all aspects of the task. Consider edge cases, potential - challenges, and best practices for addressing the issue. Review the plan - with the oracle and adjust it based on its feedback. - -**ONLY CREATE A PLAN. DO NOT WRITE ANY CODE.** Your task is to create -a thorough, comprehensive strategy for understanding and resolving the issue. -" | str trim -} diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -30,7 +30,7 @@ runs-on: ${{ matrix.variant.runner }} steps: - name: Download Source Tarball Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: run-id: ${{ inputs.source-run-id }} artifact-ids: ${{ inputs.source-artifact-id }} diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml --- a/.github/workflows/milestone.yml +++ b/.github/workflows/milestone.yml @@ -11,12 +11,15 @@ jobs: update-milestone: + # Ignore bot-authored pull requests (dependabot, app bots, etc) + # and CI-only PRs. + if: github.event_name == 'issues' || (github.event.pull_request.user.type != 'Bot' && !startsWith(github.event.pull_request.title, 'ci:')) runs-on: namespace-profile-ghostty-sm name: Milestone Update steps: - name: Set Milestone for PR uses: hustcer/milestone-action@ebed8d5daafd855a600d7e665c1b130f06d24130 # v3.1 - if: github.event.pull_request.merged == true + if: github.event.pull_request.merged == true && !contains(github.event.pull_request.title, 'VOUCHED') && !startsWith(github.event.pull_request.title, 'ci:') with: action: bind-pr # `bind-pr` is the default action github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -47,10 +47,10 @@ /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -89,11 +89,11 @@ /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -147,7 +147,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -299,7 +299,7 @@ curl -sL https://sentry.io/get-cli/ | bash - name: Download macOS Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: macos @@ -322,7 +322,7 @@ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download macOS Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: macos @@ -370,17 +370,17 @@ GHOSTTY_VERSION: ${{ needs.setup.outputs.version }} steps: - name: Download macOS Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: macos - name: Download Sparkle Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: sparkle - name: Download Source Tarball Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: source-tarball diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -42,10 +42,10 @@ with: path: | /nix - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -175,10 +175,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -195,7 +195,7 @@ nix develop -c minisign -S -m ghostty-source.tar.gz -s minisign.key < minisign.password - name: Update Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -245,7 +245,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -378,7 +378,7 @@ # Update Release - name: Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -501,7 +501,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -627,7 +627,7 @@ # Update Release - name: Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -698,7 +698,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -824,7 +824,7 @@ # Update Release - name: Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: name: 'Ghostty Tip ("Nightly")' prerelease: true diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -26,7 +26,7 @@ ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Download Source Tarball Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: run-id: ${{ inputs.source-run-id }} artifact-ids: ${{ inputs.source-artifact-id }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,19 +31,19 @@ steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter_every with: - token: ${{ secrets.GITHUB_TOKEN }} + token: "" predicate-quantifier: "every" filters: | code: - '**' - '!.github/VOUCHED.td' - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter_any with: - token: ${{ secrets.GITHUB_TOKEN }} + token: "" filters: | macos: - '.swiftlint.yml' @@ -85,24 +85,28 @@ - skip - build-bench - build-dist - - build-examples + - build-examples-zig + - build-examples-cmake + - build-examples-cmake-windows + - build-cmake - build-flatpak - build-libghostty-vt - build-libghostty-vt-android - build-libghostty-vt-macos + - build-libghostty-vt-windows - build-linux - build-linux-libghostty - build-nix - build-macos - build-macos-freetype - build-snap - - build-windows - test - test-simd - test-gtk - test-sentry-linux - test-i18n - test-fuzz-libghostty + - test-lib-vt - test-macos - pinact - prettier @@ -156,10 +160,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -167,21 +171,123 @@ - name: Build Benchmarks run: nix develop -c zig build -Demit-bench - build-examples: + list-examples: + if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' + needs: skip + runs-on: namespace-profile-ghostty-xsm + outputs: + zig: ${{ steps.list.outputs.zig }} + cmake: ${{ steps.list.outputs.cmake }} + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - id: list + name: List example directories + run: | + zig=$(ls example/*/build.zig.zon 2>/dev/null | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "$zig" | jq . + echo "zig=$zig" >> "$GITHUB_OUTPUT" + cmake=$(ls example/*/CMakeLists.txt 2>/dev/null | xargs -n1 dirname | xargs -n1 basename | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "$cmake" | jq . + echo "cmake=$cmake" >> "$GITHUB_OUTPUT" + + build-examples-zig: strategy: fail-fast: false matrix: - dir: - [ - c-vt, - c-vt-key-encode, - c-vt-paste, - c-vt-sgr, - zig-formatter, - zig-vt, - zig-vt-stream, - ] + dir: ${{ fromJSON(needs.list-examples.outputs.zig) }} name: Example ${{ matrix.dir }} + runs-on: namespace-profile-ghostty-xsm + needs: [test, list-examples] + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build Example + run: | + cd example/${{ matrix.dir }} + nix develop -c zig build + + build-examples-cmake: + strategy: + fail-fast: false + matrix: + dir: ${{ fromJSON(needs.list-examples.outputs.cmake) }} + name: Example ${{ matrix.dir }} + runs-on: namespace-profile-ghostty-xsm + needs: [test, list-examples] + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build Example + run: | + cd example/${{ matrix.dir }} + nix develop -c cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} + nix develop -c cmake --build build + + build-examples-cmake-windows: + strategy: + fail-fast: false + matrix: + dir: ${{ fromJSON(needs.list-examples.outputs.cmake) }} + name: Example ${{ matrix.dir }} (Windows) + runs-on: windows-2025 + timeout-minutes: 45 + needs: [test, list-examples] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + + - name: Build Example + shell: pwsh + run: | + cd example/${{ matrix.dir }} + cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=${{ github.workspace }} + cmake --build build + + build-cmake: runs-on: namespace-profile-ghostty-sm needs: test env: @@ -199,18 +305,25 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - name: Build Example + - name: Build run: | - cd example/${{ matrix.dir }} - nix develop -c zig build + nix develop -c cmake -B build + nix develop -c cmake --build build + + - name: Verify artifacts + run: | + test -f zig-out/lib/libghostty-vt.so.0.1.0 + test -d zig-out/include/ghostty + ls -la zig-out/lib/ + ls -la zig-out/include/ghostty/ build-flatpak: strategy: @@ -232,10 +345,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -266,10 +379,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -310,21 +423,21 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Build run: | - nix develop -c zig build lib-vt \ + nix develop -c zig build -Demit-lib-vt \ -Dtarget=${{ matrix.target }} \ -Dsimd=false - # lib-vt requires macOS runner for macOS/iOS builds becauase it requires the `apple_sdk` path + # lib-vt requires macOS runner for macOS/iOS builds because it requires the `apple_sdk` path build-libghostty-vt-macos: strategy: matrix: @@ -350,7 +463,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -360,7 +473,7 @@ - name: Build run: | - nix develop -c zig build lib-vt \ + nix develop -c zig build -Demit-lib-vt \ -Dtarget=${{ matrix.target }} # lib-vt requires the Android NDK for Android builds @@ -387,10 +500,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -406,10 +519,27 @@ - name: Build run: | - nix develop -c zig build lib-vt \ + nix develop -c zig build -Demit-lib-vt \ -Dtarget=${{ matrix.target }} env: ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + build-libghostty-vt-windows: + runs-on: windows-2025 + timeout-minutes: 45 + needs: test + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + + - name: Test libghostty-vt + run: zig build test-lib-vt + + - name: Build libghostty-vt + run: zig build -Demit-lib-vt build-linux: strategy: @@ -433,10 +563,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -462,10 +592,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -495,10 +625,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -541,10 +671,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -621,7 +751,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -681,7 +811,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -696,82 +826,15 @@ id: deps run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT + # We run tests with an empty test filter so it runs all unit tests + # but skips Xcode tests - name: Test All run: | - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype -Dtest-filter="" - name: Build All run: | nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_freetype - - build-windows: - runs-on: windows-2022 - # this will not stop other jobs from running - continue-on-error: true - timeout-minutes: 45 - needs: test - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - # This could be from a script if we wanted to but inlining here for now - # in one place. - # Using powershell so that we do not need to install WSL components. Also, - # WSLv1 is only installed on Github runners. - - name: Install zig - shell: pwsh - run: | - # Get the zig version from build.zig.zon so that it only needs to be updated - $fileContent = Get-Content -Path "build.zig.zon" -Raw - $pattern = 'minimum_zig_version\s*=\s*"([^"]+)"' - $zigVersion = [regex]::Match($fileContent, $pattern).Groups[1].Value - $version = "zig-x86_64-windows-$zigVersion" - Write-Output $version - $uri = "https://ziglang.org/download/$zigVersion/$version.zip" - Invoke-WebRequest -Uri "$uri" -OutFile ".\zig-windows.zip" - Expand-Archive -Path ".\zig-windows.zip" -DestinationPath ".\" -Force - Remove-Item -Path ".\zig-windows.zip" - Rename-Item -Path ".\$version" -NewName ".\zig" - Write-Host "Zig installed." - .\zig\zig.exe version - - - name: Generate build testing script - shell: pwsh - run: | - # Generate a script so that we can swallow the errors - $scriptContent = @" - .\zig\zig.exe build test 2>&1 | Out-File -FilePath "build.log" -Append - exit 0 - "@ - $scriptPath = "zigbuild.ps1" - # Write the script content to a file - $scriptContent | Set-Content -Path $scriptPath - Write-Host "Script generated at: $scriptPath" - - - name: Test Windows - shell: pwsh - run: .\zigbuild.ps1 -ErrorAction SilentlyContinue - - - name: Generate build script - shell: pwsh - run: | - # Generate a script so that we can swallow the errors - $scriptContent = @" - .\zig\zig.exe build 2>&1 | Out-File -FilePath "build.log" -Append - exit 0 - "@ - $scriptPath = "zigbuild.ps1" - # Write the script content to a file - $scriptContent | Set-Content -Path $scriptPath - Write-Host "Script generated at: $scriptPath" - - - name: Build Windows - shell: pwsh - run: .\zigbuild.ps1 -ErrorAction SilentlyContinue - - - name: Dump logs - shell: pwsh - run: Get-Content -Path ".\build.log" test: if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' @@ -799,10 +862,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -816,6 +879,36 @@ # This relies on the cache being populated by the commands above. - name: Test System Build run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p + + test-lib-vt: + if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' + needs: skip + runs-on: namespace-profile-ghostty-md + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2 + with: + path: | + /nix + /zig + + # Install Nix and use that to run our tests so our environment matches exactly. + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Test + run: nix develop -c zig build test-lib-vt test-gtk: strategy: @@ -841,10 +934,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -889,10 +982,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -924,10 +1017,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -958,7 +1051,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -999,10 +1092,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1030,10 +1123,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1072,10 +1165,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1103,10 +1196,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1133,10 +1226,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1165,7 +1258,7 @@ - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1191,10 +1284,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1219,10 +1312,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1247,10 +1340,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1280,10 +1373,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1308,10 +1401,10 @@ path: | /nix /zig - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1345,10 +1438,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -1364,13 +1457,13 @@ needs: [test, build-dist] steps: - name: Install and configure Namespace CLI - uses: namespacelabs/nscloud-setup@f378676225212387f1283f4da878712af2c4cd60 # v0.0.11 + uses: namespacelabs/nscloud-setup@df198f982fcecfb8264bea3f1274b56a61b6dfdc # v0.0.12 - name: Configure Namespace powered Buildx - uses: namespacelabs/nscloud-setup-buildx-action@f5814dcf37a16cce0624d5bec2ab879654294aa0 # v0.0.22 + uses: namespacelabs/nscloud-setup-buildx-action@d059ed7184f0bc7c8b27e8810cea153d02bcc6dd # v0.0.23 - name: Download Source Tarball Artifacts - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: source-tarball @@ -1380,7 +1473,7 @@ tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Build and push - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: dist file: dist/src/build/docker/debian/Dockerfile @@ -1407,10 +1500,10 @@ /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + - uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/update-colorschemes.yml b/.github/workflows/update-colorschemes.yml --- a/.github/workflows/update-colorschemes.yml +++ b/.github/workflows/update-colorschemes.yml @@ -29,10 +29,10 @@ /zig - name: Setup Nix - uses: cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0 + uses: cachix/install-nix-action@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31.10.2 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@3ba601ff5bbb07c7220846facfa2cd81eeee15a1 # v16 + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/vouch-check-issue.yml b/.github/workflows/vouch-check-issue.yml --- a/.github/workflows/vouch-check-issue.yml +++ b/.github/workflows/vouch-check-issue.yml @@ -8,7 +8,7 @@ check: runs-on: namespace-profile-ghostty-xsm steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.VOUCH_APP_ID }} diff --git a/.github/workflows/vouch-check-pr.yml b/.github/workflows/vouch-check-pr.yml --- a/.github/workflows/vouch-check-pr.yml +++ b/.github/workflows/vouch-check-pr.yml @@ -8,7 +8,7 @@ check: runs-on: namespace-profile-ghostty-xsm steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.VOUCH_APP_ID }} diff --git a/.github/workflows/vouch-manage-by-discussion.yml b/.github/workflows/vouch-manage-by-discussion.yml --- a/.github/workflows/vouch-manage-by-discussion.yml +++ b/.github/workflows/vouch-manage-by-discussion.yml @@ -12,7 +12,7 @@ manage: runs-on: namespace-profile-ghostty-xsm steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.VOUCH_APP_ID }} diff --git a/.github/workflows/vouch-manage-by-issue.yml b/.github/workflows/vouch-manage-by-issue.yml --- a/.github/workflows/vouch-manage-by-issue.yml +++ b/.github/workflows/vouch-manage-by-issue.yml @@ -12,7 +12,7 @@ manage: runs-on: namespace-profile-ghostty-xsm steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.VOUCH_APP_ID }} diff --git a/.github/workflows/vouch-sync-codeowners.yml b/.github/workflows/vouch-sync-codeowners.yml --- a/.github/workflows/vouch-sync-codeowners.yml +++ b/.github/workflows/vouch-sync-codeowners.yml @@ -13,7 +13,7 @@ sync: runs-on: namespace-profile-ghostty-xsm steps: - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.VOUCH_APP_ID }} diff --git a/dist/cmake/README.md b/dist/cmake/README.md new file mode 100644 --- /dev/null +++ b/dist/cmake/README.md @@ -0,0 +1,67 @@ +# CMake Support for libghostty-vt + +The top-level `CMakeLists.txt` wraps the Zig build system so that CMake +projects can consume libghostty-vt without invoking `zig build` manually. +Running `cmake --build` triggers `zig build -Demit-lib-vt` automatically. + +This means downstream projects do require a working Zig compiler on +`PATH` to build, but don't need to know any Zig-specific details. + +## Using FetchContent (recommended) + +Add the following to your project's `CMakeLists.txt`: + +```cmake +include(FetchContent) +FetchContent_Declare(ghostty + GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git + GIT_TAG main +) +FetchContent_MakeAvailable(ghostty) + +add_executable(myapp main.c) +target_link_libraries(myapp PRIVATE ghostty-vt) +``` + +This fetches the Ghostty source, builds libghostty-vt via Zig during your +CMake build, and links it into your target. Headers are added to the +include path automatically. + +### Using a local checkout + +If you already have the Ghostty source checked out, skip the download by +pointing CMake at it: + +```shell-session +cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=/path/to/ghostty +cmake --build build +``` + +## Using find_package (install-based) + +Build and install libghostty-vt first: + +```shell-session +cd /path/to/ghostty +cmake -B build +cmake --build build +cmake --install build --prefix /usr/local +``` + +Then in your project: + +```cmake +find_package(ghostty-vt REQUIRED) + +add_executable(myapp main.c) +target_link_libraries(myapp PRIVATE ghostty-vt::ghostty-vt) +``` + +## Files + +- `ghostty-vt-config.cmake.in` — template for the CMake package config + file installed alongside the library, enabling `find_package()` support. + +## Example + +See `example/c-vt-cmake/` for a complete working example. diff --git a/dist/cmake/ghostty-vt-config.cmake.in b/dist/cmake/ghostty-vt-config.cmake.in new file mode 100644 --- /dev/null +++ b/dist/cmake/ghostty-vt-config.cmake.in @@ -0,0 +1,65 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +set(_ghostty_vt_libdir "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@") + +# Shared library target +if(NOT TARGET ghostty-vt::ghostty-vt) + add_library(ghostty-vt::ghostty-vt SHARED IMPORTED) + + if(WIN32) + set(_ghostty_vt_shared_location "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_BINDIR@/@GHOSTTY_VT_REALNAME@") + else() + set(_ghostty_vt_shared_location "${_ghostty_vt_libdir}/@GHOSTTY_VT_REALNAME@") + endif() + + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES + IMPORTED_LOCATION "${_ghostty_vt_shared_location}" + INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@" + ) + unset(_ghostty_vt_shared_location) + + if(APPLE) + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES + IMPORTED_SONAME "@rpath/@GHOSTTY_VT_SONAME@" + INTERFACE_LINK_DIRECTORIES "${_ghostty_vt_libdir}" + ) + # Ensure consumers can find the @rpath dylib at runtime + set_property(TARGET ghostty-vt::ghostty-vt APPEND PROPERTY + INTERFACE_LINK_OPTIONS "LINKER:-rpath,${_ghostty_vt_libdir}" + ) + elseif(WIN32) + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES + IMPORTED_IMPLIB "${_ghostty_vt_libdir}/@GHOSTTY_VT_IMPLIB@" + ) + else() + set_target_properties(ghostty-vt::ghostty-vt PROPERTIES + IMPORTED_SONAME "@GHOSTTY_VT_SONAME@" + ) + endif() +endif() + +# Static library target +# +# Consumers must link transitive dependencies themselves. By default (with +# SIMD enabled): libc, libc++ (or libstdc++ on Linux), highway, and +# simdutf. Building with -Dsimd=false removes the C++ / highway / simdutf +# dependencies. +if(NOT TARGET ghostty-vt::ghostty-vt-static) + add_library(ghostty-vt::ghostty-vt-static STATIC IMPORTED) + + set_target_properties(ghostty-vt::ghostty-vt-static PROPERTIES + IMPORTED_LOCATION "${_ghostty_vt_libdir}/@GHOSTTY_VT_STATIC_REALNAME@" + INTERFACE_INCLUDE_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_INCLUDEDIR@" + ) + if(WIN32) + set_target_properties(ghostty-vt::ghostty-vt-static PROPERTIES + INTERFACE_LINK_LIBRARIES "ntdll;kernel32" + ) + endif() +endif() + +unset(_ghostty_vt_libdir) + +check_required_components(ghostty-vt) diff --git a/dist/linux/com.mitchellh.ghostty.metainfo.xml.in b/dist/linux/com.mitchellh.ghostty.metainfo.xml.in --- a/dist/linux/com.mitchellh.ghostty.metainfo.xml.in +++ b/dist/linux/com.mitchellh.ghostty.metainfo.xml.in @@ -52,6 +52,12 @@ + + https://ghostty.org/docs/install/release-notes/1-3-1 + + + https://ghostty.org/docs/install/release-notes/1-3-0 + https://ghostty.org/docs/install/release-notes/1-0-1 diff --git a/dist/linux/ghostty_dolphin.desktop b/dist/linux/ghostty_dolphin.desktop --- a/dist/linux/ghostty_dolphin.desktop +++ b/dist/linux/ghostty_dolphin.desktop @@ -7,5 +7,4 @@ [Desktop Action RunGhosttyDir] Name=Open Ghostty Here Icon=com.mitchellh.ghostty -Exec=ghostty --working-directory=%F --gtk-single-instance=false - +Exec=ghostty +new-window --working-directory=%F diff --git a/example/c-vt-build-info/README.md b/example/c-vt-build-info/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-build-info/README.md @@ -0,0 +1,17 @@ +# Example: `ghostty-vt` Build Info + +This contains a simple example of how to use the `ghostty-vt` build info +API to query compile-time build configuration. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-build-info/build.zig b/example/c-vt-build-info/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-build-info/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_build_info", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-build-info/build.zig.zon b/example/c-vt-build-info/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-build-info/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_build_info, + .version = "0.0.0", + .fingerprint = 0xc6b57ed4f83fb16, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-cmake-static/CMakeLists.txt b/example/c-vt-cmake-static/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/example/c-vt-cmake-static/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.19) +project(c-vt-cmake-static LANGUAGES C) + +include(FetchContent) +FetchContent_Declare(ghostty + GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git + GIT_TAG main +) +set(GHOSTTY_ZIG_BUILD_FLAGS "-Dsimd=false" CACHE STRING "" FORCE) +FetchContent_MakeAvailable(ghostty) + +add_executable(c_vt_cmake_static src/main.c) +target_link_libraries(c_vt_cmake_static PRIVATE ghostty-vt-static) diff --git a/example/c-vt-cmake-static/README.md b/example/c-vt-cmake-static/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-cmake-static/README.md @@ -0,0 +1,21 @@ +# c-vt-cmake-static + +Demonstrates consuming libghostty-vt as a **static** library from a CMake +project using `FetchContent`. Creates a terminal, writes VT sequences into +it, and formats the screen contents as plain text. + +## Building + +```shell-session +cd example/c-vt-cmake-static +cmake -B build +cmake --build build +./build/c_vt_cmake_static +``` + +To build against a local checkout instead of fetching from GitHub: + +```shell-session +cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=../.. +cmake --build build +``` diff --git a/example/c-vt-cmake/CMakeLists.txt b/example/c-vt-cmake/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/example/c-vt-cmake/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.19) +project(c-vt-cmake LANGUAGES C) + +include(FetchContent) +FetchContent_Declare(ghostty + GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git + GIT_TAG main +) +FetchContent_MakeAvailable(ghostty) + +add_executable(c_vt_cmake src/main.c) +target_link_libraries(c_vt_cmake PRIVATE ghostty-vt) diff --git a/example/c-vt-cmake/README.md b/example/c-vt-cmake/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-cmake/README.md @@ -0,0 +1,21 @@ +# c-vt-cmake + +Demonstrates consuming libghostty-vt from a CMake project using +`FetchContent`. Creates a terminal, writes VT sequences into it, and +formats the screen contents as plain text. + +## Building + +```shell-session +cd example/c-vt-cmake +cmake -B build +cmake --build build +./build/c_vt_cmake +``` + +To build against a local checkout instead of fetching from GitHub: + +```shell-session +cmake -B build -DFETCHCONTENT_SOURCE_DIR_GHOSTTY=../.. +cmake --build build +``` diff --git a/example/c-vt-effects/README.md b/example/c-vt-effects/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-effects/README.md @@ -0,0 +1,18 @@ +# Example: `ghostty-vt` Terminal Effects + +This contains a simple example of how to register and use terminal +effect callbacks (`write_pty`, `bell`, `title_changed`) with the +`ghostty-vt` C library. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-effects/build.zig b/example/c-vt-effects/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-effects/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_effects", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-effects/build.zig.zon b/example/c-vt-effects/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-effects/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_effects, + .version = "0.0.0", + .fingerprint = 0xc02634cd65f5b583, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-encode-focus/README.md b/example/c-vt-encode-focus/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-focus/README.md @@ -0,0 +1,17 @@ +# Example: `ghostty-vt` Encode Focus + +This contains a simple example of how to use the `ghostty-vt` focus +encoding API to encode focus gained/lost events into escape sequences. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-encode-focus/build.zig b/example/c-vt-encode-focus/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-focus/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_encode_focus", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-encode-focus/build.zig.zon b/example/c-vt-encode-focus/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-focus/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_encode_focus, + .version = "0.0.0", + .fingerprint = 0x89f01fd829fcc550, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-encode-key/README.md b/example/c-vt-encode-key/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-key/README.md @@ -0,0 +1,22 @@ +# Example: `ghostty-vt` C Key Encoding + +This example demonstrates how to use the `ghostty-vt` C library to encode key +events into terminal escape sequences. + +This example specifically shows how to: + +1. Create a key encoder with the C API +2. Configure Kitty keyboard protocol flags (this example uses KKP) +3. Create and configure a key event +4. Encode the key event into a terminal escape sequence + +The example encodes a Ctrl key release event with the Ctrl modifier set, +producing the escape sequence `\x1b[57442;5:3u`. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-encode-key/build.zig b/example/c-vt-encode-key/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-key/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_encode_key", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-encode-key/build.zig.zon b/example/c-vt-encode-key/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-key/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt, + .version = "0.0.0", + .fingerprint = 0x413a8529b1255f9a, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-encode-mouse/README.md b/example/c-vt-encode-mouse/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-mouse/README.md @@ -0,0 +1,23 @@ +# Example: `ghostty-vt` C Mouse Encoding + +This example demonstrates how to use the `ghostty-vt` C library to encode mouse +events into terminal escape sequences. + +This example specifically shows how to: + +1. Create a mouse encoder with the C API +2. Configure tracking mode and output format (this example uses SGR) +3. Set terminal geometry for pixel-to-cell coordinate mapping +4. Create and configure a mouse event +5. Encode the mouse event into a terminal escape sequence + +The example encodes a left button press at pixel position (50, 40) using SGR +format, producing an escape sequence like `\x1b[<0;6;3M`. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-encode-mouse/build.zig b/example/c-vt-encode-mouse/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-mouse/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_encode_mouse", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-encode-mouse/build.zig.zon b/example/c-vt-encode-mouse/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-encode-mouse/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt, + .version = "0.0.0", + .fingerprint = 0x413a8529a6dd3c51, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-formatter/README.md b/example/c-vt-formatter/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-formatter/README.md @@ -0,0 +1,18 @@ +# Example: `ghostty-vt` Terminal Formatter + +This contains a simple example of how to use the `ghostty-vt` terminal and +formatter APIs to create a terminal, write VT-encoded content into it, and +format the screen contents as plain text. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-formatter/build.zig b/example/c-vt-formatter/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-formatter/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_formatter", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-formatter/build.zig.zon b/example/c-vt-formatter/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-formatter/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_formatter, + .version = "0.0.0", + .fingerprint = 0x9e3758265677a0c4, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-grid-traverse/README.md b/example/c-vt-grid-traverse/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-grid-traverse/README.md @@ -0,0 +1,19 @@ +# Example: `ghostty-vt` Grid Traversal + +This contains a simple example of how to use the `ghostty-vt` terminal and +grid reference APIs to create a terminal, write content into it, and then +traverse the entire grid cell-by-cell using grid refs to inspect codepoints, +row state, and styles. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-grid-traverse/build.zig b/example/c-vt-grid-traverse/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-grid-traverse/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_grid_traverse", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-grid-traverse/build.zig.zon b/example/c-vt-grid-traverse/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-grid-traverse/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_grid_traverse, + .version = "0.0.0", + .fingerprint = 0xf694dd12db9be040, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-key-encode/README.md b/example/c-vt-key-encode/README.md deleted file mode 100644 --- a/example/c-vt-key-encode/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Example: `ghostty-vt` C Key Encoding - -This example demonstrates how to use the `ghostty-vt` C library to encode key -events into terminal escape sequences. - -This example specifically shows how to: - -1. Create a key encoder with the C API -2. Configure Kitty keyboard protocol flags (this example uses KKP) -3. Create and configure a key event -4. Encode the key event into a terminal escape sequence - -The example encodes a Ctrl key release event with the Ctrl modifier set, -producing the escape sequence `\x1b[57442;5:3u`. - -## Usage - -Run the program: - -```shell-session -zig build run -``` diff --git a/example/c-vt-key-encode/build.zig b/example/c-vt-key-encode/build.zig deleted file mode 100644 --- a/example/c-vt-key-encode/build.zig +++ /dev/null @@ -1,42 +0,0 @@ -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const run_step = b.step("run", "Run the app"); - - const exe_mod = b.createModule(.{ - .target = target, - .optimize = optimize, - }); - exe_mod.addCSourceFiles(.{ - .root = b.path("src"), - .files = &.{"main.c"}, - }); - - // You'll want to use a lazy dependency here so that ghostty is only - // downloaded if you actually need it. - if (b.lazyDependency("ghostty", .{ - // Setting simd to false will force a pure static build that - // doesn't even require libc, but it has a significant performance - // penalty. If your embedding app requires libc anyway, you should - // always keep simd enabled. - // .simd = false, - })) |dep| { - exe_mod.linkLibrary(dep.artifact("ghostty-vt")); - } - - // Exe - const exe = b.addExecutable(.{ - .name = "c_vt_key_encode", - .root_module = exe_mod, - }); - b.installArtifact(exe); - - // Run - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| run_cmd.addArgs(args); - run_step.dependOn(&run_cmd.step); -} diff --git a/example/c-vt-key-encode/build.zig.zon b/example/c-vt-key-encode/build.zig.zon deleted file mode 100644 --- a/example/c-vt-key-encode/build.zig.zon +++ /dev/null @@ -1,24 +0,0 @@ -.{ - .name = .c_vt, - .version = "0.0.0", - .fingerprint = 0x413a8529b1255f9a, - .minimum_zig_version = "0.15.1", - .dependencies = .{ - // Ghostty dependency. In reality, you'd probably use a URL-based - // dependency like the one showed (and commented out) below this one. - // We use a path dependency here for simplicity and to ensure our - // examples always test against the source they're bundled with. - .ghostty = .{ .path = "../../" }, - - // Example of what a URL-based dependency looks like: - // .ghostty = .{ - // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", - // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", - // }, - }, - .paths = .{ - "build.zig", - "build.zig.zon", - "src", - }, -} diff --git a/example/c-vt-modes/README.md b/example/c-vt-modes/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-modes/README.md @@ -0,0 +1,18 @@ +# Example: `ghostty-vt` Mode Utilities + +This contains a simple example of how to use the `ghostty-vt` mode +utilities to pack and unpack terminal mode identifiers and encode +DECRPM responses. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-modes/build.zig b/example/c-vt-modes/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-modes/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_modes", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-modes/build.zig.zon b/example/c-vt-modes/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-modes/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_modes, + .version = "0.0.0", + .fingerprint = 0x67ce079ebc70a02a, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-render/README.md b/example/c-vt-render/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-render/README.md @@ -0,0 +1,19 @@ +# Example: `ghostty-vt` Render State + +This contains an example of how to use the `ghostty-vt` render-state API +to create a render state, update it from terminal content, iterate rows +and cells, read styles and colors, inspect cursor state, and manage dirty +tracking. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-render/build.zig b/example/c-vt-render/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-render/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_render", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-render/build.zig.zon b/example/c-vt-render/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-render/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_render, + .version = "0.0.0", + .fingerprint = 0xb10e18b2fab773c9, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-size-report/README.md b/example/c-vt-size-report/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-size-report/README.md @@ -0,0 +1,17 @@ +# Example: `ghostty-vt` Size Report Encoding + +This contains a simple example of how to use the `ghostty-vt` size report +encoding API to encode terminal size reports into escape sequences. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-size-report/build.zig b/example/c-vt-size-report/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-size-report/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_size_report", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-size-report/build.zig.zon b/example/c-vt-size-report/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-size-report/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_size_report, + .version = "0.0.0", + .fingerprint = 0x17e8cdb658fab232, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-static/README.md b/example/c-vt-static/README.md new file mode 100644 --- /dev/null +++ b/example/c-vt-static/README.md @@ -0,0 +1,18 @@ +# Example: `ghostty-vt` Static Linking + +This contains a simple example of how to statically link the `ghostty-vt` +C library with a C program using the `ghostty-vt-static` artifact. It is +otherwise identical to the `c-vt` example. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-static/build.zig b/example/c-vt-static/build.zig new file mode 100644 --- /dev/null +++ b/example/c-vt-static/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + // Use "ghostty-vt-static" for static linking instead of + // "ghostty-vt" which provides a shared library. + exe_mod.linkLibrary(dep.artifact("ghostty-vt-static")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_static", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-static/build.zig.zon b/example/c-vt-static/build.zig.zon new file mode 100644 --- /dev/null +++ b/example/c-vt-static/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_static, + .version = "0.0.0", + .fingerprint = 0xa592a9fdd5d87ed2, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/wasm-key-encode/README.md b/example/wasm-key-encode/README.md --- a/example/wasm-key-encode/README.md +++ b/example/wasm-key-encode/README.md @@ -8,7 +8,7 @@ First, build the WebAssembly module: ```bash -zig build lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall +zig build -Demit-lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall ``` This will create `zig-out/bin/ghostty-vt.wasm`. diff --git a/example/wasm-sgr/README.md b/example/wasm-sgr/README.md --- a/example/wasm-sgr/README.md +++ b/example/wasm-sgr/README.md @@ -9,7 +9,7 @@ First, build the WebAssembly module: ```bash -zig build lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall +zig build -Demit-lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall ``` This will create `zig-out/bin/ghostty-vt.wasm`. diff --git a/include/ghostty/vt.h b/include/ghostty/vt.h --- a/include/ghostty/vt.h +++ b/include/ghostty/vt.h @@ -28,21 +28,38 @@ * @section groups_sec API Reference * * The API is organized into the following groups: - * - @ref key "Key Encoding" - Encode key events into terminal sequences + * - @ref terminal "Terminal" - Complete terminal emulator state and rendering + * - @ref render "Render State" - Incremental render state updates for custom renderers + * - @ref formatter "Formatter" - Format terminal content as plain text, VT sequences, or HTML * - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences * - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences * - @ref paste "Paste Utilities" - Validate paste data safety + * - @ref build_info "Build Info" - Query compile-time build configuration * - @ref allocator "Memory Management" - Memory management and custom allocators * - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions + * + * Encoding related APIs: + * - @ref focus "Focus Encoding" - Encode focus in/out events into terminal sequences + * - @ref key "Key Encoding" - Encode key events into terminal sequences + * - @ref mouse "Mouse Encoding" - Encode mouse events into terminal sequences * * @section examples_sec Examples * * Complete working examples: + * - @ref c-vt-build-info/src/main.c - Build info query example * - @ref c-vt/src/main.c - OSC parser example - * - @ref c-vt-key-encode/src/main.c - Key encoding example + * - @ref c-vt-encode-key/src/main.c - Key encoding example + * - @ref c-vt-encode-mouse/src/main.c - Mouse encoding example * - @ref c-vt-paste/src/main.c - Paste safety check example * - @ref c-vt-sgr/src/main.c - SGR parser example + * - @ref c-vt-formatter/src/main.c - Terminal formatter example + * - @ref c-vt-grid-traverse/src/main.c - Grid traversal example using grid refs * + */ + +/** @example c-vt-build-info/src/main.c + * This example demonstrates how to query compile-time build configuration + * such as SIMD support, Kitty graphics, and tmux control mode availability. */ /** @example c-vt/src/main.c @@ -50,9 +67,14 @@ * extract command information, and retrieve command-specific data like window titles. */ -/** @example c-vt-key-encode/src/main.c +/** @example c-vt-encode-key/src/main.c * This example demonstrates how to use the key encoder to convert key events * into terminal escape sequences using the Kitty keyboard protocol. + */ + +/** @example c-vt-encode-mouse/src/main.c + * This example demonstrates how to use the mouse encoder to convert mouse events + * into terminal escape sequences using the SGR mouse format. */ /** @example c-vt-paste/src/main.c @@ -65,6 +87,17 @@ * styling sequences and extract text attributes like colors and underline styles. */ +/** @example c-vt-formatter/src/main.c + * This example demonstrates how to use the terminal and formatter APIs to + * create a terminal, write VT-encoded content into it, and format the screen + * contents as plain text. + */ + +/** @example c-vt-grid-traverse/src/main.c + * This example demonstrates how to traverse the entire terminal grid using + * grid refs to inspect cell codepoints, row wrap state, and cell styles. + */ + #ifndef GHOSTTY_VT_H #define GHOSTTY_VT_H @@ -72,12 +105,25 @@ extern "C" { #endif -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include #include +#include +#include #include #ifdef __cplusplus diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; }; 819324582F24E78800A9ED8F /* DockTilePlugin.plugin in Copy DockTilePlugin */ = {isa = PBXBuildFile; fileRef = 8193244D2F24E6C000A9ED8F /* DockTilePlugin.plugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 819324642F24FF2100A9ED8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; + 8F3A9B4C2FA6B88000A18D13 /* Ghostty.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 8F3A9B4B2FA6B88000A18D13 /* Ghostty.sdef */; }; 9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; }; A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A51BFC262B30F1B800E92F16 /* Sparkle */; }; A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; }; @@ -74,6 +75,7 @@ 552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = ""; }; 810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GhosttyUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8193244D2F24E6C000A9ED8F /* DockTilePlugin.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DockTilePlugin.plugin; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F3A9B4B2FA6B88000A18D13 /* Ghostty.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.sdef; path = Ghostty.sdef; sourceTree = ""; }; 9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = ""; }; A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = ""; }; A546F1132D7B68D7003B11A0 /* locale */ = {isa = PBXFileReference; lastKnownFileType = folder; name = locale; path = "../zig-out/share/locale"; sourceTree = ""; }; @@ -134,6 +136,18 @@ "Features/App Intents/KeybindIntent.swift", "Features/App Intents/NewTerminalIntent.swift", "Features/App Intents/QuickTerminalIntent.swift", + "Features/AppleScript/AppDelegate+AppleScript.swift", + "Features/AppleScript/Ghostty.Input.Mods+AppleScript.swift", + Features/AppleScript/ScriptInputTextCommand.swift, + Features/AppleScript/ScriptKeyEventCommand.swift, + Features/AppleScript/ScriptMouseButtonCommand.swift, + Features/AppleScript/ScriptMousePosCommand.swift, + Features/AppleScript/ScriptMouseScrollCommand.swift, + Features/AppleScript/ScriptRecord.swift, + Features/AppleScript/ScriptSurfaceConfiguration.swift, + Features/AppleScript/ScriptTab.swift, + Features/AppleScript/ScriptTerminal.swift, + Features/AppleScript/ScriptWindow.swift, Features/ClipboardConfirmation/ClipboardConfirmation.xib, Features/ClipboardConfirmation/ClipboardConfirmationController.swift, Features/ClipboardConfirmation/ClipboardConfirmationView.swift, @@ -322,6 +336,7 @@ isa = PBXGroup; children = ( A571AB1C2A206FC600248498 /* Ghostty-Info.plist */, + 8F3A9B4B2FA6B88000A18D13 /* Ghostty.sdef */, A5B30538299BEAAB0047F10C /* Assets.xcassets */, A553F4122E06EB1600257779 /* Ghostty.icon */, A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */, @@ -557,6 +572,7 @@ A553F4142E06EB1600257779 /* Ghostty.icon in Resources */, 29C15B1D2CDC3B2900520DD4 /* bat in Resources */, A586167C2B7703CC009BDB1D /* fish in Resources */, + 8F3A9B4C2FA6B88000A18D13 /* Ghostty.sdef in Resources */, 55154BE02B33911F001622DC /* ghostty in Resources */, A546F1142D7B68D7003B11A0 /* locale in Resources */, A5985CE62C33060F00C57AD3 /* man in Resources */, diff --git a/macos/GhosttyUITests/GhosttyCommandPaletteTests.swift b/macos/GhosttyUITests/GhosttyCommandPaletteTests.swift new file mode 100644 --- /dev/null +++ b/macos/GhosttyUITests/GhosttyCommandPaletteTests.swift @@ -0,0 +1,81 @@ +// +// GhosttyCommandPaletteTests.swift +// Ghostty +// +// Created by Lukas on 19.03.2026. +// + +import XCTest + +final class GhosttyCommandPaletteTests: GhosttyCustomConfigCase { + override static var runsForEachTargetApplicationUIConfiguration: Bool { false } + @MainActor func testDismissingCommandPalette() async throws { + let app = try ghosttyApplication() + app.activate() + + XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 5), "New window should appear") + + app.menuItems["Command Palette"].firstMatch.click() + + let clearScreenButton = app.buttons + .containing(NSPredicate(format: "label CONTAINS[c] 'Clear Screen'")) + .firstMatch + + XCTAssertTrue(clearScreenButton.waitForExistence(timeout: 5), "Command Palette should appear") + + clearScreenButton.coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: -30, dy: 0)) + .click() + + XCTAssertTrue(clearScreenButton.waitForNonExistence(timeout: 5), "Command Palette should disappear after clicking outside") + + app.typeKey("p", modifierFlags: [.command, .shift]) + + XCTAssertTrue(clearScreenButton.waitForExistence(timeout: 5), "Command Palette should appear") + + app.typeKey(.escape, modifierFlags: []) + + XCTAssertTrue(clearScreenButton.waitForNonExistence(timeout: 5), "Command Palette should disappear after typing escape") + + app.typeKey("p", modifierFlags: [.command, .shift]) + + XCTAssertTrue(clearScreenButton.waitForExistence(timeout: 5), "Command Palette should appear") + + app.typeKey(.enter, modifierFlags: []) + + XCTAssertTrue(clearScreenButton.waitForNonExistence(timeout: 5), "Command Palette should disappear after submitting query") + + app.typeKey("p", modifierFlags: [.command, .shift]) + + XCTAssertTrue(clearScreenButton.waitForExistence(timeout: 5), "Command Palette should appear") + + app.typeText("Clear Screen") + app.typeKey(.enter, modifierFlags: []) + + XCTAssertTrue(clearScreenButton.waitForNonExistence(timeout: 5), "Command Palette should disappear after selecting a command by keyboard") + + app.typeKey("p", modifierFlags: [.command, .shift]) + app.typeKey(.delete, modifierFlags: []) + + XCTAssertTrue(clearScreenButton.waitForExistence(timeout: 5), "Command Palette should appear") + clearScreenButton.click() + + XCTAssertTrue(clearScreenButton.waitForNonExistence(timeout: 5), "Command Palette should disappear after selecting a command by mouse") + } + + @MainActor func testSelectCommandWithMouse() async throws { + let app = try ghosttyApplication() + app.activate() + + XCTAssertTrue(app.windows.firstMatch.waitForExistence(timeout: 5), "New window should appear") + + app.menuItems["Command Palette"].firstMatch.click() + + app.buttons + .containing(NSPredicate(format: "label CONTAINS[c] 'Close All Windows'")) + .firstMatch.click() + + XCTAssertTrue(app.windows.firstMatch.waitForNonExistence(timeout: 2), "All windows should be closed") + } +} + diff --git a/macos/GhosttyUITests/GhosttyCustomConfigCase.swift b/macos/GhosttyUITests/GhosttyCustomConfigCase.swift --- a/macos/GhosttyUITests/GhosttyCustomConfigCase.swift +++ b/macos/GhosttyUITests/GhosttyCustomConfigCase.swift @@ -27,6 +27,8 @@ true } + static let defaultsSuiteName: String = "GHOSTTY_UI_TESTS" + var configFile: URL? override func setUpWithError() throws { continueAfterFailure = false @@ -47,13 +49,14 @@ try newConfig.write(to: configFile!, atomically: true, encoding: .utf8) } - func ghosttyApplication() throws -> XCUIApplication { + func ghosttyApplication(defaultsSuite: String = GhosttyCustomConfigCase.defaultsSuiteName) throws -> XCUIApplication { let app = XCUIApplication() app.launchArguments.append(contentsOf: ["-ApplePersistenceIgnoreState", "YES"]) guard let configFile else { return app } app.launchEnvironment["GHOSTTY_CONFIG_PATH"] = configFile.path + app.launchEnvironment["GHOSTTY_USER_DEFAULTS_SUITE"] = defaultsSuite return app } } diff --git a/macos/GhosttyUITests/GhosttyMouseStateTests.swift b/macos/GhosttyUITests/GhosttyMouseStateTests.swift new file mode 100644 --- /dev/null +++ b/macos/GhosttyUITests/GhosttyMouseStateTests.swift @@ -0,0 +1,53 @@ +// +// GhosttyMouseStateTests.swift +// Ghostty +// +// Created by Lukas on 19.03.2026. +// + +import XCTest + +final class GhosttyMouseStateTests: GhosttyCustomConfigCase { + override static var runsForEachTargetApplicationUIConfiguration: Bool { false } + + // https://github.com/ghostty-org/ghostty/pull/11276 + @MainActor func testSelectionFocusChange() async throws { + let app = XCUIApplication() + app.activate() + // Write dummy text to a temp file, cat it into the terminal, then clean up + let lines = (1...200).map { "Line \($0): The quick brown fox jumps over the lazy dog. Lorem ipsum dolor sit amet, consectetur adipiscing elit." } + let text = lines.joined(separator: "\n") + "\n" + let tmpFile = NSTemporaryDirectory() + "ghostty_test_dummy.txt" + try text.write(toFile: tmpFile, atomically: true, encoding: .utf8) + defer { try? FileManager.default.removeItem(atPath: tmpFile) } + + app.typeText("cat \(tmpFile)\r") + app.menuItems["Command Palette"].firstMatch.click() + + let finder = XCUIApplication(bundleIdentifier: "com.apple.finder") + finder.activate() + + app.activate() + + app.buttons + .containing(NSPredicate(format: "label CONTAINS[c] 'Clear Screen'")) + .firstMatch + .click() + let surface = app.groups["Terminal pane"] + surface + .coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: 20, dy: 10)) + .click() + + surface + .coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: 20, dy: surface.frame.height * 0.5)) + .hover() + + NSPasteboard.general.clearContents() + app.typeKey("c", modifierFlags: .command) + + XCTAssertEqual(NSPasteboard.general.string(forType: .string), nil, "Moving mouse shouldn't select any texts") + } +} + diff --git a/macos/GhosttyUITests/GhosttyWindowPositionUITests.swift b/macos/GhosttyUITests/GhosttyWindowPositionUITests.swift new file mode 100644 --- /dev/null +++ b/macos/GhosttyUITests/GhosttyWindowPositionUITests.swift @@ -0,0 +1,331 @@ +// +// GhosttyWindowPositionUITests.swift +// GhosttyUITests +// +// Created by Claude on 2026-03-11. +// + +import XCTest + +final class GhosttyWindowPositionUITests: GhosttyCustomConfigCase { + override static var runsForEachTargetApplicationUIConfiguration: Bool { false } + + // MARK: - Cascading + + @MainActor func testWindowCascading() async throws { + try updateConfig( + """ + window-width = 30 + window-height = 10 + title = "GhosttyWindowPositionUITests" + """ + ) + + let app = try ghosttyApplication() + // Suppress Restoration + app.launchArguments += ["-NSQuitAlwaysKeepsWindows", "NO"] + // Clean run + app.launchEnvironment["GHOSTTY_CLEAR_USER_DEFAULTS"] = "YES" + + app.launch() // window in the center + +// app.menuBarItems["Window"].firstMatch.click() +// app.menuItems["_zoomTopLeft:"].firstMatch.click() +// +// // wait for the animation to finish +// try await Task.sleep(for: .seconds(0.5)) + + let window = app.windows.firstMatch + let windowFrame = window.frame +// XCTAssertEqual(windowFrame.minX, 0, "Window should be on the left") + + app.typeKey("n", modifierFlags: [.command]) + + let window2 = app.windows.firstMatch + XCTAssertTrue(window2.waitForExistence(timeout: 5), "New window should appear") + let windowFrame2 = window2.frame + XCTAssertNotEqual(windowFrame, windowFrame2, "New window should have moved") + + XCTAssertEqual(windowFrame2.minX, windowFrame.minX + 30, accuracy: 5, "New window should be on the right") + + XCTAssertEqual(windowFrame2.minY, windowFrame.minY + 30, accuracy: 5, "New window should be on the bottom right") + + app.typeKey("n", modifierFlags: [.command]) + + let window3 = app.windows.firstMatch + XCTAssertTrue(window3.waitForExistence(timeout: 5), "New window should appear") + let windowFrame3 = window3.frame + XCTAssertNotEqual(windowFrame2, windowFrame3, "New window should have moved") + + XCTAssertEqual(windowFrame3.minX, windowFrame2.minX + 30, accuracy: 5, "New window should be on the right") + + XCTAssertEqual(windowFrame3.minY, windowFrame2.minY + 30, accuracy: 5, "New window should be on the bottom right") + + app.typeKey("n", modifierFlags: [.command]) + + let window4 = app.windows.firstMatch + XCTAssertTrue(window4.waitForExistence(timeout: 5), "New window should appear") + let windowFrame4 = window4.frame + XCTAssertNotEqual(windowFrame3, windowFrame4, "New window should have moved") + + XCTAssertEqual(windowFrame4.minX, windowFrame3.minX + 30, accuracy: 5, "New window should be on the right") + + XCTAssertEqual(windowFrame4.minY, windowFrame3.minY + 30, accuracy: 5, "New window should be on the bottom right") + } + + @MainActor func testDragSplitWindowPosition() async throws { + try updateConfig( + """ + window-width = 40 + window-height = 20 + title = "GhosttyWindowPositionUITests" + macos-titlebar-style = hidden + """ + ) + + let app = try ghosttyApplication() + // Suppress Restoration + app.launchArguments += ["-NSQuitAlwaysKeepsWindows", "NO"] + // Clean run + app.launchEnvironment["GHOSTTY_CLEAR_USER_DEFAULTS"] = "YES" + + app.launch() // window in the center + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "New window should appear") + + // remove fixed size + try updateConfig( + """ + title = "GhosttyWindowPositionUITests" + macos-titlebar-style = hidden + """ + ) + app.typeKey(",", modifierFlags: [.command, .shift]) + + app.typeKey("d", modifierFlags: [.command]) + + let rightSplit = app.groups["Right pane"] + let rightFrame = rightSplit.frame + + let sourcePos = rightSplit.coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: rightFrame.size.width / 2, dy: 3)) + + let targetPos = rightSplit.coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: rightFrame.size.width + 100, dy: 0)) + + sourcePos.click(forDuration: 0.2, thenDragTo: targetPos) + + let window2 = app.windows.firstMatch + XCTAssertTrue(window2.waitForExistence(timeout: 5), "New window should appear") + let windowFrame2 = window2.frame + + try await Task.sleep(for: .seconds(0.5)) + + XCTAssertEqual(windowFrame2.minX, rightFrame.maxX + 100, accuracy: 5, "New window should be target position") + XCTAssertEqual(windowFrame2.minY, rightFrame.minY, accuracy: 5, "New window should be target position") + XCTAssertEqual(windowFrame2.width, rightFrame.width, accuracy: 5, "New window should use size from config") + XCTAssertEqual(windowFrame2.height, rightFrame.height, accuracy: 5, "New window should use size from config") + } + + @MainActor func testDragSplitWindowPositionWithFixedSize() async throws { + try updateConfig( + """ + window-width = 40 + window-height = 20 + title = "GhosttyWindowPositionUITests" + macos-titlebar-style = hidden + """ + ) + + let app = try ghosttyApplication() + // Suppress Restoration + app.launchArguments += ["-NSQuitAlwaysKeepsWindows", "NO"] + // Clean run + app.launchEnvironment["GHOSTTY_CLEAR_USER_DEFAULTS"] = "YES" + + app.launch() // window in the center + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "New window should appear") + let windowFrame = window.frame + + app.typeKey("d", modifierFlags: [.command]) + + let rightSplit = app.groups["Right pane"] + let rightFrame = rightSplit.frame + + let sourcePos = rightSplit.coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: rightFrame.size.width / 2, dy: 3)) + + let targetPos = rightSplit.coordinate(withNormalizedOffset: .zero) + .withOffset(.init(dx: rightFrame.size.width + 100, dy: 0)) + + sourcePos.click(forDuration: 0.2, thenDragTo: targetPos) + + let window2 = app.windows.firstMatch + XCTAssertTrue(window2.waitForExistence(timeout: 5), "New window should appear") + let windowFrame2 = window2.frame + + try await Task.sleep(for: .seconds(0.5)) + + XCTAssertEqual(windowFrame2.minX, rightFrame.maxX + 100, accuracy: 5, "New window should be target position") + XCTAssertEqual(windowFrame2.minY, rightFrame.minY, accuracy: 5, "New window should be target position") + XCTAssertEqual(windowFrame2.width, windowFrame.width, accuracy: 5, "New window should use size from config") + // We're still using right frame, because of the debug banner + XCTAssertEqual(windowFrame2.height, rightFrame.height, accuracy: 5, "New window should use size from config") + } + + // MARK: - Restore round-trip per titlebar style + + @MainActor func testRestoredNative() throws { try runRestoreTest(titlebarStyle: "native") } + @MainActor func testRestoredHidden() throws { try runRestoreTest(titlebarStyle: "hidden") } + @MainActor func testRestoredTransparent() throws { try runRestoreTest(titlebarStyle: "transparent") } + @MainActor func testRestoredTabs() throws { try runRestoreTest(titlebarStyle: "tabs") } + + // MARK: - Config overrides cached position/size + + @MainActor + func testConfigOverridesCachedPositionAndSize() async throws { + // Launch maximized so the cached frame is fullscreen-sized. + try updateConfig( + """ + maximize = true + title = "GhosttyWindowPositionUITests" + """ + ) + + let app = try ghosttyApplication() + app.launch() + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "Window should appear") + + let maximizedFrame = window.frame + + // Now update the config with a small explicit size and position, + // reload, and open a new window. It should respect the config, not the cache. + try updateConfig( + """ + window-position-x = 50 + window-position-y = 50 + window-width = 30 + window-height = 30 + title = "GhosttyWindowPositionUITests" + """ + ) + app.typeKey(",", modifierFlags: [.command, .shift]) + try await Task.sleep(for: .seconds(0.5)) + app.typeKey("n", modifierFlags: [.command]) + + XCTAssertEqual(app.windows.count, 2, "Should have 2 windows") + let newWindow = app.windows.element(boundBy: 0) + let newFrame = newWindow.frame + + // The new window should be smaller than the maximized one. + XCTAssertLessThan(newFrame.size.width, maximizedFrame.size.width, + "30 columns should be narrower than maximized") + XCTAssertLessThan(newFrame.size.height, maximizedFrame.size.height, + "30 rows should be shorter than maximized") + + app.terminate() + } + + // MARK: - Size-only config change preserves position + + @MainActor + func testSizeOnlyConfigPreservesPosition() async throws { + // Launch maximized so the window has a known position (top-left of visible frame). + try updateConfig( + """ + maximize = true + title = "GhosttyWindowPositionUITests" + """ + ) + + let app = try ghosttyApplication() + app.launch() + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "Window should appear") + + let initialFrame = window.frame + + // Reload with only size changed, close current window, open new one. + // Position should be restored from cache. + try updateConfig( + """ + window-width = 30 + window-height = 30 + title = "GhosttyWindowPositionUITests" + """ + ) + app.typeKey(",", modifierFlags: [.command, .shift]) + try await Task.sleep(for: .seconds(0.5)) + app.typeKey("w", modifierFlags: [.command]) + app.typeKey("n", modifierFlags: [.command]) + + let newWindow = app.windows.firstMatch + XCTAssertTrue(newWindow.waitForExistence(timeout: 5), "New window should appear") + + let newFrame = newWindow.frame + + // Position should be preserved from the cached value. + // Compare x and maxY since the window is anchored at the top-left + // but AppKit uses bottom-up coordinates (origin.y changes with height). + XCTAssertEqual(newFrame.origin.x, initialFrame.origin.x, accuracy: 2, + "x position should not change with size-only config") + XCTAssertEqual(newFrame.maxY, initialFrame.maxY, accuracy: 2, + "top edge (maxY) should not change with size-only config") + + app.terminate() + } + + // MARK: - Shared round-trip helper + + /// Opens a new window, records its frame, closes it, opens another, + /// and verifies the frame is restored consistently. + private func runRestoreTest(titlebarStyle: String) throws { + try updateConfig( + """ + macos-titlebar-style = \(titlebarStyle) + title = "GhosttyWindowPositionUITests" + """ + ) + + let app = try ghosttyApplication() + // Suppress Restoration + app.launchArguments += ["-NSQuitAlwaysKeepsWindows", "NO"] + // Clean run + app.launchEnvironment["GHOSTTY_CLEAR_USER_DEFAULTS"] = "YES" + app.launch() + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5), "Window should appear") + + let firstFrame = window.frame + let screenFrame = NSScreen.main?.frame ?? .zero + + XCTAssertEqual(firstFrame.midX, screenFrame.midX, accuracy: 5.0, "First window should be centered horizontally") + + // Close the window and open a new one — it should restore the same frame. + app.typeKey("w", modifierFlags: [.command]) + app.typeKey("n", modifierFlags: [.command]) + + let window2 = app.windows.firstMatch + XCTAssertTrue(window2.waitForExistence(timeout: 5), "New window should appear") + + let restoredFrame = window2.frame + + XCTAssertEqual(restoredFrame.origin.x, firstFrame.origin.x, accuracy: 2, + "[\(titlebarStyle)] x position should be restored") + XCTAssertEqual(restoredFrame.origin.y, firstFrame.origin.y, accuracy: 2, + "[\(titlebarStyle)] y position should be restored") + XCTAssertEqual(restoredFrame.size.width, firstFrame.size.width, accuracy: 2, + "[\(titlebarStyle)] width should be restored") + XCTAssertEqual(restoredFrame.size.height, firstFrame.size.height, accuracy: 2, + "[\(titlebarStyle)] height should be restored") + + app.terminate() + } +} diff --git a/pkg/dcimgui/build.zig b/pkg/dcimgui/build.zig --- a/pkg/dcimgui/build.zig +++ b/pkg/dcimgui/build.zig @@ -26,7 +26,14 @@ .linkage = .static, }); lib.linkLibC(); - lib.linkLibCpp(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } b.installArtifact(lib); // Zig module diff --git a/pkg/freetype/build.zig b/pkg/freetype/build.zig --- a/pkg/freetype/build.zig +++ b/pkg/freetype/build.zig @@ -84,11 +84,14 @@ "-DFT_CONFIG_OPTION_SYSTEM_ZLIB=1", - "-DHAVE_UNISTD_H", - "-DHAVE_FCNTL_H", - "-fno-sanitize=undefined", }); + if (target.result.os.tag != .windows) { + try flags.appendSlice(b.allocator, &.{ + "-DHAVE_UNISTD_H", + "-DHAVE_FCNTL_H", + }); + } if (target.result.os.tag == .freebsd or target.result.abi == .musl) { try flags.append(b.allocator, "-fPIC"); diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -52,7 +52,7 @@ /// Select a given charmap by its encoding tag (as listed in freetype.h). pub fn selectCharmap(self: Face, encoding: Encoding) Error!void { - return intToError(c.FT_Select_Charmap(self.handle, @intFromEnum(encoding))); + return intToError(c.FT_Select_Charmap(self.handle, @intCast(@intFromEnum(encoding)))); } /// Call FT_Request_Size to request the nominal size (in points). @@ -99,7 +99,7 @@ pub fn renderGlyph(self: Face, render_mode: RenderMode) Error!void { return intToError(c.FT_Render_Glyph( self.handle.*.glyph, - @intFromEnum(render_mode), + @intCast(@intFromEnum(render_mode)), )); } diff --git a/pkg/glslang/build.zig b/pkg/glslang/build.zig --- a/pkg/glslang/build.zig +++ b/pkg/glslang/build.zig @@ -51,7 +51,14 @@ .linkage = .static, }); lib.linkLibC(); - lib.linkLibCpp(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (upstream_) |upstream| lib.addIncludePath(upstream.path("")); lib.addIncludePath(b.path("override")); if (target.result.os.tag.isDarwin()) { @@ -65,6 +72,10 @@ "-fno-sanitize=undefined", "-fno-sanitize-trap=undefined", }); + // MSVC requires explicit std specification otherwise C++17 features + // like std::variant, std::filesystem, and inline variables are + // guarded behind _HAS_CXX17. + try flags.append(b.allocator, "-std=c++17"); if (target.result.os.tag == .freebsd or target.result.abi == .musl) { try flags.append(b.allocator, "-fPIC"); diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig --- a/pkg/harfbuzz/build.zig +++ b/pkg/harfbuzz/build.zig @@ -103,7 +103,14 @@ .linkage = .static, }); lib.linkLibC(); - lib.linkLibCpp(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (target.result.os.tag.isDarwin()) { try apple_sdk.addPaths(b, lib); diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -20,7 +20,15 @@ }), .linkage = .static, }); - lib.linkLibCpp(); + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (upstream_) |upstream| { lib.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); diff --git a/pkg/oniguruma/build.zig b/pkg/oniguruma/build.zig --- a/pkg/oniguruma/build.zig +++ b/pkg/oniguruma/build.zig @@ -68,6 +68,7 @@ .linkage = .static, }); const t = target.result; + const is_windows = t.os.tag == .windows; lib.linkLibC(); if (target.result.os.tag.isDarwin()) { @@ -86,13 +87,13 @@ .PACKAGE_VERSION = "6.9.9", .VERSION = "6.9.9", .HAVE_ALLOCA = true, - .HAVE_ALLOCA_H = true, - .USE_CRNL_AS_LINE_TERMINATOR = false, + .HAVE_ALLOCA_H = !is_windows, + .USE_CRNL_AS_LINE_TERMINATOR = is_windows, .HAVE_STDINT_H = true, - .HAVE_SYS_TIMES_H = true, - .HAVE_SYS_TIME_H = true, + .HAVE_SYS_TIMES_H = !is_windows, + .HAVE_SYS_TIME_H = !is_windows, .HAVE_SYS_TYPES_H = true, - .HAVE_UNISTD_H = true, + .HAVE_UNISTD_H = !is_windows, .HAVE_INTTYPES_H = true, .SIZEOF_INT = t.cTypeByteSize(.int), .SIZEOF_LONG = t.cTypeByteSize(.long), diff --git a/pkg/oniguruma/main.zig b/pkg/oniguruma/main.zig --- a/pkg/oniguruma/main.zig +++ b/pkg/oniguruma/main.zig @@ -1,4 +1,5 @@ const initpkg = @import("init.zig"); +const match_param = @import("match_param.zig"); const regex = @import("regex.zig"); const region = @import("region.zig"); const types = @import("types.zig"); @@ -10,6 +11,7 @@ pub const init = initpkg.init; pub const deinit = initpkg.deinit; pub const Encoding = types.Encoding; +pub const MatchParam = match_param.MatchParam; pub const Regex = regex.Regex; pub const Region = region.Region; pub const Syntax = types.Syntax; diff --git a/pkg/oniguruma/match_param.zig b/pkg/oniguruma/match_param.zig new file mode 100644 --- /dev/null +++ b/pkg/oniguruma/match_param.zig @@ -0,0 +1,23 @@ +const c = @import("c.zig").c; +const errors = @import("errors.zig"); +const Error = errors.Error; + +pub const MatchParam = struct { + value: *c.OnigMatchParam, + + pub fn init() !MatchParam { + const value = c.onig_new_match_param() orelse return Error.Memory; + return .{ .value = value }; + } + + pub fn deinit(self: *MatchParam) void { + c.onig_free_match_param(self.value); + } + + pub fn setRetryLimitInSearch(self: *MatchParam, limit: usize) !void { + _ = try errors.convertError(c.onig_set_retry_limit_in_search_of_match_param( + self.value, + @intCast(limit), + )); + } +}; diff --git a/pkg/oniguruma/regex.zig b/pkg/oniguruma/regex.zig --- a/pkg/oniguruma/regex.zig +++ b/pkg/oniguruma/regex.zig @@ -3,6 +3,7 @@ const types = @import("types.zig"); const errors = @import("errors.zig"); const testEnsureInit = @import("testing.zig").ensureInit; +const MatchParam = @import("match_param.zig").MatchParam; const Region = @import("region.zig").Region; const Error = errors.Error; const ErrorInfo = errors.ErrorInfo; @@ -44,6 +45,17 @@ str: []const u8, options: Option, ) !Region { + return self.searchWithParam(str, options, null); + } + + /// Search an entire string for matches. This always returns a region + /// which may heap allocate (C allocator). + pub fn searchWithParam( + self: *Regex, + str: []const u8, + options: Option, + match_param: ?*MatchParam, + ) !Region { var region: Region = .{}; // As part of the searchAdvanced API call below, onig may allocate @@ -51,7 +63,14 @@ // any errors to free that memory. errdefer region.deinit(); - _ = try self.searchAdvanced(str, 0, str.len, ®ion, options); + _ = try self.searchAdvancedWithParam( + str, + 0, + str.len, + ®ion, + options, + match_param, + ); return region; } @@ -64,15 +83,47 @@ region: *Region, options: Option, ) !usize { - const pos = try errors.convertError(c.onig_search( - self.value, - str.ptr, - str.ptr + str.len, - str.ptr + start, - str.ptr + end, - @ptrCast(region), - options.int(), - )); + return self.searchAdvancedWithParam( + str, + start, + end, + region, + options, + null, + ); + } + + /// onig_search_with_param directly + pub fn searchAdvancedWithParam( + self: *Regex, + str: []const u8, + start: usize, + end: usize, + region: *Region, + options: Option, + match_param: ?*MatchParam, + ) !usize { + const pos = try errors.convertError(if (match_param) |param| + c.onig_search_with_param( + self.value, + str.ptr, + str.ptr + str.len, + str.ptr + start, + str.ptr + end, + @ptrCast(region), + options.int(), + param.value, + ) + else + c.onig_search( + self.value, + str.ptr, + str.ptr + str.len, + str.ptr + start, + str.ptr + end, + @ptrCast(region), + options.int(), + )); return @intCast(pos); } @@ -90,4 +141,12 @@ try testing.expectEqual(@as(usize, 1), reg.count()); try testing.expectError(Error.Mismatch, re.search("hello", .{})); + + var match_param = try MatchParam.init(); + defer match_param.deinit(); + try match_param.setRetryLimitInSearch(1000); + + var reg_param = try re.searchWithParam("hello foo bar", .{}, &match_param); + defer reg_param.deinit(); + try testing.expectEqual(@as(usize, 1), reg_param.count()); } diff --git a/pkg/simdutf/build.zig b/pkg/simdutf/build.zig --- a/pkg/simdutf/build.zig +++ b/pkg/simdutf/build.zig @@ -12,7 +12,15 @@ }), .linkage = .static, }); - lib.linkLibCpp(); + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } lib.addIncludePath(b.path("vendor")); if (target.result.os.tag.isDarwin()) { diff --git a/pkg/spirv-cross/build.zig b/pkg/spirv-cross/build.zig --- a/pkg/spirv-cross/build.zig +++ b/pkg/spirv-cross/build.zig @@ -58,7 +58,14 @@ .linkage = .static, }); lib.linkLibC(); - lib.linkLibCpp(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); diff --git a/pkg/utfcpp/build.zig b/pkg/utfcpp/build.zig --- a/pkg/utfcpp/build.zig +++ b/pkg/utfcpp/build.zig @@ -12,7 +12,15 @@ }), .linkage = .static, }); - lib.linkLibCpp(); + lib.linkLibC(); + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime headers. + // The MSVC SDK include directories (added via linkLibC) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (target.result.abi != .msvc) { + lib.linkLibCpp(); + } if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); diff --git a/pkg/zlib/build.zig b/pkg/zlib/build.zig --- a/pkg/zlib/build.zig +++ b/pkg/zlib/build.zig @@ -32,8 +32,16 @@ "-DHAVE_SYS_TYPES_H", "-DHAVE_STDINT_H", "-DHAVE_STDDEF_H", - "-DZ_HAVE_UNISTD_H", }); + if (target.result.os.tag != .windows) { + try flags.append(b.allocator, "-DZ_HAVE_UNISTD_H"); + } + if (target.result.abi == .msvc) { + try flags.appendSlice(b.allocator, &.{ + "-D_CRT_SECURE_NO_DEPRECATE", + "-D_CRT_NONSTDC_NO_DEPRECATE", + }); + } lib.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, diff --git a/snap/local/launcher b/snap/local/launcher --- a/snap/local/launcher +++ b/snap/local/launcher @@ -41,7 +41,7 @@ export LD_LIBRARY_PATH=${SNAP}/usr/lib/${ARCH}:${SNAP}/usr/lib/${ARCH}/vdpau:${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:} export LIBGL_DRIVERS_PATH=${LIBGL_DRIVERS_PATH:+$LIBGL_DRIVERS_PATH:}${SNAP}/usr/lib/${ARCH}/dri/ export LIBVA_DRIVERS_PATH=${LIBVA_DRIVERS_PATH:+$LIBVA_DRIVERS_PATH:}${SNAP}/usr/lib/${ARCH}/dri/ -export __EGL_VENDOR_LIBRARY_DIRS=${__EGL_VENDOR_LIBRARY_DIRS:+$__EGL_VENDOR_LIBRARY_DIRS:}${SNAP}/usr/share/glvnd/egl_vendor.d +export __EGL_VENDOR_LIBRARY_DIRS=${__EGL_VENDOR_LIBRARY_DIRS:+$__EGL_VENDOR_LIBRARY_DIRS:}/etc/glvnd/egl_vendor.d:/usr/share/glvnd/egl_vendor.d:${SNAP}/usr/share/glvnd/egl_vendor.d export __EGL_EXTERNAL_PLATFORM_CONFIG_DIRS=${__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS:+$__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS:}${SNAP}/usr/share/egl/egl_external_platform.d export DRIRC_CONFIGDIR=${SNAP}/usr/share/drirc.d export VK_LAYER_PATH=${VK_LAYER_PATH:+$VK_LAYER_PATH:}${SNAP}/usr/share/vulkan/implicit_layer.d/:${SNAP}/usr/share/vulkan/explicit_layer.d/ diff --git a/src/apprt/action.zig b/src/apprt/action.zig --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -201,6 +201,9 @@ /// Set the title of the target to the requested value. set_title: SetTitle, + /// Set the tab title override for the target's tab. + set_tab_title: SetTitle, + /// Set the title of the target to a prompted value. It is up to /// the apprt to prompt. The value specifies whether to prompt for the /// surface title or the tab title. @@ -375,6 +378,7 @@ render_inspector, desktop_notification, set_title, + set_tab_title, prompt_title, pwd, mouse_shape, diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -50,10 +50,11 @@ /// Callback called to handle an action. action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.c) bool, - /// Read the clipboard value. The return value must be preserved - /// by the host until the next call. If there is no valid clipboard - /// value then this should return null. - read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.c) void, + /// Read the clipboard value. Returns true if the clipboard request + /// was started and complete_clipboard_request may be called with the + /// given state pointer. Returns false if the clipboard request couldn't + /// be started (such as when no text is available for a paste request). + read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.c) bool, /// This may be called after a read clipboard call to request /// confirmation that the clipboard value is safe to read. The embedder @@ -512,7 +513,15 @@ break :wd; } - config.@"working-directory" = wd; + var wd_val: configpkg.WorkingDirectory = .{ .path = wd }; + if (wd_val.finalize(config.arenaAlloc())) |_| { + config.@"working-directory" = wd_val; + } else |err| { + log.warn( + "error finalizing working directory config dir={s} err={}", + .{ wd_val.path, err }, + ); + } } } @@ -672,14 +681,16 @@ errdefer alloc.destroy(state_ptr); state_ptr.* = state; - self.app.opts.read_clipboard( + const started = self.app.opts.read_clipboard( self.userdata, @intCast(@intFromEnum(clipboard_type)), state_ptr, ); + if (!started) { + alloc.destroy(state_ptr); + return false; + } - // Embedded apprt can't synchronously check clipboard content types, - // so we always return true to indicate the request was started. return true; } diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -13,4 +13,5 @@ @import("std").testing.refAllDecls(@This()); _ = @import("gtk/ext.zig"); _ = @import("gtk/key.zig"); + _ = @import("gtk/portal.zig"); } diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -188,7 +188,7 @@ if (prev) |p| { if (shouldInheritWorkingDirectory(context, config)) { if (try p.pwd(alloc)) |pwd| { - copy.@"working-directory" = pwd; + copy.@"working-directory" = .{ .path = pwd }; } } } diff --git a/src/benchmark/CodepointWidth.zig b/src/benchmark/CodepointWidth.zig --- a/src/benchmark/CodepointWidth.zig +++ b/src/benchmark/CodepointWidth.zig @@ -6,6 +6,7 @@ const CodepointWidth = @This(); const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Benchmark = @import("Benchmark.zig"); @@ -104,6 +105,11 @@ extern "c" fn wcwidth(c: u32) c_int; fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void { + if (comptime builtin.os.tag == .windows) { + log.warn("wcwidth is not available on Windows", .{}); + return; + } + const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; diff --git a/src/benchmark/ScreenClone.zig b/src/benchmark/ScreenClone.zig --- a/src/benchmark/ScreenClone.zig +++ b/src/benchmark/ScreenClone.zig @@ -94,9 +94,9 @@ // Force a style on every single row, which var s = self.terminal.vtStream(); defer s.deinit(); - s.nextSlice("\x1b[48;2;20;40;60m") catch unreachable; - for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n") catch unreachable; - s.nextSlice("hello") catch unreachable; + s.nextSlice("\x1b[48;2;20;40;60m"); + for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n"); + s.nextSlice("hello"); // Setup our terminal state const data_f: std.fs.File = (options.dataFile( @@ -120,10 +120,7 @@ return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached - stream.nextSlice(buf[0..n]) catch |err| { - log.warn("error processing data file chunk err={}", .{err}); - return error.BenchmarkFailed; - }; + stream.nextSlice(buf[0..n]); } } diff --git a/src/benchmark/TerminalStream.zig b/src/benchmark/TerminalStream.zig --- a/src/benchmark/TerminalStream.zig +++ b/src/benchmark/TerminalStream.zig @@ -125,10 +125,7 @@ return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached - self.stream.nextSlice(buf[0..n]) catch |err| { - log.warn("error processing data file chunk err={}", .{err}); - return error.BenchmarkFailed; - }; + self.stream.nextSlice(buf[0..n]); } } @@ -142,9 +139,11 @@ self: *Handler, comptime action: Stream.Action.Tag, value: Stream.Action.Value(action), - ) !void { + ) void { switch (action) { - .print => try self.t.print(value.cp), + .print => self.t.print(value.cp) catch |err| { + log.warn("error processing benchmark print err={}", .{err}); + }, else => {}, } } diff --git a/src/build/Config.zig b/src/build/Config.zig --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -51,6 +51,7 @@ emit_docs: bool = false, emit_exe: bool = false, emit_helpgen: bool = false, +emit_lib_vt: bool = false, emit_macos_app: bool = false, emit_terminfo: bool = false, emit_termcap: bool = false, @@ -59,6 +60,10 @@ emit_xcframework: bool = false, emit_webdata: bool = false, emit_unicode_table_gen: bool = false, + +/// True when Ghostty is being built as a dependency of another project +/// rather than as the root project. +is_dep: bool = false, /// Environmental properties env: std.process.EnvMap, @@ -78,6 +83,19 @@ result = genericMacOSTarget(b, result.query.cpu_arch); } + // On Windows, default to the MSVC ABI so that produced COFF + // objects (including compiler_rt) are compatible with the MSVC + // linker. Zig defaults to the GNU ABI which produces objects + // with invalid COMDAT sections that MSVC rejects (LNK1143). + // Only override when no explicit ABI was requested. + if (result.result.os.tag == .windows and + result.query.abi == null) + { + var query = result.query; + query.abi = .msvc; + result = b.resolveTargetQuery(query); + } + // If we have no minimum OS version, we set the default based on // our tag. Not all tags have a minimum so this may be null. if (result.query.os_version_min == null) { @@ -86,6 +104,10 @@ break :target result; }; + + // Detect if Ghostty is a dependency of another project. + // dep_prefix is non-empty when this build is running as a dependency. + const is_dep = b.dep_prefix.len > 0; // This is set to true when we're building a system package. For now // this is trivially detected using the "system_package_mode" bool @@ -109,6 +131,7 @@ .optimize = optimize, .target = target, .wasm_target = wasm_target, + .is_dep = is_dep, .env = env, }; @@ -220,9 +243,7 @@ const app_version = try std.SemanticVersion.parse(appVersion); // Is ghostty a dependency? If so, skip git detection. - // @src().file won't resolve from b.build_root unless ghostty - // is the project being built. - b.build_root.handle.access(@src().file, .{}) catch break :version .{ + if (is_dep) break :version .{ .major = app_version.major, .minor = app_version.minor, .patch = app_version.patch, @@ -314,11 +335,17 @@ //--------------------------------------------------------------- // Artifacts to Emit + config.emit_lib_vt = b.option( + bool, + "emit-lib-vt", + "Set defaults for a libghostty-vt-only build (disables xcframework, macOS app, and docs).", + ) orelse false; + config.emit_exe = b.option( bool, "emit-exe", "Build and install main executables with 'build'", - ) orelse true; + ) orelse !config.emit_lib_vt; config.emit_test_exe = b.option( bool, @@ -352,7 +379,8 @@ // If we are emitting any other artifacts then we default to false. if (config.emit_bench or config.emit_test_exe or - config.emit_helpgen) break :emit_docs false; + config.emit_helpgen or + config.emit_lib_vt) break :emit_docs false; // We always emit docs in system package mode. if (system_package) break :emit_docs true; @@ -401,7 +429,8 @@ bool, "emit-xcframework", "Build and install the xcframework for the macOS library.", - ) orelse builtin.target.os.tag.isDarwin() and + ) orelse !config.emit_lib_vt and + builtin.target.os.tag.isDarwin() and target.result.os.tag == .macos and config.app_runtime == .none and (!config.emit_bench and @@ -412,7 +441,7 @@ bool, "emit-macos-app", "Build and install the macOS app bundle.", - ) orelse config.emit_xcframework; + ) orelse !config.emit_lib_vt and config.emit_xcframework; //--------------------------------------------------------------- // System Packages diff --git a/src/build/GhosttyExe.zig b/src/build/GhosttyExe.zig --- a/src/build/GhosttyExe.zig +++ b/src/build/GhosttyExe.zig @@ -29,8 +29,9 @@ // Set PIE if requested if (cfg.pie) exe.pie = true; - // Add the shared dependencies - _ = try deps.add(exe); + // Add the shared dependencies. When building only lib-vt we skip + // heavy deps so cross-compilation doesn't pull in GTK, etc. + if (!cfg.emit_lib_vt) _ = try deps.add(exe); // Check for possible issues try checkNixShell(exe, cfg); diff --git a/src/build/GhosttyLib.zig b/src/build/GhosttyLib.zig --- a/src/build/GhosttyLib.zig +++ b/src/build/GhosttyLib.zig @@ -39,6 +39,12 @@ lib.bundle_compiler_rt = true; lib.bundle_ubsan_rt = true; + if (deps.config.target.result.os.tag == .windows) { + // Zig's ubsan emits /exclude-symbols linker directives that + // are incompatible with the MSVC linker (LNK4229). + lib.bundle_ubsan_rt = false; + } + // Add our dependencies. Get the list of all static deps so we can // build a combined archive if necessary. var lib_list = try deps.add(lib); diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -12,10 +12,21 @@ /// The artifact result artifact: *std.Build.Step.InstallArtifact, +/// The kind of library +kind: Kind, + /// The final library file output: std.Build.LazyPath, dsym: ?std.Build.LazyPath, pkg_config: ?std.Build.LazyPath, + +/// The kind of library being built. This is similar to LinkMode but +/// also includes wasm which is an executable, not a library. +const Kind = enum { + wasm, + shared, + static, +}; pub fn initWasm( b: *std.Build, @@ -39,20 +50,40 @@ return .{ .step = &exe.step, .artifact = b.addInstallArtifact(exe, .{}), + .kind = .wasm, .output = exe.getEmittedBin(), .dsym = null, .pkg_config = null, }; } +pub fn initStatic( + b: *std.Build, + zig: *const GhosttyZig, +) !GhosttyLibVt { + return initLib(b, zig, .static); +} + pub fn initShared( b: *std.Build, zig: *const GhosttyZig, ) !GhosttyLibVt { + return initLib(b, zig, .dynamic); +} + +fn initLib( + b: *std.Build, + zig: *const GhosttyZig, + linkage: std.builtin.LinkMode, +) !GhosttyLibVt { + const kind: Kind = switch (linkage) { + .static => .static, + .dynamic => .shared, + }; const target = zig.vt.resolved_target.?; const lib = b.addLibrary(.{ - .name = "ghostty-vt", - .linkage = .dynamic, + .name = if (kind == .static) "ghostty-vt-static" else "ghostty-vt", + .linkage = linkage, .root_module = zig.vt_c, .version = std.SemanticVersion{ .major = 0, .minor = 1, .patch = 0 }, }); @@ -61,6 +92,25 @@ "ghostty", .{ .include_extensions = &.{".h"} }, ); + + if (kind == .static) { + // These must be bundled since we're compiling into a static lib. + // Otherwise, you get undefined symbol errors. This could cause + // problems if you're linking multiple static Zig libraries but + // we'll cross that bridge when we get to it. + lib.bundle_compiler_rt = true; + lib.bundle_ubsan_rt = true; + + // Enable PIC so the static library can be linked into PIE + // executables, which is the default on most Linux distributions. + lib.root_module.pic = true; + } + + if (target.result.os.tag == .windows) { + // Zig's ubsan emits /exclude-symbols linker directives that + // are incompatible with the MSVC linker (LNK4229). + lib.bundle_ubsan_rt = false; + } if (lib.rootModuleTarget().abi.isAndroid()) { // Support 16kb page sizes, required for Android 15+. @@ -82,11 +132,10 @@ if (builtin.os.tag.isDarwin()) try @import("apple_sdk").addPaths(b, lib); } - // Get our debug symbols + // Get our debug symbols (only for shared libs; static libs aren't linked) const dsymutil: ?std.Build.LazyPath = dsymutil: { - if (!target.result.os.tag.isDarwin()) { - break :dsymutil null; - } + if (kind != .shared) break :dsymutil null; + if (!target.result.os.tag.isDarwin()) break :dsymutil null; const dsymutil = RunStep.create(b, "dsymutil"); dsymutil.addArgs(&.{"dsymutil"}); @@ -116,6 +165,7 @@ return .{ .step = &lib.step, .artifact = b.addInstallArtifact(lib, .{}), + .kind = kind, .output = lib.getEmittedBin(), .dsym = dsymutil, .pkg_config = pc, diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -104,6 +104,8 @@ "test", "-scheme", "Ghostty", + "-skip-testing", + "GhosttyUITests", }); if (xc_arch) |arch| step.addArgs(&.{ "-arch", arch }); diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -64,8 +64,12 @@ .optimize = cfg.optimize, // SIMD require libc/libcpp (both) but otherwise we don't care. + // On MSVC, we must not use linkLibCpp because Zig passes + // -nostdinc++ and adds its bundled libc++/libc++abi headers + // which conflict with MSVC's C++ runtime. The MSVC SDK dirs + // added via link_libc contain both C and C++ headers. .link_libc = if (cfg.simd) true else null, - .link_libcpp = if (cfg.simd) true else null, + .link_libcpp = if (cfg.simd and cfg.target.result.abi != .msvc) true else null, }); vt.addOptions("build_options", general_options); vt_options.add(b, vt); diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -121,7 +121,7 @@ // We don't support cross-compiling to Darwin but due to the way // lazy dependencies work with Zig, we call this function. So we just // bail. The build will fail but the build would've failed anyways. - // And this lets other non-platform-specific targets like `lib-vt` + // And this lets other non-platform-specific targets like `-Demit-lib-vt` // cross-compile properly. if (!builtin.target.os.tag.isDarwin() and self.config.target.result.os.tag.isDarwin()) @@ -134,6 +134,33 @@ // Every exe needs the terminal options self.config.terminalOptions().add(b, step.root_module); + + // C imports needed to manage/create PTYs + switch (target.result.os.tag) { + .freebsd, + .linux, + .macos, + => { + const c = b.addTranslateC(.{ + .root_source_file = b.path("src/pty.c"), + .target = target, + .optimize = optimize, + }); + switch (target.result.os.tag) { + .macos => { + const libc = try std.zig.LibCInstallation.findNative(.{ + .allocator = b.allocator, + .target = &target.result, + .verbose = false, + }); + c.addSystemIncludePath(.{ .cwd_relative = libc.sys_include_dir.? }); + }, + else => {}, + } + step.root_module.addImport("pty-c", c.createModule()); + }, + else => {}, + } // Freetype. We always include this even if our font backend doesn't // use it because Dear Imgui uses Freetype. @@ -372,8 +399,15 @@ step.addIncludePath(b.path("src/apprt/gtk")); } - // libcpp is required for various dependencies - step.linkLibCpp(); + // libcpp is required for various dependencies. On MSVC, we must + // not use linkLibCpp because Zig unconditionally passes -nostdinc++ + // and then adds its bundled libc++/libc++abi include paths, which + // conflict with MSVC's own C++ runtime headers. The MSVC SDK + // include directories (already added via linkLibC above) contain + // both C and C++ headers, so linkLibCpp is not needed. + if (step.rootModuleTarget().abi != .msvc) { + step.linkLibCpp(); + } // We always require the system SDK so that our system headers are available. // This makes things like `os/log.h` available for cross-compiling. @@ -626,9 +660,6 @@ .wayland_protocols = wayland_protocols_dep.path(""), }); - scanner.addCustomProtocol( - plasma_wayland_protocols_dep.path("src/protocols/blur.xml"), - ); // FIXME: replace with `zxdg_decoration_v1` once GTK merges https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6398 scanner.addCustomProtocol( plasma_wayland_protocols_dep.path("src/protocols/server-decoration.xml"), @@ -636,13 +667,18 @@ scanner.addCustomProtocol( plasma_wayland_protocols_dep.path("src/protocols/slide.xml"), ); + scanner.addCustomProtocol( + plasma_wayland_protocols_dep.path("src/protocols/kde-output-order-v1.xml"), + ); scanner.addSystemProtocol("staging/xdg-activation/xdg-activation-v1.xml"); + scanner.addSystemProtocol("staging/ext-background-effect/ext-background-effect-v1.xml"); scanner.generate("wl_compositor", 1); - scanner.generate("org_kde_kwin_blur_manager", 1); scanner.generate("org_kde_kwin_server_decoration_manager", 1); scanner.generate("org_kde_kwin_slide_manager", 1); + scanner.generate("kde_output_order_v1", 1); scanner.generate("xdg_activation_v1", 1); + scanner.generate("ext_background_effect_manager_v1", 1); step.root_module.addImport("wayland", b.createModule(.{ .root_source_file = scanner.result, @@ -657,10 +693,10 @@ .optimize = optimize, })) |gtk4_layer_shell| { const layer_shell_module = gtk4_layer_shell.module("gtk4-layer-shell"); - if (gobject_) |gobject| layer_shell_module.addImport( - "gtk", - gobject.module("gtk4"), - ); + if (gobject_) |gobject| { + layer_shell_module.addImport("gtk", gobject.module("gtk4")); + layer_shell_module.addImport("gdk", gobject.module("gdk4")); + } step.root_module.addImport( "gtk4-layer-shell", layer_shell_module, @@ -754,12 +790,35 @@ const HWY_AVX3_DL: c_int = 1 << 7; const HWY_AVX3: c_int = 1 << 8; + var flags: std.ArrayListUnmanaged([]const u8) = .empty; + // Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414 // To workaround this we just disable AVX512 support completely. // The performance difference between AVX2 and AVX512 is not // significant for our use case and AVX512 is very rare on consumer // hardware anyways. const HWY_DISABLED_TARGETS: c_int = HWY_AVX10_2 | HWY_AVX3_SPR | HWY_AVX3_ZEN4 | HWY_AVX3_DL | HWY_AVX3; + if (target.result.cpu.arch == .x86_64) try flags.append( + b.allocator, + b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), + ); + + // MSVC requires explicit std specification otherwise these + // are guarded, at least on Windows 2025. Doing it unconditionally + // doesn't cause any issues on other platforms and ensures we get + // C++17 support on MSVC. + try flags.append( + b.allocator, + "-std=c++17", + ); + + // Disable ubsan for MSVC to avoid undefined references to + // __ubsan_handle_* symbols that require a runtime we don't link + // and bundle. Hopefully we can fix this one day since ubsan is nice! + if (target.result.abi == .msvc) try flags.appendSlice(b.allocator, &.{ + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", + }); m.addCSourceFiles(.{ .files = &.{ @@ -768,9 +827,7 @@ "src/simd/index_of.cpp", "src/simd/vt.cpp", }, - .flags = if (target.result.cpu.arch == .x86_64) &.{ - b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}), - } else &.{}, + .flags = flags.items, }); } } diff --git a/src/cli/CommaSplitter.zig b/src/cli/CommaSplitter.zig --- a/src/cli/CommaSplitter.zig +++ b/src/cli/CommaSplitter.zig @@ -13,7 +13,17 @@ //! //! Quotes and escapes are not stripped or decoded, that must be handled as a //! separate step! +//! +//! On Windows, backslash is only treated as an escape character inside quoted +//! strings. Outside quotes, backslash is a literal character (path separator). const CommaSplitter = @This(); + +const builtin = @import("builtin"); + +/// Whether backslash acts as an escape character outside quoted strings. +/// On Windows, backslash is the path separator so it is always literal +/// outside quotes. +const escape_outside_quotes = builtin.os.tag != .windows; pub const Error = error{ UnclosedQuote, @@ -77,8 +87,11 @@ }, '\\' => { self.index += 1; - last = .normal; - continue :loop .escape; + if (comptime escape_outside_quotes) { + last = .normal; + continue :loop .escape; + } + continue :loop .normal; }, else => { self.index += 1; @@ -273,6 +286,7 @@ } test "splitter 9" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -281,6 +295,7 @@ } test "splitter 10" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -289,6 +304,7 @@ } test "splitter 11" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -297,6 +313,7 @@ } test "splitter 12" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -305,6 +322,7 @@ } test "splitter 13" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -313,6 +331,7 @@ } test "splitter 14" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -330,6 +349,7 @@ } test "splitter 16" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -338,6 +358,7 @@ } test "splitter 17" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -346,6 +367,7 @@ } test "splitter 18" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; @@ -415,10 +437,47 @@ } test "splitter 25" { + if (comptime !escape_outside_quotes) return error.SkipZigTest; const std = @import("std"); const testing = std.testing; var s: CommaSplitter = .init("a,\\u{10,df}"); try testing.expectEqualStrings("a", (try s.next()).?); try testing.expectError(error.IllegalEscape, s.next()); +} + +// Windows-specific tests: backslash is literal outside quotes. + +test "splitter: windows paths" { + if (comptime escape_outside_quotes) return error.SkipZigTest; + const std = @import("std"); + const testing = std.testing; + + var s: CommaSplitter = .init("light:C:\\Users\\foo\\theme,dark:C:\\Users\\bar\\theme"); + try testing.expectEqualStrings("light:C:\\Users\\foo\\theme", (try s.next()).?); + try testing.expectEqualStrings("dark:C:\\Users\\bar\\theme", (try s.next()).?); + try testing.expect(null == try s.next()); +} + +test "splitter: backslash literal outside quotes on windows" { + if (comptime escape_outside_quotes) return error.SkipZigTest; + const std = @import("std"); + const testing = std.testing; + + // Backslash followed by characters that would be escapes on Unix + // are treated as literal on Windows outside quotes. + var s: CommaSplitter = .init("\\n\\r\\t"); + try testing.expectEqualStrings("\\n\\r\\t", (try s.next()).?); + try testing.expect(null == try s.next()); +} + +test "splitter: backslash still escapes inside quotes on windows" { + if (comptime escape_outside_quotes) return error.SkipZigTest; + const std = @import("std"); + const testing = std.testing; + + // Inside quotes, backslash escapes work on all platforms. + var s: CommaSplitter = .init("\"hello\\nworld\""); + try testing.expectEqualStrings("\"hello\\nworld\"", (try s.next()).?); + try testing.expect(null == try s.next()); } diff --git a/src/cli/explain_config.zig b/src/cli/explain_config.zig new file mode 100644 --- /dev/null +++ b/src/cli/explain_config.zig @@ -0,0 +1,142 @@ +const std = @import("std"); +const args = @import("args.zig"); +const Allocator = std.mem.Allocator; +const Action = @import("ghostty.zig").Action; +const help_strings = @import("help_strings"); +const Config = @import("../config/Config.zig"); +const ConfigKey = @import("../config/key.zig").Key; +const KeybindAction = @import("../input/Binding.zig").Action; + +pub const Options = struct { + /// The config option to explain. For example: + /// + /// ghostty +explain-config --option=font-size + option: ?[]const u8 = null, + + /// The keybind action to explain. For example: + /// + /// ghostty +explain-config --keybind=copy_to_clipboard + keybind: ?[]const u8 = null, + + pub fn deinit(self: Options) void { + _ = self; + } + + /// Enables `-h` and `--help` to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } +}; + +/// The `explain-config` command prints the documentation for a single +/// Ghostty configuration option or keybind action. +/// +/// Examples: +/// +/// ghostty +explain-config font-size +/// ghostty +explain-config copy_to_clipboard +/// ghostty +explain-config --option=font-size +/// ghostty +explain-config --keybind=copy_to_clipboard +/// +/// Flags: +/// +/// * `--option`: The name of the configuration option to explain. +/// * `--keybind`: The name of the keybind action to explain. +pub fn run(alloc: Allocator) !u8 { + var option_name: ?[]const u8 = null; + var keybind_name: ?[]const u8 = null; + var positional: ?[]const u8 = null; + var iter = try args.argsIterator(alloc); + defer iter.deinit(); + + while (iter.next()) |arg| { + if (std.mem.startsWith(u8, arg, "--option=")) { + option_name = arg["--option=".len..]; + } else if (std.mem.startsWith(u8, arg, "--keybind=")) { + keybind_name = arg["--keybind=".len..]; + } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { + return Action.help_error; + } else if (!std.mem.startsWith(u8, arg, "-")) { + positional = arg; + } + } + + // Resolve what to look up. Explicit flags go directly to their + // respective lookup. A bare positional argument tries config + // options first, then keybind actions as a fallback. + const name = keybind_name orelse option_name orelse positional orelse { + var stderr: std.fs.File = .stderr(); + var buffer: [4096]u8 = undefined; + var stderr_writer = stderr.writer(&buffer); + try stderr_writer.interface.writeAll("Usage: ghostty +explain-config