diff --git a/Cargo.lock b/Cargo.lock index 52bc191..9358c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,9 +279,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "blake3" @@ -500,6 +500,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cordyceps" version = "0.3.2" @@ -590,6 +599,35 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "base64", + "bitflags 2.9.1", + "crossterm_winapi", + "derive_more 2.0.1", + "document-features", + "futures-core", + "mio", + "parking_lot", + "rustix 1.0.7", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -736,6 +774,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.98", @@ -783,9 +822,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -2075,9 +2114,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" @@ -2203,6 +2242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2332,7 +2372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" dependencies = [ "anyhow", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -2420,7 +2460,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -3153,7 +3193,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] @@ -3334,7 +3374,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3347,7 +3387,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -3495,7 +3535,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3535,9 +3575,9 @@ name = "sendme" version = "0.26.0" dependencies = [ "anyhow", - "base64", "clap", "console", + "crossterm", "data-encoding", "derive_more 1.0.0", "duct", @@ -3546,6 +3586,7 @@ dependencies = [ "indicatif", "iroh", "iroh-blobs", + "libc", "n0-future", "nix", "num_cpus", @@ -3557,6 +3598,7 @@ dependencies = [ "tracing", "tracing-subscriber", "walkdir", + "windows-sys 0.59.0", ] [[package]] @@ -3666,6 +3708,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3696,7 +3759,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] @@ -3907,7 +3970,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4294,6 +4357,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.0" @@ -4601,7 +4670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ "windows-core 0.59.0", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -4655,10 +4724,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ "windows-implement 0.59.0", - "windows-interface 0.59.0", + "windows-interface 0.59.1", "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -4668,7 +4737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" dependencies = [ "windows-implement 0.59.0", - "windows-interface 0.59.0", + "windows-interface 0.59.1", "windows-link", "windows-result 0.3.4", "windows-strings 0.3.1", @@ -4719,9 +4788,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.0" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", @@ -4752,7 +4821,7 @@ checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -4876,9 +4945,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -5095,7 +5164,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e5ea2c0..4c7fd5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,17 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } walkdir = "2.4.0" data-encoding = "2.6.0" n0-future = "0.1.2" -base64 = { version = "0.22.1", optional = true } hex = "0.4.3" +crossterm = { version = "0.29.0", features = [ + "event-stream", + "osc52", +], optional = true } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2.174", optional = true } + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59.0", features = ["Win32_System_Console"], optional = true } [dev-dependencies] duct = "0.13.6" @@ -46,7 +55,7 @@ serde_json = "1.0.108" tempfile = "3.8.1" [features] -clipboard = ["dep:base64"] +clipboard = ["dep:crossterm", "dep:windows-sys", "dep:libc"] default = ["clipboard"] [patch.crates-io] diff --git a/src/main.rs b/src/main.rs index b562069..5afb499 100644 --- a/src/main.rs +++ b/src/main.rs @@ -742,24 +742,7 @@ async fn send(args: SendArgs) -> anyhow::Result<()> { println!("sendme receive {ticket}"); #[cfg(feature = "clipboard")] - { - use console::{Key, Term}; - - // Add command to the clipboard - if args.clipboard { - add_to_clipboard(&ticket); - } - - let _keyboard = tokio::task::spawn(async move { - let term = Term::stdout(); - println!("press c to copy command to clipboard, or use the --clipboard argument"); - loop { - if let Ok(Key::Char('c')) = term.read_key() { - add_to_clipboard(&ticket); - } - } - }); - } + handle_key_press(args.clipboard, ticket); tokio::signal::ctrl_c().await?; @@ -777,20 +760,85 @@ async fn send(args: SendArgs) -> anyhow::Result<()> { } #[cfg(feature = "clipboard")] -fn add_to_clipboard(ticket: &BlobTicket) { - use std::io::{stdout, Write}; +fn handle_key_press(set_clipboard: bool, ticket: BlobTicket) { + use crossterm::{ + event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, + terminal::{disable_raw_mode, enable_raw_mode}, + }; - use base64::prelude::{Engine, BASE64_STANDARD}; + #[cfg(any(unix, windows))] + use std::io; - // Use OSC 52 to copy content to clipboard. - print!( - "\x1B]52;c;{}\x07", - BASE64_STANDARD.encode(format!("sendme receive {ticket}")) - ); + #[cfg(unix)] + use libc::{raise, SIGINT}; + + #[cfg(windows)] + use windows_sys::Win32::System::Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT}; + + if set_clipboard { + add_to_clipboard(&ticket); + } + + let _keyboard = tokio::task::spawn(async move { + println!("press c to copy command to clipboard, or use the --clipboard argument"); + + // `enable_raw_mode` will remember the current terminal mode + // and restore it when `disable_raw_mode` is called. + enable_raw_mode().unwrap_or_else(|err| eprintln!("Failed to enable raw mode: {err}")); + EventStream::new() + .for_each(move |e| match e { + Err(err) => eprintln!("Failed to process event: {err}"), + // c is pressed + Ok(Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + .. + })) => add_to_clipboard(&ticket), + // Ctrl+c is pressed + Ok(Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + .. + })) => { + disable_raw_mode() + .unwrap_or_else(|e| eprintln!("Failed to disable raw mode: {e}")); + + #[cfg(unix)] + // Safety: Raw syscall to re-send the SIGINT signal to the console. + // `raise` returns nonzero for failure. + if unsafe { raise(SIGINT) } != 0 { + eprintln!("Failed to raise signal: {}", io::Error::last_os_error()); + } + + #[cfg(windows)] + // Safety: Raw syscall to re-send the `CTRL_C_EVENT` to the console. + // `GenerateConsoleCtrlEvent` returns 0 for failure. + if unsafe { GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) } == 0 { + eprintln!( + "Failed to generate console event: {}", + io::Error::last_os_error() + ); + } + } + _ => {} + }) + .await + }); +} + +#[cfg(feature = "clipboard")] +fn add_to_clipboard(ticket: &BlobTicket) { + use std::io::stdout; + + use crossterm::{clipboard::CopyToClipboard, execute}; - stdout() - .flush() - .unwrap_or_else(|e| eprintln!("Failed to flush stdout: {e}")); + execute!( + stdout(), + CopyToClipboard::to_clipboard_from(format!("sendme receive {ticket}")) + ) + .unwrap_or_else(|e| eprintln!("Failed to copy to clipboard: {e}")); } const TICK_MS: u64 = 250;