From b74dca4fa136441b69194614982de09fc47d1e9d Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 20 Aug 2020 16:06:38 +0200 Subject: [PATCH] channels --- Cargo.lock | 541 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + src/main.rs | 484 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 909 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 488c298e..e0d91b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,12 +12,91 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +[[package]] +name = "async-channel" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43de69555a39d52918e2bc33a408d3c0a86c829b212d898f4ca25d21a6387478" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-std" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" +dependencies = [ + "async-task", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-timer", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "smol", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blocking" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2468ff7bf85066b4a3678fede6fe66db31846d753ff0adfbfab2c6a6e81612b" +dependencies = [ + "async-channel", + "atomic-waker", + "futures-lite", + "once_cell", + "parking", + "waker-fn", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + [[package]] name = "cc" version = "1.0.57" @@ -30,16 +109,231 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "concurrent-queue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e296417c8154304ac70aceda41f05318f986f7c0c767bcb0a2366fbb890e78e1" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "event-listener" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298f00c3b04c1d9b4cb86aefaaa35348af0957d98b30a5306fc635f8e718923d" + +[[package]] +name = "fastrand" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a9cb09840f81cd211e435d00a4e487edd263dc3c8ff815c32dd76ad668ebed" + +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-executor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-lite" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe71459749b2e8e66fb95df721b22fa08661ad384a0c5b519e11d3893b4692a" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + [[package]] name = "mosaic" version = "0.1.0" dependencies = [ + "async-std", + "futures", "libc", "nix", "signal-hook", @@ -62,6 +356,72 @@ dependencies = [ "void", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "parking" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c" + +[[package]] +name = "pin-project" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-hack" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + [[package]] name = "proc-macro2" version = "1.0.18" @@ -80,6 +440,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "signal-hook" version = "0.1.16" @@ -100,6 +478,56 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smol" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" +dependencies = [ + "async-task", + "blocking", + "concurrent-queue", + "fastrand", + "futures-io", + "futures-util", + "libc", + "once_cell", + "scoped-tls", + "slab", + "socket2", + "wepoll-sys-stjepang", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "termios" version = "0.3.2" @@ -162,3 +590,116 @@ dependencies = [ "proc-macro2", "quote", ] + +[[package]] +name = "waker-fn" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" + +[[package]] +name = "wasm-bindgen" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" + +[[package]] +name = "web-sys" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-sys-stjepang" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" +dependencies = [ + "cc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 6098c6af..19033587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,8 @@ signal-hook = "0.1.10" unicode-width = "0.1.8" unicode-truncate = "0.1.1" vte = "0.8.0" +futures = "0.3.5" + +[dependencies.async-std] +version = "1.3.0" +features = ["unstable"] diff --git a/src/main.rs b/src/main.rs index c9f69f8d..b9261405 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ -// use std::time::{Duration, Instant}; +use std::time::{Duration, Instant}; use std::io; +use futures::future::join_all; +use std::cell::Cell; use ::std::fmt::{self, Display, Formatter}; use std::cmp::max; use std::io::{Read, Write}; @@ -21,8 +23,59 @@ use std::os::unix::io::RawFd; use std::process::Command; use ::std::thread; use ::std::sync::{Arc, Mutex}; - use vte; +use async_std::stream::*; +use async_std::task; +use async_std::task::*; +use async_std::prelude::*; +use ::std::pin::*; +use std::sync::mpsc::{channel, Sender, Receiver}; + + + +struct ReadFromPid { + pid: RawFd, + read_buffer: [u8; 115200], +} + +impl ReadFromPid { + fn new(pid: &RawFd) -> ReadFromPid { + ReadFromPid { + pid: *pid, + read_buffer: [0; 115200], // TODO: ??? + } + } +} + +impl Stream for ReadFromPid { + type Item = Vec; + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let read_result = read(self.pid, &mut self.read_buffer); + match read_result { + Ok(res) => { + // TODO: this might become an issue with multiple panes sending data simultaneously + // ...consider returning None if res == 0 and handling it in the task (or sending + // Poll::Pending?) + let res = Some(self.read_buffer[..=res].to_vec()); + self.read_buffer = [0; 115200]; + return Poll::Ready(res) + }, + Err(e) => { + match e { + nix::Error::Sys(errno) => { + if errno == nix::errno::Errno::EAGAIN { + return Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow + // task::block_on(task::sleep(Duration::from_millis(10))); + } else { + panic!("error {:?}", e); + } + }, + _ => panic!("error {:?}", e) + } + } + } + } +} fn read_from_pid (pid: RawFd) -> Option> { let mut read_buffer = [0; 115200]; @@ -104,7 +157,8 @@ fn spawn_terminal (ws: &Winsize) -> (RawFd, RawFd) { let pid_primary = fork_pty_res.master; let pid_secondary = match fork_pty_res.fork_result { ForkResult::Parent { child } => { - fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl"); + fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl"); + // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl"); child }, ForkResult::Child => { @@ -175,6 +229,7 @@ impl Display for TerminalCharacter { } struct TerminalOutput { + pub pid: RawFd, pub characters: Vec, pub display_rows: u16, pub display_cols: u16, @@ -188,18 +243,47 @@ struct TerminalOutput { const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', ansi_code: None }; impl TerminalOutput { - pub fn new () -> TerminalOutput { + pub fn new (pid: RawFd, ws: Winsize) -> TerminalOutput { TerminalOutput { + pid, characters: vec![], cursor_position: 0, newline_indices: Vec::new(), linebreak_indices: Vec::new(), - display_rows: 0, - display_cols: 0, + display_rows: ws.ws_row, + display_cols: ws.ws_col, should_render: false, pending_ansi_code: None, } } + pub fn handle_event(&mut self, event: VteEvent) { + match event { + VteEvent::Print(c) => { + self.print(c); + }, + VteEvent::Execute(byte) => { + self.execute(byte); + }, + VteEvent::Hook(params, intermediates, ignore, c) => { + self.hook(¶ms, &intermediates, ignore, c); + }, + VteEvent::Put(byte) => { + self.put(byte); + }, + VteEvent::Unhook => { + self.unhook(); + }, + VteEvent::OscDispatch(params, bell_terminated) => { + self.osc_dispatch(params, bell_terminated); + }, + VteEvent::CsiDispatch(params, intermediates, ignore, c) => { + self.csi_dispatch(¶ms, &intermediates, ignore, c); + }, + VteEvent::EscDispatch(intermediates, ignore, byte) => { + self.esc_dispatch(&intermediates, ignore, byte); + } + } + } pub fn reduce_width(&mut self, count: u16) { self.display_cols -= count; self.reflow_lines(); @@ -236,10 +320,13 @@ impl TerminalOutput { x += 1; } } - pub fn read_buffer_as_lines (&mut self) -> Vec> { + pub fn read_buffer_as_lines (&self) -> Vec> { if DEBUGGING { return vec![]; } + if self.characters.len() == 0 { + return vec![]; + } let mut output: VecDeque> = VecDeque::new(); let mut i = self.characters.len(); let mut current_line: VecDeque<&TerminalCharacter> = VecDeque::new(); @@ -289,7 +376,7 @@ impl TerminalOutput { output.push_front(Vec::from(empty_line.clone())); } } - self.should_render = false; + // self.should_render = false; Vec::from(output) } pub fn cursor_position_in_last_line (&self) -> usize { @@ -390,7 +477,8 @@ impl TerminalOutput { const DEBUGGING: bool = false; -impl vte::Perform for TerminalOutput { +// vte methods +impl TerminalOutput { fn print(&mut self, c: char) { if DEBUGGING { println!("\r[print] {:?}", c); @@ -463,7 +551,9 @@ impl vte::Perform for TerminalOutput { } } - fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + // fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + // TODO: normalize vec/slices for all of these methods and the enum + fn osc_dispatch(&mut self, params: Vec>, bell_terminated: bool) { if DEBUGGING { println!("\r[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); } @@ -525,6 +615,73 @@ impl vte::Perform for TerminalOutput { } } +enum VteEvent { // TODO: try not to allocate Vecs + Print(char), + Execute(u8), // byte + Hook(Vec, Vec, bool, char), // params, intermediates, ignore, char + Put(u8), // byte + Unhook, + OscDispatch(Vec>, bool), // params, bell_terminated + CsiDispatch(Vec, Vec, bool, char), // params, intermediates, ignore, char + EscDispatch(Vec, bool, u8), // intermediates, ignore, byte +} + +struct VteEventSender { + id: RawFd, + sender: Sender, +} + +impl VteEventSender { + pub fn new (id: RawFd, sender: Sender) -> Self { + VteEventSender { id, sender } + } +} + +impl vte::Perform for VteEventSender { + fn print(&mut self, c: char) { + self.sender.send( + ScreenInstruction::Pty(self.id, VteEvent::Print(c)) + ).unwrap(); + } + fn execute(&mut self, byte: u8) { + self.sender.send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte))).unwrap(); + } + + fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + let params = params.iter().copied().collect(); + let intermediates = intermediates.iter().copied().collect(); + let instruction = ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c)); + self.sender.send(instruction).unwrap(); + } + + fn put(&mut self, byte: u8) { + self.sender.send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte))).unwrap(); + } + + fn unhook(&mut self) { + self.sender.send(ScreenInstruction::Pty(self.id, VteEvent::Unhook)).unwrap(); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + let params = params.iter().map(|p| p.to_vec()).collect(); + let instruction = ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated)); + self.sender.send(instruction).unwrap(); + } + + fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + let params = params.iter().copied().collect(); + let intermediates = intermediates.iter().copied().collect(); + let instruction = ScreenInstruction::Pty(self.id, VteEvent::CsiDispatch(params, intermediates, ignore, c)); + self.sender.send(instruction).unwrap(); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + let intermediates = intermediates.iter().copied().collect(); + let instruction = ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte)); + self.sender.send(instruction).unwrap(); + } +} + // sigwinch stuff use ::signal_hook::iterator::Signals; @@ -574,35 +731,92 @@ fn character_is_already_onscreen( last_character_style == current_character_style && last_character.character == current_character.character } +enum ScreenInstruction { + Pty(RawFd, VteEvent), + Render, + AddTerminal(RawFd, Winsize), + WriteCharacter(u8), + ResizeLeft, + ResizeRight, + MoveFocus, +} + struct Screen { + pub receiver: Receiver, + pub sender: Sender, + full_screen_ws: Winsize, last_frame: Option>, vertical_separator: TerminalCharacter, // TODO: better + active_terminal: Option, + terminal1_output: Option, + terminal2_output: Option, } impl Screen { pub fn new () -> Self { + let (sender, receiver): (Sender, Receiver) = channel(); + let full_screen_ws = get_terminal_size_using_fd(0); Screen { + receiver, + sender, + full_screen_ws, last_frame: None, vertical_separator: TerminalCharacter::new('|').ansi_code(String::from("\u{1b}[m")), // TODO: better + terminal1_output: None, + terminal2_output: None, + active_terminal: None, } } - pub fn render (&mut self, terminal1_output: &mut TerminalOutput, terminal2_output: &mut TerminalOutput, full_screen_ws: &Winsize, terminal1_is_active: bool) { - if DEBUGGING { + pub fn add_terminal(&mut self, pid: RawFd, ws: Winsize) { + if self.terminal1_output.is_none() { + self.terminal1_output = Some(TerminalOutput::new(pid, ws)); + self.active_terminal = Some(pid); + } else if self.terminal2_output.is_none() { + self.terminal2_output = Some(TerminalOutput::new(pid, ws)); + } else { + panic!("cannot support more than 2 terminals atm"); + } + } + pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) { + if let Some(terminal_output) = self.terminal1_output.as_mut() { + if terminal_output.pid == pid { + terminal_output.handle_event(event); + return; + } + } + if let Some(terminal_output) = self.terminal2_output.as_mut() { + if terminal_output.pid == pid { + terminal_output.handle_event(event); + return; + } + } + } + pub fn write_to_active_terminal(&self, byte: u8) { + if let Some(active_terminal) = &self.active_terminal { + let mut buffer = [byte]; + write(*active_terminal, &mut buffer).expect("failed to write to terminal"); + tcdrain(*active_terminal).expect("failed to drain terminal"); + } + } + pub fn render (&mut self) { + let left_terminal_lines = self.terminal1_output.as_ref().unwrap().read_buffer_as_lines(); + let right_terminal_lines = self.terminal2_output.as_ref().unwrap().read_buffer_as_lines(); + if left_terminal_lines.len() < self.full_screen_ws.ws_row as usize || right_terminal_lines.len() < self.full_screen_ws.ws_row as usize { + // TODO: this is hacky and is only here(?) for when the terminals are not ready yet return; } - let left_terminal_lines = terminal1_output.read_buffer_as_lines(); - let right_terminal_lines = terminal2_output.read_buffer_as_lines(); let mut frame: Vec<&TerminalCharacter> = vec![]; - for i in 0..full_screen_ws.ws_row { - let left_terminal_row = left_terminal_lines.get(i as usize).unwrap(); + let empty_vec = vec![]; // TODO: do not allocate, and less hacky + for i in 0..self.full_screen_ws.ws_row { + let left_terminal_row = left_terminal_lines.get(i as usize).unwrap_or(&empty_vec); for terminal_character in left_terminal_row.iter() { frame.push(terminal_character); } frame.push(&self.vertical_separator); - let right_terminal_row = right_terminal_lines.get(i as usize).unwrap(); + let right_terminal_row = right_terminal_lines.get(i as usize).unwrap_or(&empty_vec); for terminal_character in right_terminal_row.iter() { frame.push(terminal_character); } @@ -621,8 +835,8 @@ impl Screen { for i in 0..last_frame.len() { let last_character = last_frame.get(i).unwrap(); let current_character = frame.get(i).unwrap(); - let row = i / full_screen_ws.ws_col as usize + 1; - let col = i % full_screen_ws.ws_col as usize + 1; + let row = i / self.full_screen_ws.ws_col as usize + 1; + let col = i % self.full_screen_ws.ws_col as usize + 1; if !character_is_already_onscreen(&last_character, ¤t_character) { if !last_character_was_changed { data_lines.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", row, &col)); // goto row/col @@ -652,152 +866,180 @@ impl Screen { // TODO: consider looping through current frame and only updating the cells that changed self.last_frame = Some(frame.into_iter().cloned().collect::>()); - let left_terminal_cursor_position = terminal1_output.cursor_position_in_last_line(); - let right_terminal_cursor_position = terminal2_output.cursor_position_in_last_line(); - if terminal1_is_active { + let left_terminal_cursor_position = self.terminal1_output.as_ref().unwrap().cursor_position_in_last_line(); + let right_terminal_cursor_position = self.terminal2_output.as_ref().unwrap().cursor_position_in_last_line(); + + let active_terminal = self.active_terminal.unwrap(); + if active_terminal == self.terminal1_output.as_ref().unwrap().pid { data_lines.push_str(&format!("\r\u{1b}[{}C", left_terminal_cursor_position)); } else { - data_lines.push_str(&format!("\r\u{1b}[{}C", right_terminal_cursor_position + (terminal1_output.display_cols + 1) as usize)); + data_lines.push_str(&format!("\r\u{1b}[{}C", right_terminal_cursor_position + (self.terminal1_output.as_ref().unwrap().display_cols + 1) as usize)); } ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); ::std::io::stdout().flush().expect("could not flush"); } + pub fn resize_left (&mut self) { + let terminal1_output = self.terminal1_output.as_mut().unwrap(); + let terminal2_output = self.terminal2_output.as_mut().unwrap(); + terminal1_output.reduce_width(10); + terminal2_output.increase_width(10); + set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows); + set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows); + } + pub fn resize_right (&mut self) { + let terminal1_output = self.terminal1_output.as_mut().unwrap(); + let terminal2_output = self.terminal2_output.as_mut().unwrap(); + terminal1_output.increase_width(10); + terminal2_output.reduce_width(10); + set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows); + set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows); + } + pub fn move_focus(&mut self) { + let terminal1_output = self.terminal1_output.as_ref().unwrap(); + let terminal2_output = self.terminal2_output.as_ref().unwrap(); + let active_terminal = self.active_terminal.unwrap(); + if active_terminal == terminal1_output.pid { + self.active_terminal = Some(terminal2_output.pid); + } else { + self.active_terminal = Some(terminal1_output.pid); + } + self.render(); + } +} + +struct PtyBus { + sender: Sender, + active_ptys: Vec>, +} + +impl PtyBus { + pub fn new (sender: Sender) -> Self { + PtyBus { + sender, + active_ptys: Vec::new() + } + } + pub fn spawn_terminal(&mut self, ws: &Winsize) { + let ws = *ws; + let (pid_primary, _pid_secondary): (RawFd, RawFd) = spawn_terminal(&ws); + let task_handle = task::spawn({ + // let pid_primary = pid_primary.clone(); + let sender = self.sender.clone(); + async move { + let mut vte_parser = vte::Parser::new(); + let mut vte_event_sender = VteEventSender::new(pid_primary, sender.clone()); + let mut first_terminal_bytes = ReadFromPid::new(&pid_primary); + while let Some(bytes) = first_terminal_bytes.next().await { + let bytes_is_empty = bytes.is_empty(); + for byte in bytes { + vte_parser.advance(&mut vte_event_sender, byte); + } + if !bytes_is_empty { + sender.send(ScreenInstruction::Render).unwrap(); + } + } + } + }); + self.sender.send(ScreenInstruction::AddTerminal(pid_primary, ws)).unwrap(); + self.active_ptys.push(task_handle); + } + pub async fn wait_for_tasks(&mut self) { +// let task1 = self.active_ptys.get_mut(0).unwrap(); +// task1.await; + let mut v = vec![]; + for handle in self.active_ptys.iter_mut() { + // TODO: better, see commented lines above... can't we do this on the original vec? + v.push(handle); + } + join_all(v).await; + } } fn main() { let mut active_threads = vec![]; - let full_screen_ws = get_terminal_size_using_fd(0); - let (first_terminal_ws, second_terminal_ws) = split_horizontally_with_gap(&full_screen_ws); - let (first_terminal_pid, _pid_secondary): (RawFd, RawFd) = spawn_terminal(&first_terminal_ws); - let (second_terminal_pid, _pid_secondary): (RawFd, RawFd) = spawn_terminal(&second_terminal_ws); let stdin = io::stdin(); into_raw_mode(0); set_baud_rate(0); ::std::thread::sleep(std::time::Duration::from_millis(2000)); - let active_terminal = Arc::new(Mutex::new(first_terminal_pid)); - - let first_terminal_ws = Arc::new(Mutex::new(first_terminal_ws)); - let second_terminal_ws = Arc::new(Mutex::new(second_terminal_ws)); - - let terminal1_output = Arc::new(Mutex::new(TerminalOutput::new())); - let terminal2_output = Arc::new(Mutex::new(TerminalOutput::new())); - - let screen = Arc::new(Mutex::new(Screen::new())); + let mut screen = Screen::new(); + let send_screen_instructions = screen.sender.clone(); active_threads.push( thread::Builder::new() - .name("terminal_stdout_handler".to_string()) + .name("pty".to_string()) .spawn({ - - let mut vte_parser_terminal1 = vte::Parser::new(); - let mut vte_parser_terminal2 = vte::Parser::new(); - - let active_terminal = active_terminal.clone(); - let terminal1_output = terminal1_output.clone(); - let terminal2_output = terminal2_output.clone(); + let full_screen_ws = get_terminal_size_using_fd(0); + let (first_terminal_ws, second_terminal_ws) = split_horizontally_with_gap(&full_screen_ws); let first_terminal_ws = first_terminal_ws.clone(); let second_terminal_ws = second_terminal_ws.clone(); - let screen = screen.clone(); + let send_screen_instructions = send_screen_instructions.clone(); + move || { + let mut pty_bus = PtyBus::new(send_screen_instructions); + // this is done here so that we can add terminals dynamically on a different + // thread later + pty_bus.spawn_terminal(&first_terminal_ws); + pty_bus.spawn_terminal(&second_terminal_ws); + task::block_on(pty_bus.wait_for_tasks()); + + } + }).unwrap() + ); + + active_threads.push( + thread::Builder::new() + .name("screen".to_string()) + .spawn({ move || { - { - // TODO: better - let first_terminal_ws = first_terminal_ws.lock().unwrap(); - let second_terminal_ws = second_terminal_ws.lock().unwrap(); - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - terminal1_output.set_size(&first_terminal_ws); - terminal2_output.set_size(&second_terminal_ws); - } loop { - match (read_from_pid(first_terminal_pid), read_from_pid(second_terminal_pid)) { - (Some(first_terminal_read_bytes), Some(second_terminal_read_bytes)) => { - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - for byte in first_terminal_read_bytes.iter() { - vte_parser_terminal1.advance(&mut *terminal1_output, *byte); - } - for byte in second_terminal_read_bytes.iter() { - vte_parser_terminal2.advance(&mut *terminal2_output, *byte); - } + let event = screen.receiver + .recv() + .expect("failed to receive event on channel"); + match event { + ScreenInstruction::Pty(pid, vte_event) => { + screen.handle_pty_event(pid, vte_event); + }, + ScreenInstruction::Render => { + screen.render(); + }, + ScreenInstruction::AddTerminal(pid, ws) => { + screen.add_terminal(pid, ws); } - (Some(first_terminal_read_bytes), None) => { - let mut terminal1_output = terminal1_output.lock().unwrap(); - // let now = Instant::now(); - for byte in first_terminal_read_bytes.iter() { - vte_parser_terminal1.advance(&mut *terminal1_output, *byte); - } - // println!("\rParsed in {:?}", now.elapsed()); + ScreenInstruction::WriteCharacter(byte) => { + screen.write_to_active_terminal(byte); } - (None, Some(second_terminal_read_bytes)) => { - // let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - for byte in second_terminal_read_bytes.iter() { - vte_parser_terminal2.advance(&mut *terminal2_output, *byte); - } + ScreenInstruction::ResizeLeft => { + screen.resize_left(); } - (None, None) => { - ::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this + ScreenInstruction::ResizeRight => { + screen.resize_right(); + } + ScreenInstruction::MoveFocus => { + screen.move_focus(); } - } - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - if terminal1_output.should_render || terminal2_output.should_render { - let active_terminal = active_terminal.lock().unwrap(); - let mut screen = screen.lock().unwrap(); - // let now = Instant::now(); - screen.render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); - // println!("\r-------->R rendered in {:?}", now.elapsed()); } } } - }) - .unwrap(), + }).unwrap() ); + loop { let mut buffer = [0; 1]; { let mut handle = stdin.lock(); handle.read(&mut buffer).expect("failed to read stdin"); if buffer[0] == 10 { // ctrl-j - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - let active_terminal = active_terminal.lock().unwrap(); - terminal1_output.reduce_width(10); - terminal2_output.increase_width(10); - set_terminal_size_using_fd(first_terminal_pid, terminal1_output.display_cols, terminal1_output.display_rows); - set_terminal_size_using_fd(second_terminal_pid, terminal2_output.display_cols, terminal2_output.display_rows); - screen.lock().unwrap().render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + send_screen_instructions.send(ScreenInstruction::ResizeLeft).unwrap(); continue; } else if buffer[0] == 11 { // ctrl-k - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - let active_terminal = active_terminal.lock().unwrap(); - terminal1_output.increase_width(10); - terminal2_output.reduce_width(10); - set_terminal_size_using_fd(first_terminal_pid, terminal1_output.display_cols, terminal1_output.display_rows); - set_terminal_size_using_fd(second_terminal_pid, terminal2_output.display_cols, terminal2_output.display_rows); - screen.lock().unwrap().render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + send_screen_instructions.send(ScreenInstruction::ResizeRight).unwrap(); continue; } else if buffer[0] == 16 { // ctrl-p - let mut active_terminal = active_terminal.lock().unwrap(); - if *active_terminal == first_terminal_pid { - *active_terminal = second_terminal_pid; - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - screen.lock().unwrap().render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); - } else { - *active_terminal = first_terminal_pid; - let mut terminal1_output = terminal1_output.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - screen.lock().unwrap().render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); - } + send_screen_instructions.send(ScreenInstruction::MoveFocus).unwrap(); continue; } } - let active_terminal = active_terminal.lock().unwrap(); - write(*active_terminal, &mut buffer).expect("failed to write to terminal"); - tcdrain(*active_terminal).expect("failed to drain terminal"); + send_screen_instructions.send(ScreenInstruction::WriteCharacter(buffer[0])).unwrap(); }; // cleanup();