Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,163 changes: 974 additions & 189 deletions mux/Cargo.lock

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions mux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@ members = [
]

[workspace.package]
edition = "2021"
edition = "2024"
rust-version = "1.88"
license = "MIT"
publish = false

[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "deny"
unused_qualifications = "warn"

[workspace.lints.clippy]
uninlined_format_args = "warn"
semicolon_if_nothing_returned = "warn"
redundant_clone = "warn"
# Evaluated and dropped: needless_pass_by_value causes signature churn across mux-core call sites.

[workspace.dependencies]
ghostty-vt-sys = { path = "crates/ghostty-vt-sys" }
ghostty-vt = { path = "crates/ghostty-vt" }
Expand All @@ -23,14 +34,15 @@ anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
portable-pty = "0.9"
crossterm = "0.28"
ratatui = "0.29"
crossterm = "0.29"
ratatui = "0.30"
unicode-width = "0.2"
base64 = "0.22"
libc = "0.2"
tungstenite = { version = "0.24", default-features = false, features = ["handshake"] }
tungstenite = { version = "0.29", default-features = false, features = ["handshake"] }
uds_windows = "1.2"
regex = "1"
bindgen = "0.72"

[profile.release]
lto = "thin"
Expand Down
4 changes: 4 additions & 0 deletions mux/bindings/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
name = "cmux-client"
version = "0.1.2"
edition.workspace = true
rust-version.workspace = true
Comment on lines 4 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Published crate now carries a hard MSRV floor

cmux-client is the only crate in the workspace with publish = true, and it now inherits rust-version = "1.88" from the workspace. Any downstream consumer pinned to an older stable toolchain will see a hard rust-version rejection from Cargo 1.73+. This is intentional per the PR description, but the published CHANGELOG / crate docs should call it out explicitly so users aren't surprised on cargo update.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

license.workspace = true
description = "Rust client for the cmux-mux terminal multiplexer control protocol"
repository = "https://github.com/manaflow-ai/cmux"
homepage = "https://github.com/manaflow-ai/cmux/tree/main/mux/bindings/rust"
publish = true

[lints]
workspace = true

[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
2 changes: 1 addition & 1 deletion mux/bindings/rust/examples/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn next_resized(
}
match events.recv_timeout(time_left(deadline))? {
Event::SurfaceResized(event) if event.surface == surface => {
return Ok((event.cols, event.rows))
return Ok((event.cols, event.rows));
}
_ => {}
}
Expand Down
16 changes: 9 additions & 7 deletions mux/bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ impl CmuxClient {
let id = self.next_id();
params.insert("id".to_string(), Value::from(id));
params.insert("cmd".to_string(), Value::from(cmd));
CmuxStream::open(&self.config.socket_path, self.config.timeout, Value::Object(params))
CmuxStream::open(&self.config.socket_path, self.config.timeout, &Value::Object(params))
}

fn next_id(&mut self) -> u64 {
Expand All @@ -507,10 +507,10 @@ pub struct CmuxStream {
}

impl CmuxStream {
fn open(socket_path: &PathBuf, timeout: Duration, request: Value) -> Result<Self> {
fn open(socket_path: &PathBuf, timeout: Duration, request: &Value) -> Result<Self> {
let mut conn = JsonLineConnection::connect(socket_path, timeout)?;
let request_id = request.get("id").cloned();
conn.send(&request)?;
conn.send(request)?;
let mut buffered = Vec::new();
loop {
let response = conn.recv()?;
Expand Down Expand Up @@ -551,10 +551,12 @@ impl CmuxStream {
if !self.buffered.is_empty() {
return Ok(self.buffered.remove(0));
}
self.conn.with_read_timeout(timeout, |conn| loop {
let value = conn.recv()?;
if value.get("event").is_some() {
return Ok(parse_event(value));
self.conn.with_read_timeout(timeout, |conn| {
loop {
let value = conn.recv()?;
if value.get("event").is_some() {
return Ok(parse_event(value));
}
}
})
}
Expand Down
6 changes: 5 additions & 1 deletion mux/crates/ghostty-vt-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
name = "ghostty-vt-sys"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Raw FFI bindings to libghostty-vt, built from the vendored ghostty submodule via zig"
links = "ghostty-vt"

[lints]
workspace = true

[build-dependencies]
bindgen = "0.71"
bindgen.workspace = true
8 changes: 4 additions & 4 deletions mux/crates/ghostty-vt-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ fn main() {
.arg("-Demit-lib-vt=true")
.arg("-Demit-xcframework=false")
.arg("-Doptimize=ReleaseFast");
if target != host {
if let Some(zig_target) = zig_target_for_rust_target(&target) {
command.arg(format!("-Dtarget={zig_target}"));
}
if target != host
&& let Some(zig_target) = zig_target_for_rust_target(&target)
{
command.arg(format!("-Dtarget={zig_target}"));
}
// Valgrind's instruction emulation doesn't cover every CPU-native SIMD
// extension zig's default target detection can select (e.g. some AVX-512
Expand Down
4 changes: 4 additions & 0 deletions mux/crates/ghostty-vt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
name = "ghostty-vt"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Safe Rust wrapper around libghostty-vt"

[lints]
workspace = true

[dependencies]
ghostty-vt-sys.workspace = true
2 changes: 1 addition & 1 deletion mux/crates/ghostty-vt/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::ptr;
use ghostty_vt_sys as sys;

use crate::terminal::Terminal;
use crate::{check, Result};
use crate::{Result, check};

/// Key press/release/repeat.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion mux/crates/ghostty-vt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod terminal;
/// Raw bindings, re-exported for key/mode constants.
pub use ghostty_vt_sys as sys;

pub use key::{key_input_from_chord, KeyAction, KeyEncoder, KeyInput, Mods};
pub use key::{KeyAction, KeyEncoder, KeyInput, Mods, key_input_from_chord};
pub use render::{Cell, ColorSpec, CursorInfo, CursorShape, Dirty, RenderState};
pub use terminal::{Callbacks, NotifyFn, PtyWriteFn, Rgb, Screen, Scrollbar, Terminal};

Expand Down
4 changes: 2 additions & 2 deletions mux/crates/ghostty-vt/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ptr;
use ghostty_vt_sys as sys;

use crate::terminal::{Rgb, Terminal};
use crate::{check, Result};
use crate::{Result, check};

/// Global dirty state after a [`RenderState::update`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -314,7 +314,7 @@ fn fill_cell(cells: sys::GhosttyRenderStateRowCells, cell: &mut Cell, grapheme_b
}

let mut style =
sys::GhosttyStyle { size: std::mem::size_of::<sys::GhosttyStyle>(), ..Default::default() };
sys::GhosttyStyle { size: size_of::<sys::GhosttyStyle>(), ..Default::default() };
let style_ok = unsafe {
sys::ghostty_render_state_row_cells_get(
cells,
Expand Down
20 changes: 10 additions & 10 deletions mux/crates/ghostty-vt/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::ptr;

use ghostty_vt_sys as sys;

use crate::{check, Result};
use crate::{Result, check};

/// RGB color triple.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
Expand Down Expand Up @@ -357,25 +357,25 @@ impl Terminal {
value: sys::GhosttyPointValue { coordinate: sys::GhosttyPointCoordinate { x, y } },
};
let mut out = sys::GhosttyGridRef {
size: std::mem::size_of::<sys::GhosttyGridRef>(),
size: size_of::<sys::GhosttyGridRef>(),
..Default::default()
};
let result = unsafe { sys::ghostty_terminal_grid_ref(self.raw, point, &mut out) };
(result == sys::GHOSTTY_SUCCESS).then_some(out)
};
let selection = sys::GhosttySelection {
size: std::mem::size_of::<sys::GhosttySelection>(),
size: size_of::<sys::GhosttySelection>(),
start: grid_ref(start.0, start.1)?,
end: grid_ref(end.0, end.1)?,
rectangle: false,
};
let opts = sys::GhosttyFormatterTerminalOptions {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalOptions>(),
size: size_of::<sys::GhosttyFormatterTerminalOptions>(),
emit: sys::GHOSTTY_FORMATTER_FORMAT_PLAIN,
unwrap: unwrap_lines,
trim,
extra: sys::GhosttyFormatterTerminalExtra {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalExtra>(),
size: size_of::<sys::GhosttyFormatterTerminalExtra>(),
..Default::default()
},
selection: &selection,
Expand All @@ -388,12 +388,12 @@ impl Terminal {
/// scrollback. For the rendered viewport only, use [`Self::viewport_text`].
pub fn plain_text(&mut self) -> Result<String> {
let opts = sys::GhosttyFormatterTerminalOptions {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalOptions>(),
size: size_of::<sys::GhosttyFormatterTerminalOptions>(),
emit: sys::GHOSTTY_FORMATTER_FORMAT_PLAIN,
unwrap: false,
trim: true,
extra: sys::GhosttyFormatterTerminalExtra {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalExtra>(),
size: size_of::<sys::GhosttyFormatterTerminalExtra>(),
..Default::default()
},
selection: ptr::null(),
Expand All @@ -408,20 +408,20 @@ impl Terminal {
/// frontend replays this, then follows the live pty stream.
pub fn vt_replay(&mut self) -> Result<Vec<u8>> {
let opts = sys::GhosttyFormatterTerminalOptions {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalOptions>(),
size: size_of::<sys::GhosttyFormatterTerminalOptions>(),
emit: sys::GHOSTTY_FORMATTER_FORMAT_VT,
unwrap: false,
trim: false,
extra: sys::GhosttyFormatterTerminalExtra {
size: std::mem::size_of::<sys::GhosttyFormatterTerminalExtra>(),
size: size_of::<sys::GhosttyFormatterTerminalExtra>(),
palette: true,
modes: true,
scrolling_region: true,
tabstops: true,
pwd: true,
keyboard: true,
screen: sys::GhosttyFormatterScreenExtra {
size: std::mem::size_of::<sys::GhosttyFormatterScreenExtra>(),
size: size_of::<sys::GhosttyFormatterScreenExtra>(),
cursor: true,
style: true,
hyperlink: true,
Expand Down
4 changes: 4 additions & 0 deletions mux/crates/mux-cdp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
name = "mux-cdp"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true
description = "Synchronous Chrome DevTools Protocol transport and Chrome lifecycle for cmux-mux"

[lints]
workspace = true

[dependencies]
anyhow.workspace = true
serde_json.workspace = true
Expand Down
16 changes: 7 additions & 9 deletions mux/crates/mux-cdp/src/chrome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use std::ffi::OsString;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::mpsc;
use std::sync::Mutex;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

static PROFILE_SEQ: AtomicU64 = AtomicU64::new(1);
Expand All @@ -28,11 +28,11 @@ impl Chrome {
/// Launch Chrome in headless mode and wait for the browser CDP
/// endpoint printed on stderr.
pub fn launch(binary: PathBuf) -> anyhow::Result<Self> {
Chrome::launch_with(ChromeLaunchOptions { binary, user_data_dir: None, ephemeral: true })
Chrome::launch_with(&ChromeLaunchOptions { binary, user_data_dir: None, ephemeral: true })
}

pub fn launch_with(options: ChromeLaunchOptions) -> anyhow::Result<Self> {
let (profile_dir, profile_ephemeral) = profile_dir_for(&options)?;
pub fn launch_with(options: &ChromeLaunchOptions) -> anyhow::Result<Self> {
let (profile_dir, profile_ephemeral) = profile_dir_for(options)?;
std::fs::create_dir_all(&profile_dir)?;
let mut child = Command::new(&options.binary)
.arg("--headless=new")
Expand Down Expand Up @@ -66,11 +66,9 @@ impl Chrome {
match reader.read_line(&mut line) {
Ok(0) | Err(_) => break,
Ok(_) => {
if !sent {
if let Some(url) = parse_devtools_url(&line) {
let _ = tx.send(url);
sent = true;
}
if !sent && let Some(url) = parse_devtools_url(&line) {
let _ = tx.send(url);
sent = true;
}
}
}
Expand Down
Loading
Loading