Files
tty7/Cargo.toml
2026-09-04 17:42:08 +08:00

403 lines
20 KiB
TOML

[package]
name = "tty7"
version.workspace = true
edition = "2024"
description = "A terminal workbench: shells, persistent sessions, SSH, coding agents — GPU-rendered on Zed's gpui, pure Rust"
repository = "https://github.com/l0ng-ai/tty7"
license = "Apache-2.0"
readme = "README.md"
publish = false
default-run = "tty7-app"
[[bin]]
name = "tty7-app"
path = "src/main.rs"
[[bin]]
name = "tty7-updater"
path = "src/bin/tty7-updater.rs"
required-features = ["updater"]
[dependencies]
# The framework-free half of tty7: wire protocol, session daemon, PTY, the
# native SSH engine, and the domain model the headless `tty7-server` shares with
# this GUI. Everything that does *not* need gpui lives there.
# `gssapi` is off in tty7-core's defaults (a static musl `tty7-server` cannot
# link the system krb5 it binds); the GUI, which builds against a real desktop
# toolchain, turns it on so managed SSH connections keep offering
# `gssapi-with-mic` exactly as before.
#
# `remote-install` is off there for the same shape of reason: it pulls an HTTPS
# client used only to *download* a `tty7-server` onto a remote machine
# (decision D5). The server binary is the thing being downloaded, so it can
# never take that path; the GUI, which is the client that pushes it, can.
tty7-core = { path = "crates/tty7-core", features = ["gssapi", "remote-install"] }
gpui = { workspace = true }
gpui_platform = { workspace = true }
gpui-component = { workspace = true }
gpui-component-assets = { workspace = true }
anyhow.workspace = true
log.workspace = true
# Smart double-click selection patterns (URL/email/path). Already in the tree
# transitively, so pinning it here adds no new native code.
regex = "1"
# SIMD byte search for `ParkedCursorScanner`'s fast path
# (`terminal::parked_cursor`), which scans every output batch the client
# receives — the same skip-ahead the
# tokenizers in `tty7-core` already use. Already in the tree via `tty7-core`,
# so this pins no new code.
memchr = "2"
# Grapheme-cluster boundaries for the sidebar's label elision
# (`ui::tab_strip`). Cutting a label by `char` tears emoji apart: a ZWJ
# sequence loses its joiner, `❤️` loses the variation selector that makes it an
# emoji, and half a flag renders as a bare letter. Already in the tree via
# gpui, so this pins no new code.
unicode-segmentation = "1"
# Column widths for the input bar (`terminal::view`). The bar has to lay text
# out the way the terminal grid will, and the grid gets its widths from this
# same crate by way of `alacritty_terminal` — a hand-rolled code-point table
# drifts from it the moment Unicode adds a block. Already in the tree via
# `tty7-cli`, so this pins no new code.
unicode-width = "0.2"
smol.workspace = true
smallvec.workspace = true
serde = { workspace = true }
serde_json.workspace = true
tempfile = "3"
# SSH profile ids in the connection manager UI (`ui::ssh_connect`,
# `ui::settings`, the command palette). The profiles themselves — and the
# secret-free `CredentialRef` they carry — live in `tty7-core`, which `Config`
# needs to parse `config.json` without gpui.
uuid = { version = "1", features = ["v4", "serde"] }
# The credential vault's storage half (`core::keychain`). `keyring` 4.x's default
# `v1` feature auto-selects the platform store (macOS Keychain / Windows
# Credential Manager / Linux Secret Service), so no per-platform guards are
# needed.
#
# It sits here rather than in `tty7-core` on purpose: every caller is in `ui::`
# (`ssh_prompt`, `ssh_connect`, `settings`, `app`), and leaving it downstairs made
# the headless `tty7-server` — a static binary pushed onto machines that have no
# keychain at all — link `zbus`/`secret-service` and thirty-odd crates behind them
# for code it can never reach. Only the secret-free naming half (`CredentialRef`,
# the account scheme) stays in core, where `config.json` parsing needs it.
keyring = "4"
# Theme files. tty7 themes are authored as YAML (`~/.config/tty7/themes/*.yaml`,
# our own schema); `plist` parses imported iTerm2 `.itermcolors` schemes (XML
# plist) so the large iTerm color-scheme ecosystem drops straight in. serde_yaml
# is already in the tree transitively, so it pins no new code; plist is pure Rust.
serde_yaml = "0.9"
plist = "1"
# Clipboard-image paste (`terminal::view`). When the clipboard holds a screenshot
# and a coding-agent TUI (Claude Code &co.) is in the pane, off macOS we stage the
# image to a temp file and paste its path — agents attach an image path the same way
# they do a drag-drop. Windows screenshots arrive as BMP (`CF_DIB`), which agent
# vision won't accept, so we transcode to PNG. `image` is already in the tree via
# gpui's own clipboard code, so pinning it here adds no new native code.
image = "0.25"
# HTTP client for the startup update check (`core::update`): one GET to the
# GitHub releases API to see if a newer version has shipped. `reqwest_client`
# wraps Zed's `zed-reqwest` fork behind gpui's `http_client` trait (re-exported
# as `gpui::http_client`) and manages its own tokio runtime. That reqwest+rustls
# stack is *already* compiled into the tree via `gpui-component-assets`, so this
# pins no new native code — it only exposes what we're already building. Pinned
# to gpui's rev so the shared `http_client`/`zed-reqwest` versions stay aligned.
reqwest_client = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" }
# See `[workspace.dependencies]`: the pin is shared with tty7-cli, which parses
# `capture` output through the same grid.
alacritty_terminal.workspace = true
# Desktop notifications driven by OSC 9 / OSC 777 escape sequences. Cross-platform;
# the macOS backend uses the deprecated NSUserNotification (weak — a completion
# toast is fine, revisit mac-notification-sys if it proves unusable).
notify-rust = "4"
# Filesystem watcher for live config reload: watches `config.json` and reloads
# `Config` + re-applies the theme without a restart. The code panel (file tree /
# editor, `ui::code`) reuses it to refresh the tree and detect external edits.
notify = "8"
# System tray / menu bar status item (`ui::tray`). Rasterizes the bundled SVG
# logo into the tray bitmap at runtime — gpui's own SVG path only yields a
# tinted alpha mask, not raw RGBA. The gpui and gpui-component forks both pin
# the same 0.47, so the whole tree shares a single resvg/usvg/tiny-skia stack;
# default features off drops the text/font machinery our icon SVGs don't use.
resvg = { version = "0.47", default-features = false }
# The tray icon itself, macOS + Windows: tauri's `tray-icon` (NSStatusItem /
# Shell_NotifyIcon via objc2 / windows-sys, both already in the tree). Linux is
# deliberately NOT on this crate — its Linux backend needs GTK + libappindicator,
# which tty7's AppImage doesn't bundle and the x11/wayland gpui backends don't
# pull. Linux instead uses `ksni` below.
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
tray-icon = "0.24"
# The pty-size ioctl in `terminal::generator` is the only libc user left in the
# GUI (the daemon's own libc calls went to tty7-core), and it is Unix-only — so
# the dep is Unix-only too.
[target.'cfg(unix)'.dependencies]
libc = "0.2"
# `core::cli_install` edits HKCU\Environment to put the bundled CLI on PATH.
# `setx` is not an option there: it truncates at 1024 characters, so a long user
# PATH comes back mangled. 0.61 is already in the tree (tray-icon pulls it), so
# this pins no new code.
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Storage_FileSystem",
# ConvertStringSecurityDescriptorToSecurityDescriptorW + SECURITY_ATTRIBUTES:
# the elevated updater's %ProgramData% staging directory gets an explicit
# admin-only DACL (see `create_dir_admin_only` in tty7-updater).
"Win32_Security",
"Win32_Security_Authorization",
"Win32_System_Registry",
"Wdk_System_SystemServices",
"Win32_System_SystemInformation",
# `MessageBeep` (the terminal bell in `terminal::view`) is a user32 export,
# but the Win32 metadata files it under Diagnostics::Debug, so that is the
# module the binding needs — nothing here pulls in dbghelp.
"Win32_System_Diagnostics_Debug",
"Win32_System_Threading",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
] }
# The standalone updater extracts only Windows release ZIPs. Reuse the async
# reader already present in Cargo.lock and enable only the Deflate codec emitted
# by PowerShell's Compress-Archive.
async_zip = { version = "0.0.18", default-features = false, features = ["deflate"], optional = true }
# COM for toast branding (`core::aumid`): IShellLinkW + IPropertyStore stamp
# System.AppUserModel.ID onto the Start Menu shortcut, which Windows requires
# before it honors an unpackaged app's toast identity (otherwise the toast
# backend falls back to PowerShell's). 0.58 is already in the tree via gpui.
windows = { version = "0.58", features = [
"Data_Xml_Dom",
"Foundation",
"UI_Notifications",
"Win32_Foundation",
"Win32_Storage_EnhancedStorage",
# IShellLinkW::GetPath takes a WIN32_FIND_DATAW, so reading an existing
# shortcut's target back — which is how `aumid` decides whether it has to
# write at all — needs the file-system bindings too.
"Win32_Storage_FileSystem",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_System_Variant",
"Win32_UI_Shell",
"Win32_UI_Shell_PropertiesSystem",
] }
# Embeds `assets/favicon.ico` into the `.exe` so Windows shows the tty7 logo in
# the taskbar / window / Explorer (macOS gets its icon from the `.app` bundle via
# bundle.sh instead). Only needed at build time on Windows — see build.rs.
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"
# gpui only *reads* the macOS window appearance; it never sets it. We force the
# app appearance to match the active theme (see `ui::theme::sync_native_appearance`)
# so the native traffic-light buttons render in the right light/dark style.
[target.'cfg(target_os = "macos")'.dependencies]
# CFStringTokenizer FFI for dictionary-based CJK word segmentation on
# double-click (`terminal::smart_select`). Already in the tree transitively,
# and it makes jieba unnecessary here — see the non-macos section below.
core-foundation = "0.10"
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSResponder", "NSAppearance", "NSGraphics", "NSImage"] }
# NSData feeds the runtime Dock icon for bare (non-bundled) binaries — see
# `set_dock_icon_for_bare_binary` in main.rs.
objc2-foundation = { version = "0.3", features = ["NSData"] }
# Clickable desktop notifications on macOS. `notify-rust` already pulls this
# crate transitively for basic display; we depend on it directly to use
# wait_for_click / NotificationResponse::Click.
mac-notification-sys = "0.6"
# Dictionary-based Chinese word segmentation for double-click selection
# (`terminal::smart_select`), as a *fallback* where the OS has no tokenizer of
# its own. macOS is excluded on purpose: CFStringTokenizer segments Chinese
# about as well (and Japanese/Korean far better) at zero cost, while jieba's
# table costs ~55 MB resident once built and ~2 MB of binary for the embedded
# dictionary. Keeping the dep off macOS means that dictionary isn't even
# linked into the build that can't use it.
[target.'cfg(not(target_os = "macos"))'.dependencies]
jieba-rs = "0.10"
# x11/wayland are the Linux windowing backends; only pull them on Linux. The
# Windows backend (`gpui_windows`) and macOS backend are selected by gpui_platform
# itself via `cfg`, so no feature is needed for them.
[target.'cfg(target_os = "linux")'.dependencies]
gpui_platform = { workspace = true, features = ["x11", "wayland"] }
# Linux tray (`ui::tray`): pure-Rust StatusNotifierItem over DBus (zbus — already
# in the tree). `blocking` + `async-io` gives a synchronous handle without
# requiring the app to own a tokio runtime; ksni runs its own service thread. On
# desktops without an SNI host (bare GNOME without the AppIndicator extension)
# spawning fails and the tray is silently absent — the app is unaffected.
ksni = { version = "0.3.6", default-features = false, features = ["blocking", "async-io"] }
# gpui's `test-support` unlocks `#[gpui::test]` + `TestAppContext`, the headless
# App/Window harness the view/event tests run on. Dev-only: the feature merges
# into test builds and never reaches a release binary.
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
[lints]
workspace = true
[features]
default = []
updater = ["dep:async_zip"]
# ---- Standalone workspace mirroring gpui-component's pins so the git/source
# ---- caches are shared and versions stay aligned. ----
[workspace]
members = ["crates/*"]
# The root `tty7` package is a member implicitly; naming every crate here is
# what makes a bare `cargo build` / `cargo test` at the root cover the whole
# workspace, which is how CI invokes them.
default-members = [".", "crates/tty7-core", "crates/tty7-server", "crates/tty7-cli"]
[workspace.package]
edition = "2024"
# The single version for all three crates — `tty7`, `tty7-core`, `tty7-server`
# all inherit it with `version.workspace = true`. They ship together and a
# client talking to a server of a different build has to be able to say so, so
# they must never drift apart. This is the line a release bump edits (and the
# one nightly's `awk '/^version = /'` stamps: it is the first such line in the
# file, since the package entries above are all `version.workspace = true`).
version = "26.9.0"
[workspace.dependencies]
# Our fork's `tty7` branch carries the local customizations tty7 relies on:
# `PopupMenu::with_size`, the 1px hairline menu separator, and the custom-button
# label color the chrome tiles need. The exact commit is still pinned by
# Cargo.lock. For co-developing the UI crate, point these back at a sibling
# checkout: `path = "../gpui-component/crates/{ui,assets}"` — but never commit
# that, since a path dependency can't resolve on CI.
#
# `tree-sitter-languages` compiles the grammars the code editor highlights with.
gpui-component = { git = "https://github.com/l0ng-ai/gpui-component", branch = "tty7", version = "0.5.2", features = [
"tree-sitter-languages",
] }
gpui-component-assets = { git = "https://github.com/l0ng-ai/gpui-component", branch = "tty7", version = "0.5.1" }
gpui = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe" }
# Base features are cross-platform (`font-kit`, `runtime_shaders` only map to the
# macOS backend; they're no-ops elsewhere). The Linux-only `x11`/`wayland`
# backends are added by the `cfg(target_os = "linux")` dependency entry in the
# package manifest, so they never reach the Windows/macOS builds.
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "1d217ee39d381ac101b7cf49d3d22451ac1093fe", features = ["font-kit", "runtime_shaders"] }
# Our fork of Zed's alacritty_terminal fork: the VT parser + grid
# (`Term`/`ansi::Processor`). Two packages read it and they must agree, hence
# this being a workspace pin rather than one each — the GUI (`terminal::remote`)
# to render the live mirror, and tty7-cli to turn a captured pane back into text
# (`tty7 capture --plain`). The daemon parses nothing; it is a byte pipe, and its
# PTY is driven by `portable-pty`.
#
# The `tty7` branch is Zed's `fcf32fe` (the rev Zed pins) plus two commits, both
# still missing from alacritty master as of 852e971:
#
# 1. `push_keyboard_mode` capped its stack by removing from `title_stack` instead
# of `keyboard_mode_stack` — a copy-paste slip from `push_title` that compiles
# because both are `Vec`s. With `kitty_keyboard` on (see
# `terminal_config_from_user`) every overflowing push silently drops a saved
# title, and once the title stack is empty `Vec::remove(0)` panics — 4097
# unpopped `CSI > 1 u` pushes, about 20KB of output, kill the reader thread and
# freeze the pane.
# 2. `input` reserves columns per `char`, so an emoji written as base + U+FE0F
# (`❤️`, `🗂️`, `⚠️` — any base whose East Asian Width is Neutral) gets one
# column instead of two and shifts the rest of the line left by one. The fork
# re-scores the sequence with `UnicodeWidthStr` and widens the cell (issue
# #203).
#
# Each patch has a guard test in `src/terminal/remote.rs`; drop this fork once
# both land upstream.
alacritty_terminal = { git = "https://github.com/l0ng-ai/alacritty", rev = "1276f128fbaa8832cbc66210675cbe8aeb570499" }
anyhow = "1"
log = "0.4"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1"
smallvec = "1"
smol = "2"
[patch.crates-io]
# Temporary until upstream russh releases gssapi-with-mic client auth support
# (https://github.com/Eugeny/russh/pull/737) — remove this patch and take the
# crates.io release once it lands. Pinned to an exact rev (never a branch):
# russh is the SSH protocol layer handling user credentials, and a moving
# branch on a third-party fork could change what `cargo update` builds.
russh = { git = "https://github.com/ayamir/russh", rev = "0d1d073350ed823069252075cbf3db9672d5b490" }
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
type_complexity = "allow"
[profile.dev]
codegen-units = 16
debug = "limited"
split-debuginfo = "unpacked"
[profile.dev.package]
resvg = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
taffy = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
smol = { opt-level = 3 }
gpui = { opt-level = 3 }
gpui_platform = { opt-level = 3 }
gpui_macros = { opt-level = 3 }
# The VT parser + grid run on every PTY byte; at opt-level 0 a `cat bigfile`
# crawls under `cargo dev`.
alacritty_terminal = { opt-level = 3 }
# The render/parse hot path crosses the tty7 ↔ alacritty_terminal ↔ gpui crate
# boundaries, so cross-crate inlining (thin LTO, like Zed ships) buys real
# throughput there; single codegen unit for the same reason.
[profile.release]
lto = "thin"
codegen-units = 1
# ---- gpui fork ------------------------------------------------------------
# Our `tty7` branch (cut from the pinned upstream rev, three commits on top) carries:
#
# 1. `prefers_ime_for_printable_keys` takes the keystroke, so an input handler can
# answer per key instead of per view. tty7 needs it for Option-as-Meta — macOS
# routes ⌥-chords to the IME whenever a CJK input source is active, and without
# the keystroke there is no way to decline just those chords (see
# `terminal::input::prefers_ime_for_printable_keys`, issue #177).
#
# 2. gpui's Windows backend rasterizes a font-fallback run with the face
# DirectWrite actually shaped it with, instead of re-deriving one from the
# face's family/weight/style. The round trip mapped DirectWrite's italic to
# oblique, so italic CJK — which every pane reaches through the fallback chain,
# Hack having no CJK — drew a *different* face's outlines at the shaped glyph
# indices. Every character rendered as an unrelated character, one for one,
# which reads as mojibake rather than as a font bug.
#
# 3. resvg/usvg bumped 0.45 → 0.47 so gpui's SVG stack unifies with the resvg
# tty7 pins directly above (follow-up to the #227 dependabot bump).
#
# Patching by source rather than editing the `gpui`/`gpui_platform` pins above is
# deliberate: `gpui-component` declares its own `gpui` from the upstream URL, and
# a plain pin swap would put two incompatible copies of gpui in the tree. `[patch]`
# rewrites the source for every dependent at once, so the fork's sibling crates
# (`gpui_macos`, `gpui_web`, …) come along through their in-repo path deps.
#
# When bumping the upstream rev: rebase the fork's `tty7` branch onto the new rev,
# push, then update the `rev` pins above — the branch here follows automatically.
[patch."https://github.com/zed-industries/zed"]
gpui = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_platform = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_macros = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
gpui_web = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }
reqwest_client = { git = "https://github.com/l0ng-ai/zed", branch = "tty7" }