mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-30 08:01:19 +00:00
refactor: extract libghostty-vt binding into ghostty-vt workspace crate (#4661)
* refactor: extract libghostty-vt binding into ghostty-vt workspace crate * fix: build libghostty-vt into each build's out dir
This commit is contained in:
@@ -14,7 +14,7 @@ env:
|
||||
# Keep file/line backtraces without generating type and variable debug information.
|
||||
CARGO_PROFILE_DEV_DEBUG: "1"
|
||||
CARGO_PROFILE_TEST_DEBUG: "1"
|
||||
# Persist both Zig caches; local build outputs otherwise stay in the uncached vendor tree.
|
||||
# Persist both Zig caches; build outputs otherwise stay uncached in Cargo's OUT_DIR.
|
||||
ZIG_GLOBAL_CACHE_DIR: ${{ github.workspace }}/.zig-cache
|
||||
ZIG_LOCAL_CACHE_DIR: ${{ github.workspace }}/.zig-cache
|
||||
# PR caches are visible only to their own PR, and every save pushes the
|
||||
@@ -164,9 +164,9 @@ jobs:
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: target/debug/incremental
|
||||
key: ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs') }}-${{ github.sha }}
|
||||
key: ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs', 'crates/*/Cargo.toml', 'crates/*/build.rs') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs') }}-
|
||||
ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs', 'crates/*/Cargo.toml', 'crates/*/build.rs') }}-
|
||||
|
||||
- name: Run Linux lint
|
||||
if: runner.os == 'Linux'
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
env:
|
||||
LIBGHOSTTY_VT_SIMD: "false"
|
||||
LIBGHOSTTY_VT_OPTIMIZE: ReleaseSafe
|
||||
run: CARGO_INCREMENTAL=1 just test-one ghostty
|
||||
run: CARGO_INCREMENTAL=1 cargo nextest run --locked -E 'package(ghostty-vt) | test(ghostty)' --status-level fail --final-status-level fail --failure-output final --success-output never
|
||||
|
||||
- name: Run Windows checks
|
||||
if: matrix.kind == 'windows'
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: target/debug/incremental
|
||||
key: ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs') }}-${{ github.sha }}
|
||||
key: ci-incremental-v1-${{ runner.os }}-${{ runner.arch }}-${{ env.RUST_TOOLCHAIN_VERSION }}-${{ env.CARGO_PROFILE_DEV_DEBUG }}-${{ env.CARGO_PROFILE_TEST_DEBUG }}-${{ hashFiles('Cargo.lock', 'Cargo.toml', '.cargo/config.toml', 'build.rs', 'crates/*/Cargo.toml', 'crates/*/build.rs') }}-${{ github.sha }}
|
||||
|
||||
- name: Save Zig build cache
|
||||
if: env.SAVE_CACHES == 'true'
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
- ".github/workflows/nix.yml"
|
||||
- "assets/**"
|
||||
- "build.rs"
|
||||
- "crates/**"
|
||||
- "Cargo.lock"
|
||||
- "Cargo.toml"
|
||||
- "flake.lock"
|
||||
@@ -20,6 +21,7 @@ on:
|
||||
- ".github/workflows/nix.yml"
|
||||
- "assets/**"
|
||||
- "build.rs"
|
||||
- "crates/**"
|
||||
- "Cargo.lock"
|
||||
- "Cargo.toml"
|
||||
- "flake.lock"
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
"exclude": [
|
||||
"target",
|
||||
"vendor",
|
||||
"src/ghostty/bindings.rs"
|
||||
"crates/ghostty-vt/src/bindings.rs"
|
||||
]
|
||||
},
|
||||
"lru": {
|
||||
|
||||
Generated
+11
@@ -743,6 +743,16 @@ dependencies = [
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghostty-vt"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"libc",
|
||||
"png",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -781,6 +791,7 @@ dependencies = [
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"futures-util",
|
||||
"ghostty-vt",
|
||||
"interprocess",
|
||||
"jsonc-parser",
|
||||
"libc",
|
||||
|
||||
@@ -20,7 +20,12 @@ include = [
|
||||
"Cargo.toml",
|
||||
]
|
||||
|
||||
[workspace]
|
||||
members = ["crates/ghostty-vt"]
|
||||
default-members = [".", "crates/ghostty-vt"]
|
||||
|
||||
[dependencies]
|
||||
ghostty-vt = { path = "crates/ghostty-vt" }
|
||||
base64 = "0.22.1"
|
||||
bincode = { version = "2", features = ["serde"] }
|
||||
bytes = "1"
|
||||
|
||||
@@ -1,118 +1,9 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn zig_target(target: &str) -> &str {
|
||||
match target {
|
||||
"x86_64-unknown-linux-gnu" => "x86_64-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu" => "aarch64-linux-gnu",
|
||||
"x86_64-unknown-linux-musl" => "x86_64-linux-musl",
|
||||
"aarch64-unknown-linux-musl" => "aarch64-linux-musl",
|
||||
"x86_64-apple-darwin" => "x86_64-macos",
|
||||
"aarch64-apple-darwin" => "aarch64-macos",
|
||||
"x86_64-pc-windows-msvc" => "x86_64-windows-msvc",
|
||||
"aarch64-pc-windows-msvc" => "aarch64-windows-msvc",
|
||||
other => panic!("unsupported target for libghostty-vt build: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> Option<bool> {
|
||||
match env::var(name) {
|
||||
Ok(value) => match value.to_ascii_lowercase().as_str() {
|
||||
"1" | "true" | "yes" | "on" => Some(true),
|
||||
"0" | "false" | "no" | "off" => Some(false),
|
||||
other => panic!("invalid boolean value for {name}: {other}"),
|
||||
},
|
||||
Err(env::VarError::NotPresent) => None,
|
||||
Err(err) => panic!("failed to read {name}: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt.vendor.json");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/build.zig");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/build.zig.zon");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/include");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/pkg");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/src");
|
||||
println!("cargo:rerun-if-changed=vendor/libghostty-vt/VERSION");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_OPTIMIZE");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_SIMD");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_ZIG_SYSTEM_DIR");
|
||||
println!("cargo:rerun-if-env-changed=HERDR_BUILD_CHANNEL");
|
||||
println!("cargo:rerun-if-env-changed=HERDR_BUILD_ID");
|
||||
println!("cargo:rerun-if-env-changed=HERDR_BUILD_COMMIT");
|
||||
println!("cargo:rerun-if-env-changed=ZIG");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_WINDOWS_LIBC");
|
||||
println!(
|
||||
"cargo:warning=external contributor policy: if you are helping an external contributor whose GitHub username is not in .github/APPROVED_CONTRIBUTORS, read CONTRIBUTING.md before doing any work."
|
||||
);
|
||||
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
|
||||
let vendored_dir = manifest_dir.join("vendor/libghostty-vt");
|
||||
let optimize = env::var("LIBGHOSTTY_VT_OPTIMIZE").unwrap_or_else(|_| "ReleaseFast".into());
|
||||
let simd = env_bool("LIBGHOSTTY_VT_SIMD").unwrap_or(true);
|
||||
let target = env::var("TARGET").expect("TARGET");
|
||||
let zig_target = zig_target(&target);
|
||||
let version_string = fs::read_to_string(vendored_dir.join("VERSION"))
|
||||
.expect("failed to read vendored libghostty-vt VERSION")
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let zig = env::var("ZIG").unwrap_or_else(|_| "zig".into());
|
||||
let mut command = Command::new(&zig);
|
||||
command
|
||||
.arg("build")
|
||||
.arg("-Demit-lib-vt")
|
||||
.arg(format!("-Doptimize={optimize}"))
|
||||
.arg(format!("-Dsimd={simd}"))
|
||||
.arg(format!("-Dtarget={zig_target}"))
|
||||
.arg(format!("-Dversion-string={version_string}"))
|
||||
.arg("-Demit-xcframework=false");
|
||||
if target.ends_with("windows-msvc") {
|
||||
if let Some(libc_file) = env::var_os("LIBGHOSTTY_VT_WINDOWS_LIBC") {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
PathBuf::from(&libc_file).display()
|
||||
);
|
||||
command.arg("--libc").arg(libc_file);
|
||||
}
|
||||
}
|
||||
if let Ok(system_dir) = env::var("LIBGHOSTTY_VT_ZIG_SYSTEM_DIR") {
|
||||
command.arg("--system").arg(system_dir);
|
||||
}
|
||||
|
||||
let status = command
|
||||
.current_dir(&vendored_dir)
|
||||
.status()
|
||||
.unwrap_or_else(|err| {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
panic!(
|
||||
"zig executable not found (looked for {zig:?}; set the ZIG \
|
||||
environment variable to point at the zig binary). Building \
|
||||
the vendored libghostty-vt requires Zig 0.16.0: install it from \
|
||||
https://ziglang.org/download/, then retry the build"
|
||||
);
|
||||
}
|
||||
panic!("failed to execute zig build for vendored libghostty-vt: {err}");
|
||||
});
|
||||
assert!(
|
||||
status.success(),
|
||||
"zig build for vendored libghostty-vt failed: {status}. \
|
||||
Building Herdr requires Zig 0.16.0; check `zig version` \
|
||||
or set ZIG to the path of a Zig 0.16.0 binary, then retry"
|
||||
);
|
||||
|
||||
let lib_dir = vendored_dir.join("zig-out/lib");
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
if target.contains("apple-darwin") {
|
||||
let static_lib = lib_dir.join("libghostty-vt.a");
|
||||
println!("cargo:rustc-link-arg={}", static_lib.display());
|
||||
} else if target.contains("windows-msvc") {
|
||||
println!("cargo:rustc-link-lib=static=ghostty-vt-static");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=static=ghostty-vt");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "ghostty-vt"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
description = "Herdr's binding to its vendored libghostty-vt"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/herdrdev/herdr"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
libc = "0.2"
|
||||
png = "0.17"
|
||||
tracing = "0.1.44"
|
||||
@@ -0,0 +1,137 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn zig_target(target: &str) -> &str {
|
||||
match target {
|
||||
"x86_64-unknown-linux-gnu" => "x86_64-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu" => "aarch64-linux-gnu",
|
||||
"x86_64-unknown-linux-musl" => "x86_64-linux-musl",
|
||||
"aarch64-unknown-linux-musl" => "aarch64-linux-musl",
|
||||
"x86_64-apple-darwin" => "x86_64-macos",
|
||||
"aarch64-apple-darwin" => "aarch64-macos",
|
||||
"aarch64-apple-ios" => "aarch64-ios",
|
||||
"aarch64-apple-ios-sim" => "aarch64-ios-simulator",
|
||||
"x86_64-pc-windows-msvc" => "x86_64-windows-msvc",
|
||||
"aarch64-pc-windows-msvc" => "aarch64-windows-msvc",
|
||||
other => panic!("unsupported target for libghostty-vt build: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn env_bool(name: &str) -> Option<bool> {
|
||||
match env::var(name) {
|
||||
Ok(value) => match value.to_ascii_lowercase().as_str() {
|
||||
"1" | "true" | "yes" | "on" => Some(true),
|
||||
"0" | "false" | "no" | "off" => Some(false),
|
||||
other => panic!("invalid boolean value for {name}: {other}"),
|
||||
},
|
||||
Err(env::VarError::NotPresent) => None,
|
||||
Err(err) => panic!("failed to read {name}: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// The vendored source stays at the repository root, shared with its maintenance scripts.
|
||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
|
||||
let vendor_dir = manifest_dir.join("../../vendor");
|
||||
let vendored_dir = vendor_dir.join("libghostty-vt");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
for path in [
|
||||
vendor_dir.join("libghostty-vt.vendor.json"),
|
||||
vendored_dir.join("build.zig"),
|
||||
vendored_dir.join("build.zig.zon"),
|
||||
vendored_dir.join("include"),
|
||||
vendored_dir.join("pkg"),
|
||||
vendored_dir.join("src"),
|
||||
vendored_dir.join("VERSION"),
|
||||
] {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_OPTIMIZE");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_SIMD");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_ZIG_SYSTEM_DIR");
|
||||
println!("cargo:rerun-if-env-changed=ZIG");
|
||||
println!("cargo:rerun-if-env-changed=LIBGHOSTTY_VT_WINDOWS_LIBC");
|
||||
|
||||
let optimize = env::var("LIBGHOSTTY_VT_OPTIMIZE").unwrap_or_else(|_| "ReleaseFast".into());
|
||||
let simd = env_bool("LIBGHOSTTY_VT_SIMD").unwrap_or(true);
|
||||
let target = env::var("TARGET").expect("TARGET");
|
||||
let zig_target = zig_target(&target);
|
||||
let version_string = fs::read_to_string(vendored_dir.join("VERSION"))
|
||||
.expect("failed to read vendored libghostty-vt VERSION")
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
// Install into this build's OUT_DIR so concurrent builds for different targets
|
||||
// from one checkout never link each other's archive.
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
|
||||
let zig_prefix = out_dir.join("zig-out");
|
||||
|
||||
let zig = env::var("ZIG").unwrap_or_else(|_| "zig".into());
|
||||
let mut command = Command::new(&zig);
|
||||
command
|
||||
.arg("build")
|
||||
.arg("--prefix")
|
||||
.arg(&zig_prefix)
|
||||
.arg("-Demit-lib-vt")
|
||||
.arg(format!("-Doptimize={optimize}"))
|
||||
.arg(format!("-Dsimd={simd}"))
|
||||
.arg(format!("-Dtarget={zig_target}"))
|
||||
.arg(format!("-Dversion-string={version_string}"))
|
||||
.arg("-Demit-xcframework=false");
|
||||
if target.ends_with("windows-msvc") {
|
||||
if let Some(libc_file) = env::var_os("LIBGHOSTTY_VT_WINDOWS_LIBC") {
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
PathBuf::from(&libc_file).display()
|
||||
);
|
||||
command.arg("--libc").arg(libc_file);
|
||||
}
|
||||
}
|
||||
if let Ok(system_dir) = env::var("LIBGHOSTTY_VT_ZIG_SYSTEM_DIR") {
|
||||
command.arg("--system").arg(system_dir);
|
||||
}
|
||||
|
||||
let status = command
|
||||
.current_dir(&vendored_dir)
|
||||
.status()
|
||||
.unwrap_or_else(|err| {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
panic!(
|
||||
"zig executable not found (looked for {zig:?}; set the ZIG \
|
||||
environment variable to point at the zig binary). Building \
|
||||
the vendored libghostty-vt requires Zig 0.16.0: install it from \
|
||||
https://ziglang.org/download/, then retry the build"
|
||||
);
|
||||
}
|
||||
panic!("failed to execute zig build for vendored libghostty-vt: {err}");
|
||||
});
|
||||
assert!(
|
||||
status.success(),
|
||||
"zig build for vendored libghostty-vt failed: {status}. \
|
||||
Building Herdr requires Zig 0.16.0; check `zig version` \
|
||||
or set ZIG to the path of a Zig 0.16.0 binary, then retry"
|
||||
);
|
||||
|
||||
let mut lib_dir = zig_prefix.join("lib");
|
||||
if target.contains("-apple-") {
|
||||
// Apple's linker prefers the sibling dylib for `-l`, so search a directory
|
||||
// holding only the static archive.
|
||||
let static_dir = out_dir.join("lib");
|
||||
fs::create_dir_all(&static_dir).expect("failed to create libghostty-vt link directory");
|
||||
fs::copy(
|
||||
lib_dir.join("libghostty-vt.a"),
|
||||
static_dir.join("libghostty-vt.a"),
|
||||
)
|
||||
.expect("failed to copy libghostty-vt static archive");
|
||||
lib_dir = static_dir;
|
||||
}
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
if target.contains("windows-msvc") {
|
||||
println!("cargo:rustc-link-lib=static=ghostty-vt-static");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=static=ghostty-vt");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,13 @@ use std::slice;
|
||||
use std::sync::{Arc, Mutex, Once, OnceLock};
|
||||
|
||||
use crate::pane_graphics_files::OwnedExport;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod native_image_sources;
|
||||
mod native_source;
|
||||
pub mod pane_graphics_files;
|
||||
|
||||
/// Terminfo entry the terminal emulates; child processes should see it as TERM.
|
||||
pub const TERM: &str = "xterm-256color";
|
||||
|
||||
pub use bindings as ffi;
|
||||
|
||||
@@ -77,7 +83,7 @@ pub enum Dirty {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum TerminalCompressionResult {
|
||||
pub enum TerminalCompressionResult {
|
||||
Unsupported,
|
||||
Pending,
|
||||
Complete,
|
||||
@@ -199,7 +205,7 @@ const TERMINAL_DATA_COLOR_CURSOR: ffi::GhosttyTerminalData = 20;
|
||||
const KITTY_IMAGE_STORAGE_LIMIT_BYTES: u64 = 64 * 1024 * 1024;
|
||||
const APC_MAX_BYTES: usize = 16 * 1024 * 1024;
|
||||
const APC_MAX_BYTES_KITTY: usize = 16 * 1024 * 1024;
|
||||
pub(crate) const KITTY_UNICODE_PLACEHOLDER: u32 = 0x10EEEE;
|
||||
pub const KITTY_UNICODE_PLACEHOLDER: u32 = 0x10EEEE;
|
||||
// The vendored C headers expose these placement fields, but the checked-in
|
||||
// generated bindings predate the names. Keep the explicit values aligned with
|
||||
// vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h.
|
||||
@@ -230,7 +236,7 @@ pub struct KittyImagePlacement {
|
||||
pub data_len: usize,
|
||||
pub data_fingerprint: u64,
|
||||
pub data: Vec<u8>,
|
||||
pub(crate) source_file: Option<Arc<OwnedExport>>,
|
||||
pub source_file: Option<Arc<OwnedExport>>,
|
||||
pub render: KittyPlacementRenderInfo,
|
||||
}
|
||||
|
||||
@@ -463,13 +469,13 @@ pub enum CellWide {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ScreenTextCell {
|
||||
pub struct ScreenTextCell {
|
||||
pub wide: CellWide,
|
||||
pub graphemes: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ScreenTextRow {
|
||||
pub struct ScreenTextRow {
|
||||
pub cells: Vec<ScreenTextCell>,
|
||||
pub soft_wrapped: bool,
|
||||
pub wrap_continuation: bool,
|
||||
@@ -826,11 +832,19 @@ pub fn encode_focus(event: FocusEvent) -> Result<Vec<u8>, Error> {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum LinkTarget {
|
||||
pub enum LinkTarget {
|
||||
Uri(String),
|
||||
Text { text: String, clicked_byte: usize },
|
||||
}
|
||||
|
||||
/// Inclusive display-cell columns on the current viewport.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LinkRegion {
|
||||
pub row: u16,
|
||||
pub start_col: u16,
|
||||
pub end_col: u16,
|
||||
}
|
||||
|
||||
pub struct Terminal {
|
||||
raw: ffi::GhosttyTerminal,
|
||||
max_scrollback: usize,
|
||||
@@ -870,8 +884,8 @@ impl Terminal {
|
||||
let userdata = (&mut *terminal.callback_state as *mut TerminalCallbackState).cast();
|
||||
let glyph_protocol = false;
|
||||
let terminfo_name = ffi::GhosttyString {
|
||||
ptr: crate::pane::PANE_TERM.as_ptr().cast(),
|
||||
len: crate::pane::PANE_TERM.len(),
|
||||
ptr: TERM.as_ptr().cast(),
|
||||
len: TERM.len(),
|
||||
};
|
||||
let grapheme_default = ffi::GhosttyTerminalModeConfig {
|
||||
mode: MODE_GRAPHEME_CLUSTER,
|
||||
@@ -951,7 +965,7 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compression_activity(&self) -> Result<u64, Error> {
|
||||
pub fn compression_activity(&self) -> Result<u64, Error> {
|
||||
let mut activity = 0;
|
||||
// SAFETY: self.raw is a live terminal handle and activity is a valid out pointer.
|
||||
unsafe {
|
||||
@@ -960,7 +974,7 @@ impl Terminal {
|
||||
Ok(activity)
|
||||
}
|
||||
|
||||
pub(crate) fn compress_incremental(&mut self) -> Result<TerminalCompressionResult, Error> {
|
||||
pub fn compress_incremental(&mut self) -> Result<TerminalCompressionResult, Error> {
|
||||
let mut result =
|
||||
ffi::GhosttyTerminalCompressionResult_GHOSTTY_TERMINAL_COMPRESSION_RESULT_UNSUPPORTED;
|
||||
// SAFETY: self.raw is a live terminal handle and result is a valid out pointer.
|
||||
@@ -1240,7 +1254,7 @@ impl Terminal {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn track_row(&mut self, y: u32) -> Option<usize> {
|
||||
pub fn track_row(&mut self, y: u32) -> Option<usize> {
|
||||
let mut point = ffi::GhosttyPointCoordinate::default();
|
||||
let tag = ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_SCREEN;
|
||||
let result =
|
||||
@@ -1262,11 +1276,11 @@ impl Terminal {
|
||||
Ok((wide, graphemes))
|
||||
}
|
||||
|
||||
pub(crate) fn screen_text_rows(&self) -> Result<Vec<ScreenTextRow>, Error> {
|
||||
pub fn screen_text_rows(&self) -> Result<Vec<ScreenTextRow>, Error> {
|
||||
self.screen_text_rows_range(0, usize::MAX)
|
||||
}
|
||||
|
||||
pub(crate) fn screen_text_rows_range(
|
||||
pub fn screen_text_rows_range(
|
||||
&self,
|
||||
start_row: usize,
|
||||
end_row_exclusive: usize,
|
||||
@@ -1317,7 +1331,7 @@ impl Terminal {
|
||||
grid_ref_hyperlink_uri(&grid_ref)
|
||||
}
|
||||
|
||||
pub(crate) fn viewport_link_target(&self, x: u16, y: u32) -> Result<Option<LinkTarget>, Error> {
|
||||
pub fn viewport_link_target(&self, x: u16, y: u32) -> Result<Option<LinkTarget>, Error> {
|
||||
Ok(self
|
||||
.viewport_link_selection(x, y)?
|
||||
.map(|(target, _)| target))
|
||||
@@ -1390,12 +1404,12 @@ impl Terminal {
|
||||
|
||||
/// Resolve only the bounded plain-text token. OSC 8 regions are resolved by
|
||||
/// clients from frame hyperlink IDs; their full URI activation path is unchanged.
|
||||
pub(crate) fn viewport_link_regions(
|
||||
pub fn viewport_link_regions(
|
||||
&self,
|
||||
x: u16,
|
||||
y: u32,
|
||||
resolve: fn(&str, usize) -> Option<std::ops::Range<usize>>,
|
||||
) -> Result<Vec<crate::api::schema::PaneLinkRegion>, Error> {
|
||||
) -> Result<Vec<LinkRegion>, Error> {
|
||||
let cols = self.cols()?;
|
||||
let rows = self.rows()?;
|
||||
if x >= cols || y >= u32::from(rows) {
|
||||
@@ -1461,7 +1475,7 @@ impl Terminal {
|
||||
false,
|
||||
)?;
|
||||
let mut byte = prefix.len().saturating_sub(cell_len(&first)?);
|
||||
let mut regions: Vec<crate::api::schema::PaneLinkRegion> = Vec::new();
|
||||
let mut regions: Vec<LinkRegion> = Vec::new();
|
||||
for row in start_row..=end_row {
|
||||
let mut cell = self.grid_ref(ghostty_viewport_point(0, row))?;
|
||||
let left = if row == start_row { start_col } else { 0 };
|
||||
@@ -1485,7 +1499,7 @@ impl Terminal {
|
||||
{
|
||||
last.end_col = end;
|
||||
} else {
|
||||
regions.push(crate::api::schema::PaneLinkRegion {
|
||||
regions.push(LinkRegion {
|
||||
row: row as u16,
|
||||
start_col: col,
|
||||
end_col: end,
|
||||
@@ -1716,11 +1730,11 @@ impl Terminal {
|
||||
self.get_optional_rgb_color(TERMINAL_DATA_COLOR_CURSOR)
|
||||
}
|
||||
|
||||
pub(crate) fn width_px(&self) -> Result<u32, Error> {
|
||||
pub fn width_px(&self) -> Result<u32, Error> {
|
||||
self.get_u32(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_WIDTH_PX)
|
||||
}
|
||||
|
||||
pub(crate) fn height_px(&self) -> Result<u32, Error> {
|
||||
pub fn height_px(&self) -> Result<u32, Error> {
|
||||
self.get_u32(ffi::GhosttyTerminalData_GHOSTTY_TERMINAL_DATA_HEIGHT_PX)
|
||||
}
|
||||
|
||||
@@ -1805,7 +1819,7 @@ impl Terminal {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn kitty_graphics_may_have_placements(&self) -> Result<bool, Error> {
|
||||
pub fn kitty_graphics_may_have_placements(&self) -> Result<bool, Error> {
|
||||
let generation = self.kitty_graphics_generation()?;
|
||||
Ok(generation != 0 && self.kitty_empty_generation.get() != Some(generation))
|
||||
}
|
||||
@@ -2460,7 +2474,7 @@ fn kitty_placeholder_diacritic_index(codepoint: u32) -> Option<u32> {
|
||||
// Reuse Ghostty's vendored table so Herdr decodes the same placeholder
|
||||
// row/column diacritics that libghostty accepts.
|
||||
let source =
|
||||
include_str!("../../vendor/libghostty-vt/src/terminal/kitty/graphics_unicode.zig");
|
||||
include_str!("../../../vendor/libghostty-vt/src/terminal/kitty/graphics_unicode.zig");
|
||||
let mut map = HashMap::new();
|
||||
let mut in_table = false;
|
||||
for line in source.lines() {
|
||||
@@ -3159,6 +3173,8 @@ pub struct RowIter<'a> {
|
||||
}
|
||||
|
||||
impl<'a> RowIter<'a> {
|
||||
// Advances a native cursor; rows are read in place, so this is not an Iterator.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> bool {
|
||||
// SAFETY: iterator handle is valid while self is alive.
|
||||
unsafe { ffi::ghostty_render_state_row_iterator_next(self.iterator.raw) }
|
||||
@@ -3327,6 +3343,7 @@ impl Default for CellBasicData {
|
||||
}
|
||||
|
||||
impl<'a> RowCellIter<'a> {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> bool {
|
||||
// SAFETY: cells handle is valid while self is alive.
|
||||
unsafe { ffi::ghostty_render_state_row_cells_next(self.cells.raw) }
|
||||
@@ -3989,6 +4006,8 @@ mod tests {
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn kitty_graphics_file_upload_can_be_placed_later() {
|
||||
use base64::Engine as _;
|
||||
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"herdr-kitty-file-upload-test-{}",
|
||||
std::process::id()
|
||||
@@ -4000,14 +4019,10 @@ mod tests {
|
||||
let mut terminal = Terminal::new(10, 5, 0).unwrap();
|
||||
terminal.enable_kitty_graphics().unwrap();
|
||||
terminal.resize(10, 5, 8, 16).unwrap();
|
||||
let mut upload = Vec::new();
|
||||
crate::kitty_graphics::encode_kitty_regular_file(
|
||||
&mut upload,
|
||||
&[],
|
||||
"a=t,f=32,s=1,v=1,i=10,q=0",
|
||||
path.to_str().unwrap(),
|
||||
);
|
||||
terminal.write(&upload);
|
||||
let payload =
|
||||
base64::engine::general_purpose::STANDARD.encode(path.to_str().unwrap().as_bytes());
|
||||
let upload = format!("\x1b7\x1b_Ga=t,f=32,s=1,v=1,i=10,q=0,t=f;{payload}\x1b\\\x1b8");
|
||||
terminal.write(upload.as_bytes());
|
||||
assert!(terminal.kitty_image_placements().unwrap().is_empty());
|
||||
|
||||
terminal.write(b"\x1b_Ga=p,i=10,p=5,c=10,r=5,C=1,q=2\x1b\\");
|
||||
@@ -9,13 +9,32 @@ use std::sync::{Arc, Mutex, OnceLock};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{DirBuilderExt, FileExt, MetadataExt, OpenOptionsExt, PermissionsExt};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::native_image_sources::clone_native_image_source;
|
||||
|
||||
/// Kernel CoW snapshots are deliberately unsupported outside Linux.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn clone_native_image_source(
|
||||
_source_fd: i64,
|
||||
_destination: &File,
|
||||
_expected_len: usize,
|
||||
) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"native source cloning requires Linux",
|
||||
))
|
||||
}
|
||||
|
||||
// Herdr's default log filter is `herdr=info`; keep these warnings under its target.
|
||||
const LOG_TARGET: &str = "herdr::pane_graphics_files";
|
||||
|
||||
#[cfg(unix)]
|
||||
const DIRECTORY_MODE: u32 = 0o700;
|
||||
#[cfg(unix)]
|
||||
const FILE_MODE: u32 = 0o600;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FileStore {
|
||||
pub struct FileStore {
|
||||
base: PathBuf,
|
||||
generation: OnceLock<Arc<Generation>>,
|
||||
next_fingerprint: AtomicU64,
|
||||
@@ -30,7 +49,7 @@ struct Generation {
|
||||
|
||||
/// Keep this owned snapshot alive until the terminal has consumed its path.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OwnedExport {
|
||||
pub struct OwnedExport {
|
||||
lease: Lease,
|
||||
_file: ExportFile,
|
||||
_reservation: Option<Reservation>,
|
||||
@@ -106,7 +125,7 @@ impl FileStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn native_sources() -> Self {
|
||||
pub fn native_sources() -> Self {
|
||||
let mut store = Self::new(native_base());
|
||||
store.native_budget = Some(Arc::new(Mutex::new(NativeBudget::default())));
|
||||
store
|
||||
@@ -114,7 +133,7 @@ impl FileStore {
|
||||
|
||||
/// Kernel-only bounded snapshot; any unsupported clone returns an error.
|
||||
/// The caller keeps the borrowed descriptor alive throughout this call.
|
||||
pub(crate) fn snapshot(&self, source_fd: i64, expected_len: usize) -> io::Result<OwnedExport> {
|
||||
pub fn snapshot(&self, source_fd: i64, expected_len: usize) -> io::Result<OwnedExport> {
|
||||
if expected_len == 0 || expected_len > MAX_EXPORT_BYTES || !expected_len.is_multiple_of(4) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
@@ -133,7 +152,7 @@ impl FileStore {
|
||||
path,
|
||||
_generation: generation,
|
||||
};
|
||||
crate::platform::clone_native_image_source(source_fd, &destination, expected_len)?;
|
||||
clone_native_image_source(source_fd, &destination, expected_len)?;
|
||||
drop(destination);
|
||||
let lease = self.lease(&file.path, expected_len)?;
|
||||
Ok(OwnedExport {
|
||||
@@ -143,8 +162,9 @@ impl FileStore {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
pub(crate) fn source_directory(&self) -> io::Result<PathBuf> {
|
||||
// Not test-gated: herdr's own tests use it, and a dependency's cfg(test) is off.
|
||||
#[cfg(unix)]
|
||||
pub fn source_directory(&self) -> io::Result<PathBuf> {
|
||||
Ok(self.generation()?.source.clone())
|
||||
}
|
||||
|
||||
@@ -168,7 +188,7 @@ impl FileStore {
|
||||
|
||||
/// Snapshot decoded bytes into a private, uniquely named file. Aggregate
|
||||
/// outstanding-export limits are the caller's responsibility.
|
||||
pub(crate) fn export(&self, data: &[u8]) -> io::Result<OwnedExport> {
|
||||
pub fn export(&self, data: &[u8]) -> io::Result<OwnedExport> {
|
||||
if data.len() > MAX_EXPORT_BYTES {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
@@ -230,7 +250,7 @@ impl PartialEq for OwnedExport {
|
||||
impl Eq for OwnedExport {}
|
||||
|
||||
impl OwnedExport {
|
||||
pub(crate) fn copy_rgba(&self) -> io::Result<Vec<u8>> {
|
||||
pub fn copy_rgba(&self) -> io::Result<Vec<u8>> {
|
||||
self.lease.copy_rgba()
|
||||
}
|
||||
|
||||
@@ -238,15 +258,16 @@ impl OwnedExport {
|
||||
self.lease.read_into(data)
|
||||
}
|
||||
|
||||
pub(crate) fn path(&self) -> &Path {
|
||||
pub fn path(&self) -> &Path {
|
||||
self.lease.path()
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.lease.len()
|
||||
}
|
||||
|
||||
pub(crate) fn fingerprint(&self) -> u64 {
|
||||
pub fn fingerprint(&self) -> u64 {
|
||||
self.lease.fingerprint()
|
||||
}
|
||||
}
|
||||
@@ -255,7 +276,7 @@ impl Drop for ExportFile {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = fs::remove_file(&self.path) {
|
||||
if err.kind() != io::ErrorKind::NotFound {
|
||||
tracing::warn!(path = %self.path.display(), err = %err, "failed to remove decoded graphics export");
|
||||
tracing::warn!(target: LOG_TARGET, path = %self.path.display(), err = %err, "failed to remove decoded graphics export");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,14 +327,14 @@ impl Drop for Generation {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = fs::remove_dir_all(&self.root) {
|
||||
if err.kind() != io::ErrorKind::NotFound {
|
||||
tracing::warn!(path = %self.root.display(), err = %err, "failed to remove pane graphics directory");
|
||||
tracing::warn!(target: LOG_TARGET, path = %self.root.display(), err = %err, "failed to remove pane graphics directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn validate_direct_source(path: &Path, expected_len: usize) -> io::Result<()> {
|
||||
pub fn validate_direct_source(path: &Path, expected_len: usize) -> io::Result<()> {
|
||||
validate_source_under(path, expected_len, runtime_base())
|
||||
}
|
||||
|
||||
@@ -343,7 +364,7 @@ fn validate_source_under(path: &Path, expected_len: usize, base: PathBuf) -> io:
|
||||
|
||||
/// Unlike the API validator, accepts only the dedicated native hierarchy.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn validate_native_source(path: &Path, expected_len: usize) -> io::Result<()> {
|
||||
pub fn validate_native_source(path: &Path, expected_len: usize) -> io::Result<()> {
|
||||
if expected_len == 0 || expected_len > MAX_EXPORT_BYTES || !expected_len.is_multiple_of(4) {
|
||||
return Err(invalid_path());
|
||||
}
|
||||
@@ -463,7 +484,7 @@ fn remove_stale_generations(base: &Path) {
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = fs::remove_dir_all(entry.path()) {
|
||||
tracing::warn!(path = %entry.path().display(), err = %err, "failed to remove stale pane graphics directory");
|
||||
tracing::warn!(target: LOG_TARGET, path = %entry.path().display(), err = %err, "failed to remove stale pane graphics directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ rustPlatform.buildRustPackage {
|
||||
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./..)) (
|
||||
lib.fileset.unions [
|
||||
../assets
|
||||
../crates
|
||||
../distribution/install.ps1
|
||||
../docs/next/api/herdr-api.schema.json
|
||||
../src
|
||||
|
||||
@@ -8,5 +8,5 @@ cd "$(dirname "$0")/.."
|
||||
--allowlist-function 'ghostty_.*' \
|
||||
--allowlist-var 'GHOSTTY_.*' \
|
||||
--with-derive-default \
|
||||
--output src/ghostty/bindings.rs \
|
||||
--output crates/ghostty-vt/src/bindings.rs \
|
||||
-- -Ivendor/libghostty-vt/include "$@"
|
||||
|
||||
+2
-2
@@ -2364,7 +2364,7 @@ mod tests {
|
||||
fn link_resolution_regions_keep_grapheme_byte_offsets() {
|
||||
let mut terminal = crate::ghostty::Terminal::new(40, 3, 1024).unwrap();
|
||||
terminal.write("e\u{301}(https://example.com/路e\u{301}),".as_bytes());
|
||||
let expected = vec![crate::api::schema::PaneLinkRegion {
|
||||
let expected = vec![crate::ghostty::LinkRegion {
|
||||
row: 0,
|
||||
start_col: 2,
|
||||
end_col: 24,
|
||||
@@ -2392,7 +2392,7 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
regions,
|
||||
vec![crate::api::schema::PaneLinkRegion {
|
||||
vec![crate::ghostty::LinkRegion {
|
||||
row: 0,
|
||||
start_col: 0,
|
||||
end_col: 3
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ mod config;
|
||||
mod copy_mode;
|
||||
mod detect;
|
||||
mod events;
|
||||
mod ghostty;
|
||||
use ghostty_vt as ghostty;
|
||||
mod handoff_runtime;
|
||||
mod input;
|
||||
mod integration;
|
||||
@@ -34,7 +34,7 @@ mod logging;
|
||||
mod metadata_tokens;
|
||||
mod noninteractive_process;
|
||||
mod pane;
|
||||
mod pane_graphics_files;
|
||||
use ghostty_vt::pane_graphics_files;
|
||||
mod persist;
|
||||
mod platform;
|
||||
mod plugin_command;
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ pub(crate) struct TerminalDirtyPatchSnapshot {
|
||||
const RELEASE_REACQUIRE_SUPPRESSION: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
const TERMINAL_COMPRESSION_IDLE: std::time::Duration = std::time::Duration::from_millis(250);
|
||||
const TERMINAL_COMPRESSION_STEP: std::time::Duration = std::time::Duration::from_millis(1);
|
||||
pub(crate) const PANE_TERM: &str = "xterm-256color";
|
||||
pub(crate) const PANE_TERM: &str = crate::ghostty::TERM;
|
||||
const PANE_COLORTERM: &str = "truecolor";
|
||||
|
||||
fn terminal_compression_permits() -> Arc<tokio::sync::Semaphore> {
|
||||
|
||||
@@ -2289,6 +2289,13 @@ impl GhosttyPaneTerminal {
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|region| crate::api::schema::PaneLinkRegion {
|
||||
row: region.row,
|
||||
start_col: region.start_col,
|
||||
end_col: region.end_col,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn link_target_at(&self, col: u16, row: u16) -> Option<crate::ghostty::LinkTarget> {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
mod native_image_sources;
|
||||
pub(crate) use native_image_sources::clone_native_image_source;
|
||||
|
||||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
io::{Read, Write},
|
||||
|
||||
@@ -727,16 +727,3 @@ pub(crate) fn shared_ssh_control_path(
|
||||
"interactive SSH recovery requires Unix OpenSSH multiplexing",
|
||||
))
|
||||
}
|
||||
|
||||
/// Kernel CoW snapshots are deliberately unsupported outside Linux.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub(crate) fn clone_native_image_source(
|
||||
_source_fd: i64,
|
||||
_destination: &std::fs::File,
|
||||
_expected_len: usize,
|
||||
) -> std::io::Result<()> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Unsupported,
|
||||
"native source cloning requires Linux",
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
use std::{mem::size_of, ptr};
|
||||
|
||||
// Reuse the generated C API; this test observer needs only terminal parsing and formatting.
|
||||
#[allow(
|
||||
dead_code,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals
|
||||
)]
|
||||
#[path = "../../src/ghostty/bindings.rs"]
|
||||
mod ffi;
|
||||
use ghostty_vt::ffi;
|
||||
|
||||
struct Screen {
|
||||
terminal: ffi::GhosttyTerminal,
|
||||
|
||||
Reference in New Issue
Block a user