From 436c46ba12eeb34dc9ed8fce104b77d14c1e5b03 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sun, 8 Jan 2023 01:47:35 -0600 Subject: [PATCH] try to polish it up again --- .gitignore | 1 + Cargo.lock | 868 +++++++++++++++++++++++++++++++++++++---------- Cargo.toml | 34 +- output.cast | 34 -- src/asciicast.rs | 14 +- src/errors.rs | 16 - src/lib.rs | 10 + src/main.rs | 63 +++- src/pty.rs | 203 ----------- src/recorder.rs | 310 +++++++++++++++-- 10 files changed, 1067 insertions(+), 486 deletions(-) delete mode 100644 src/errors.rs create mode 100644 src/lib.rs delete mode 100644 src/pty.rs diff --git a/.gitignore b/.gitignore index 3da6280..dc761d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /output.cast .direnv +/logs diff --git a/Cargo.lock b/Cargo.lock index 37d5474..b26b1f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,12 +3,27 @@ version = 3 [[package]] -name = "ansi_term" -version = "0.12.1" +name = "addr2line" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ - "winapi", + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", ] [[package]] @@ -16,36 +31,8 @@ name = "anyhow" version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" - -[[package]] -name = "asciinema" -version = "0.1.0" dependencies = [ - "anyhow", - "futures", - "libc", - "nix", - "parking_lot", - "serde", - "serde_derive", - "serde_json", - "signal-hook", - "structopt", - "termios", - "thiserror", - "tokio", - "tokio-util", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "backtrace", ] [[package]] @@ -54,6 +41,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -61,10 +63,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bytes" -version = "0.6.0" +name = "bumpalo" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" @@ -79,18 +87,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "clap" -version = "2.34.0" +name = "chrono" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "4.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ - "ansi_term", - "atty", "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", "strsim", - "textwrap", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", "unicode-width", - "vec_map", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cxx" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] @@ -183,22 +328,16 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.3.3" +name = "gimli" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -210,12 +349,49 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "iana-time-zone" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ - "cfg-if", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", ] [[package]] @@ -224,6 +400,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -236,6 +421,45 @@ version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "liveterm" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap", + "futures", + "libc", + "log", + "nix", + "parking_lot", + "serde", + "serde_derive", + "serde_json", + "signal-hook", + "termios", + "tokio", + "tokio-util", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -262,58 +486,97 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "mio" -version = "0.7.14" +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", + "autocfg", ] [[package]] -name = "miow" -version = "0.3.7" +name = "miniz_oxide" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ - "winapi", + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] name = "nix" -version = "0.19.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" dependencies = [ "bitflags", - "cc", "cfg-if", "libc", + "memoffset", + "pin-utils", + "static_assertions", ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] +[[package]] +name = "object" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -321,28 +584,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] -name = "parking_lot" -version = "0.11.2" +name = "os_str_bytes" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys", ] [[package]] @@ -408,6 +681,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.12" @@ -420,6 +713,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "serde" version = "1.0.152" @@ -449,10 +748,19 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.1.17" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -482,35 +790,27 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -523,6 +823,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termios" version = "0.3.3" @@ -533,61 +842,77 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "thread_local" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46409491c9375a693ce7032101970a54f8a2010efb77e13f70788f0d84489e39" -dependencies = [ - "autocfg", - "bytes", - "futures-core", - "libc", - "memchr", - "mio", - "num_cpus", "once_cell", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "slab", - "tokio-macros", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] -name = "tokio-macros" -version = "0.3.2" +name = "time" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46dfffa59fc3c8aad216ed61bdc2c263d2b9d87a9c8ac9de0c11a813e51b6db7" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] + +[[package]] +name = "tokio" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -596,16 +921,85 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.5.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3137de2b078e95274b696cc522e87f22c9a753fe3ef3344116ffb94f104f10a3" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel", + "time 0.3.17", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -614,12 +1008,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - [[package]] name = "unicode-width" version = "0.1.10" @@ -627,10 +1015,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] -name = "vec_map" -version = "0.8.2" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" @@ -638,6 +1026,72 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + [[package]] name = "winapi" version = "0.3.9" @@ -654,8 +1108,74 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" diff --git a/Cargo.toml b/Cargo.toml index e09778e..99886e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,25 @@ [package] -name = "asciinema" +name = "liveterm" version = "0.1.0" authors = ["Michael Zhang "] -edition = "2018" +edition = "2021" [dependencies] -anyhow = "1.0.34" -futures = "0.3.7" -libc = "0.2.80" -nix = "0.19.0" -parking_lot = "0.11.0" -serde = "1.0.117" -serde_derive = "1.0.117" -serde_json = "1.0.59" -signal-hook = "0.1.16" -structopt = "0.3.20" +anyhow = { version = "1.0.68", features = ["backtrace"] } +chrono = "0.4.23" +clap = { version = "4.0.32", features = ["derive"] } +futures = "0.3.25" +libc = "0.2.139" +log = "0.4.17" +nix = "0.26.1" +parking_lot = "0.12.1" +serde = "1.0.152" +serde_derive = "1.0.152" +serde_json = "1.0.91" +signal-hook = "0.3.14" termios = "0.3.3" -thiserror = "1.0.22" -tokio = { version = "0.3.3", features = ["full"] } -tokio-util = { version = "0.5.0", features = ["codec"] } +tokio = { version = "1.24.1", features = ["full"] } +tokio-util = { version = "0.7.4", features = ["codec"] } +tracing = "0.1.37" +tracing-appender = "0.2.2" +tracing-subscriber = "0.3.16" diff --git a/output.cast b/output.cast index 0f8ccba..e69de29 100644 --- a/output.cast +++ b/output.cast @@ -1,34 +0,0 @@ -[0.101333215,"o","\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] -[0.101467546,"o","\u001b]2;michael@manjaro: ~/Projects/asciinema\u0007\u001b]1;..cts/asciinema\u0007"] -[0.117281595,"o","\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[1m\u001b[34m#\u001b[00m \u001b[36mmichael \u001b[37m@ \u001b[32mmanjaro \u001b[37min \u001b[1m\u001b[33m~/Projects/asciinema\u001b[00m \u001b[37mon\u001b[00m git:\u001b[36mmaster \u001b[31mx\u001b[00m \u001b[37m[3:21:36] \r\n\u001b[1m\u001b[31m$ \u001b[00m\u001b[K"] -[0.117374656,"o","\u001b[?1h\u001b="] -[0.117667658,"o","\u001b[?2004h"] -[1.680256137,"i","w"] -[1.6861166810000001,"o","\u001b[32mw\u001b[39m"] -[1.7842199970000001,"i","h"] -[1.7917789800000001,"o","\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[1m\u001b[34m#\u001b[00m \u001b[36mmichael \u001b[37m@ \u001b[32mmanjaro \u001b[37min \u001b[1m\u001b[33m~/Projects/asciinema\u001b[00m \u001b[37mon\u001b[00m git:\u001b[36mmaster \u001b[31mx\u001b[00m \u001b[37m[3:21:38] \r\n\u001b[1m\u001b[31m$ \u001b[00mwh"] -[1.792416114,"o","\b\b\u001b[1m\u001b[31mw\u001b[1m\u001b[31mh\u001b[0m\u001b[39m"] -[1.8562032560000001,"i","a"] -[1.857965656,"o","\b\b\u001b[1m\u001b[31mw\u001b[1m\u001b[31mh\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"] -[1.960175246,"i","t"] -[1.968143781,"o","\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[1m\u001b[34m#\u001b[00m \u001b[36mmichael \u001b[37m@ \u001b[32mmanjaro \u001b[37min \u001b[1m\u001b[33m~/Projects/asciinema\u001b[00m \u001b[37mon\u001b[00m git:\u001b[36mmaster \u001b[31mx\u001b[00m \u001b[37m[3:21:38] \r\n\u001b[1m\u001b[31m$ \u001b[00mwhat"] -[1.9686859540000001,"o","\b\b\b\b\u001b[1m\u001b[31mw\u001b[1m\u001b[31mh\u001b[1m\u001b[31ma\u001b[1m\u001b[31mt\u001b[0m\u001b[39m"] -[2.352188361,"i"," "] -[2.35392467,"o"," "] -[2.592205933,"i","u"] -[2.593708682,"o","u"] -[2.64813527,"i","p"] -[2.6493960469999998,"o","p"] -[3.232210615,"i","\r"] -[3.232716968,"o","\u001b[?1l\u001b>"] -[3.233917915,"o","\u001b[?2004l\r\r\n"] -[3.23484062,"o","\u001b]2;what up\u0007\u001b]1;what\u0007"] -[3.235505654,"o","zsh: command not found: what\r\n"] -[3.235709995,"o","\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] -[3.235766455,"o","\u001b]2;michael@manjaro: ~/Projects/asciinema\u0007"] -[3.235797955,"o","\u001b]1;..cts/asciinema\u0007"] -[3.242549144,"o","\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[1m\u001b[34m#\u001b[00m \u001b[36mmichael \u001b[37m@ \u001b[32mmanjaro \u001b[37min \u001b[1m\u001b[33m~/Projects/asciinema\u001b[00m \u001b[37mon\u001b[00m git:\u001b[36mmaster \u001b[31mx\u001b[00m \u001b[37m[3:21:39] C:\u001b[31m127\u001b[00m\r\n\u001b[1m\u001b[31m$ \u001b[00m\u001b[K"] -[3.242670815,"o","\u001b[?1h\u001b="] -[3.243217488,"o","\u001b[?2004h"] -[3.800181468,"i","\u0004"] -[3.800319309,"o","\u001b[?2004l\r\r\n"] diff --git a/src/asciicast.rs b/src/asciicast.rs index e700830..c2d0e1d 100644 --- a/src/asciicast.rs +++ b/src/asciicast.rs @@ -1,3 +1,7 @@ +//! Data structures for representing the [asciicast v2] format +//! +//! [asciicast v2]: https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md + use std::collections::HashMap; use serde::ser::{Serialize, SerializeSeq, Serializer}; @@ -24,14 +28,14 @@ pub struct Theme { palette: String, } -pub struct Event<'a>(pub f64, pub EventKind<'a>); +pub struct Event(pub f64, pub EventKind); -pub enum EventKind<'a> { - Output(&'a [u8]), - Input(&'a [u8]), +pub enum EventKind { + Output(Vec), + Input(Vec), } -impl<'a> Serialize for Event<'a> { +impl Serialize for Event { fn serialize( &self, s: S, diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 61e5b8b..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub type Result = std::result::Result; - -#[derive(Debug, Error)] -pub enum Error { - #[error("generic nix error: {0}")] - Nix(#[from] nix::Error), - - #[error("write(3) error (fd={1}, data={2:?}): {0}")] - NixWrite(nix::Error, i32, Vec), - - #[error("generic io error: {0}")] - Io(#[from] std::io::Error), - - #[error("generic serde_json error: {0}")] - SerdeJson(#[from] serde_json::Error), -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bc9ccdb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate tracing; + +pub mod asciicast; +pub mod recorder; +// pub mod recorder; +// pub mod term; +// pub mod writer; diff --git a/src/main.rs b/src/main.rs index a979b2c..34cefa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,67 @@ #[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate thiserror; - -mod asciicast; -mod errors; -mod pty; -mod recorder; -mod term; -mod writer; +extern crate tracing; use std::fs::File; use anyhow::Result; -use structopt::StructOpt; +use clap::{ArgAction, Parser}; +use liveterm::recorder::Terminal; +use tokio::process::Command; +use tracing::Level; -use crate::writer::FileWriter; +#[derive(Parser)] +struct Opt { + #[clap(subcommand)] + subcommand: Subcommand, -#[derive(StructOpt)] -enum Command { + #[clap(long, short = 'v', action = ArgAction::Count, global = true)] + verbose: u8, +} + +#[derive(Parser)] +enum Subcommand { /// Record terminal session. #[structopt(name = "rec")] Record, } fn record() -> Result<()> { - let file = File::create("output.cast")?; - pty::record(&["sh", "-c", "/usr/bin/zsh"], FileWriter::new(file))?; + let _file = File::create("output.cast")?; + + let mut command = Command::new("/usr/bin/env"); + command.arg("bash"); + + let (pty, rx) = Terminal::setup(command)?; + pty.wait_until_complete()?; + Ok(()) } fn main() -> Result<()> { - match Command::from_args() { - Command::Record => { + let opt = Opt::parse(); + setup_logging(&opt)?; + + match opt.subcommand { + Subcommand::Record => { record()?; } } Ok(()) } + +fn setup_logging(opts: &Opt) -> Result<()> { + let file_appender = tracing_appender::rolling::hourly("logs", "liveterm.log"); + let max_level = match opts.verbose { + 0 => Level::ERROR, + 1 => Level::WARN, + 2 => Level::INFO, + 3 => Level::DEBUG, + _ => Level::TRACE, + }; + + tracing_subscriber::fmt() + .with_max_level(max_level) + .with_writer(file_appender) + .init(); + Ok(()) +} diff --git a/src/pty.rs b/src/pty.rs deleted file mode 100644 index 612c2b8..0000000 --- a/src/pty.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::env; -use std::ffi::CString; -use std::io; -use std::os::unix::io::RawFd; -use std::time::Instant; - -use nix::{ - fcntl::{fcntl, FcntlArg, OFlag}, - ioctl_write_buf, - pty::{forkpty, Winsize}, - sys::{ - select::{select, FdSet}, - termios::{tcsetattr, SetArg, Termios}, - wait::waitpid, - }, - unistd::{execvpe, isatty, pipe, read, write, ForkResult}, -}; -use signal_hook::SigId; - -use crate::errors::{Error, Result}; -use crate::writer::Writer; - -const STDIN_FILENO: RawFd = 0; -const STDOUT_FILENO: RawFd = 1; - -pub fn record(args: &[&str], writer: impl Writer + Send) -> Result<()> { - let forkpty_result = forkpty(None, None)?; - let master_fd = forkpty_result.master; - let start_time = Instant::now(); - - let _set_pty_size = || -> Result<()> { - ioctl_write_buf!(helper_write, libc::TIOCGWINSZ, 104, Winsize); - let winsize = Winsize { - ws_row: 24, - ws_col: 80, - ws_xpixel: 0, - ws_ypixel: 0, - }; - if isatty(STDOUT_FILENO)? {} - unsafe { helper_write(master_fd, &[winsize]) }?; - Ok(()) - }; - - let write_stdout = |data: &[u8]| -> Result<()> { - write(STDOUT_FILENO, &data)?; - Ok(()) - }; - - let mut master_writer = writer.clone(); - let mut handle_master_read = move |data: &[u8]| -> Result<()> { - let elapsed = (Instant::now() - start_time).as_secs_f64(); - master_writer.write_stdout(elapsed, data)?; - write_stdout(data)?; - Ok(()) - }; - - let write_master = |data: &[u8]| -> Result<()> { - let mut offset = 0; - while offset < data.len() { - let len = write(master_fd, data) - .map_err(|err| Error::NixWrite(err, master_fd, data.to_vec()))?; - offset += len; - } - Ok(()) - }; - - let mut stdin_writer = writer.clone(); - let mut handle_stdin_read = |data: &[u8]| -> Result<()> { - write_master(data)?; - let elapsed = (Instant::now() - start_time).as_secs_f64(); - stdin_writer.write_stdin(elapsed, data)?; - Ok(()) - }; - - let mut copy = |signal_fd: RawFd| -> Result<()> { - let mut fdset = FdSet::new(); - let mut buf = [0; 1024]; - - loop { - fdset.clear(); - fdset.insert(master_fd); - fdset.insert(STDIN_FILENO); - fdset.insert(signal_fd); - - select(None, &mut fdset, None, None, None)?; - - if fdset.contains(master_fd) { - let len = read(master_fd, &mut buf)?; - if len == 0 { - fdset.remove(master_fd); - } else { - handle_master_read(&buf[..len])?; - } - } else if fdset.contains(STDIN_FILENO) { - let len = read(STDIN_FILENO, &mut buf)?; - if len == 0 { - fdset.remove(STDIN_FILENO); - } else { - handle_stdin_read(&buf[..len])?; - } - } else if fdset.contains(signal_fd) { - // TODO - } - } - }; - - let child_pid = match forkpty_result.fork_result { - ForkResult::Parent { child } => child, - ForkResult::Child => { - let cstr_args: Vec<_> = args - .into_iter() - .map(|s| CString::new(*s).unwrap()) - .collect(); - let args: Vec<_> = cstr_args.iter().map(|s| s.as_ref()).collect(); - let cstr_env: Vec<_> = env::vars() - .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap()) - .collect(); - let env: Vec<_> = cstr_env.iter().map(|s| s.as_ref()).collect(); - execvpe(&args[0], &args, &env)?; - unreachable!(); - } - }; - - let (pipe_r, pipe_w) = pipe()?; - let mut flags = fcntl(pipe_w, FcntlArg::F_GETFL)?; - flags |= libc::O_NONBLOCK; - fcntl(pipe_w, FcntlArg::F_SETFL(OFlag::from_bits(flags).unwrap()))?; - - let old_handlers = set_signals( - [ - signal_hook::SIGWINCH, - signal_hook::SIGCHLD, - signal_hook::SIGHUP, - signal_hook::SIGTERM, - signal_hook::SIGQUIT, - ] - .iter() - .map(|sig| (*sig, move || eprintln!("HIT SIGNAL {:?}", *sig))), - )?; - - // set_pty_size()?; - - { - let _term = RawTerm::init(STDIN_FILENO)?; - copy(pipe_r)?; - } - unset_signals(old_handlers)?; - - waitpid(child_pid, None)?; - - Ok(()) -} - -fn set_signals(signals_list: I) -> Result, io::Error> -where - I: Iterator, - F: Fn() + Sync + Send + 'static, -{ - let mut old_handlers = Vec::new(); - for (sig, handler) in signals_list { - old_handlers.push(unsafe { signal_hook::register(sig, handler) }?); - } - Ok(old_handlers) -} - -fn unset_signals(handlers_list: Vec) -> Result<(), io::Error> { - for handler in handlers_list { - signal_hook::unregister(handler); - } - Ok(()) -} - -struct RawTerm(RawFd, Termios); - -impl RawTerm { - pub fn init(fd: RawFd) -> Result { - use nix::sys::termios::*; - let saved_mode = tcgetattr(fd)?; - let mut mode = saved_mode.clone(); - mode.input_flags &= !(InputFlags::BRKINT - | InputFlags::ICRNL - | InputFlags::INPCK - | InputFlags::ISTRIP - | InputFlags::IXON); - mode.output_flags &= !OutputFlags::OPOST; - mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB); - mode.control_flags |= ControlFlags::CS8; - mode.local_flags &= !(LocalFlags::ECHO - | LocalFlags::ICANON - | LocalFlags::IEXTEN - | LocalFlags::ISIG); - mode.control_chars[libc::VMIN] = 1; - mode.control_chars[libc::VTIME] = 0; - tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?; - Ok(RawTerm(fd, saved_mode)) - } -} - -impl Drop for RawTerm { - fn drop(&mut self) { - tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap(); - } -} diff --git a/src/recorder.rs b/src/recorder.rs index 45018e3..85273ad 100644 --- a/src/recorder.rs +++ b/src/recorder.rs @@ -1,30 +1,298 @@ -use std::env; -use std::path::PathBuf; +//! Setting up a recorder -pub struct RecordOptions { - pub path: PathBuf, - pub append: Option, - pub command: Option, - pub capture_env: Option>, - pub title: Option, +use std::os::unix::prelude::RawFd; +use std::sync::mpsc::{self, Receiver, Sender}; + +use anyhow::Result; +use libc::{STDIN_FILENO, STDOUT_FILENO}; +use nix::pty::{openpty, OpenptyResult}; +use nix::sys::select::{select, FdSet}; +use nix::unistd::{dup, dup2, fsync, read, write}; +use tokio::process::Command; + +use crate::asciicast::Event; + +pub struct Terminal { + /// The file descriptor that the parent process' original stdout got duplicated to. + /// + /// This needs to be saved so we can restore it later. + parent_stdout_dup: RawFd, + + pty: OpenptyResult, + + event_tx: Sender, } -pub fn record(options: RecordOptions) { - let _command = options - .command - .unwrap_or_else(|| env::var("SHELL").unwrap_or_else(|_| "sh".to_string())); +impl Terminal { + pub fn setup(mut command: Command) -> Result<(Self, Receiver)> { + command.env("TERM", "xterm-256color"); - let _command_env = env::vars(); + // Set up channels + let (event_tx, event_rx) = mpsc::channel::(); - // let header_env = HashMap::new(); + // Open a pty + let pty = openpty(None, None)?; + let parent_stdout_dup = dup(STDOUT_FILENO)?; + info!(parent_stdout_dup, "Duplicated parent process' stdout."); + dup2(pty.slave, STDOUT_FILENO)?; + info!( + redirected_to = pty.slave, + "Redirected parent process' stdout to new pty." + ); - // let (width, height) = term::get_size(); + eprintln!("Starting child process..."); - // let header = Header { - // version: 2, - // width, - // height, + // Spawn the child + let child = command.spawn()?; + let _child_pid = child.id(); - // title: options.title, - // }; + let term = Terminal { + parent_stdout_dup, + pty, + event_tx, + }; + Ok((term, event_rx)) + } + + pub fn wait_until_complete(self) -> Result<()> { + self.run()?; + + // Restore stdout + fsync(STDOUT_FILENO)?; + dup2(self.parent_stdout_dup, STDOUT_FILENO)?; + + Ok(()) + } + + fn run(&self) -> Result<()> { + let mut read_fd_set = FdSet::new(); + let mut buf = [0; 1024]; + + info!("Starting read loop..."); + loop { + // Set up fdsets + // asciinema does not capture stdin by default + read_fd_set.clear(); + read_fd_set.insert(self.pty.master); + read_fd_set.insert(STDIN_FILENO); + // TODO: Signal file descriptor + + select(None, &mut read_fd_set, None, None, None)?; + + // Master is ready for read, which means child process stdout has data + // (child process stdout) -> (pty.slave) -> (pty.master) + if read_fd_set.contains(self.pty.master) { + let bytes_read = read(self.pty.master, &mut buf)?; + if bytes_read == 0 { + info!("Read 0 bytes, exiting the read loop."); + break; + } + + let data = &buf[..bytes_read]; + debug!(bytes_read, "Read data from master."); + + // We should take this and rewrite this to the current stdout + write(STDOUT_FILENO, data)?; + } + // Stdin is ready for read, which means input from the user + else if read_fd_set.contains(STDIN_FILENO) { + let bytes_read = read(self.pty.master, &mut buf)?; + let data = &buf[..bytes_read]; + debug!(bytes_read, "Read data from master."); + + write(self.pty.master, data)?; + } + } + + Ok(()) + } } + +/* +pub fn record(args: &[&str], writer: impl Writer + Send) -> Result<()> { + // Setup + + let forkpty_result = forkpty(None, None)?; + let master_fd = forkpty_result.master; + let start_time = Instant::now(); + + let _set_pty_size = || -> Result<()> { + ioctl_write_buf!(helper_write, libc::TIOCGWINSZ, 104, Winsize); + let winsize = Winsize { + ws_row: 24, + ws_col: 80, + ws_xpixel: 0, + ws_ypixel: 0, + }; + if isatty(STDOUT_FILENO)? {} + unsafe { helper_write(master_fd, &[winsize]) }?; + Ok(()) + }; + + let write_stdout = |data: &[u8]| -> Result<()> { + write(STDOUT_FILENO, &data)?; + Ok(()) + }; + + let mut master_writer = writer.clone(); + let mut handle_master_read = move |data: &[u8]| -> Result<()> { + let elapsed = (Instant::now() - start_time).as_secs_f64(); + master_writer.write_stdout(elapsed, data)?; + write_stdout(data)?; + Ok(()) + }; + + let write_master = |data: &[u8]| -> Result<()> { + let mut offset = 0; + while offset < data.len() { + let len = write(master_fd, data) + .map_err(|err| Error::NixWrite(err, master_fd, data.to_vec()))?; + offset += len; + } + Ok(()) + }; + + let mut stdin_writer = writer.clone(); + let mut handle_stdin_read = |data: &[u8]| -> Result<()> { + write_master(data)?; + let elapsed = (Instant::now() - start_time).as_secs_f64(); + stdin_writer.write_stdin(elapsed, data)?; + Ok(()) + }; + + // Copy fds? + + let mut copy = |signal_fd: RawFd| -> Result<()> { + let mut fdset = FdSet::new(); + let mut buf = [0; 1024]; + + loop { + fdset.clear(); + fdset.insert(master_fd); + fdset.insert(STDIN_FILENO); + fdset.insert(signal_fd); + + select(None, &mut fdset, None, None, None)?; + + if fdset.contains(master_fd) { + let len = read(master_fd, &mut buf)?; + if len == 0 { + fdset.remove(master_fd); + } else { + handle_master_read(&buf[..len])?; + } + } else if fdset.contains(STDIN_FILENO) { + let len = read(STDIN_FILENO, &mut buf)?; + if len == 0 { + fdset.remove(STDIN_FILENO); + } else { + handle_stdin_read(&buf[..len])?; + } + } else if fdset.contains(signal_fd) { + // TODO + } + } + }; + + // Fork + + let child_pid = match forkpty_result.fork_result { + ForkResult::Parent { child } => child, + ForkResult::Child => { + let cstr_args: Vec<_> = args + .into_iter() + .map(|s| CString::new(*s).unwrap()) + .collect(); + let args: Vec<_> = cstr_args.iter().map(|s| s.as_ref()).collect(); + let cstr_env: Vec<_> = env::vars() + .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap()) + .collect(); + let env: Vec<_> = cstr_env.iter().map(|s| s.as_ref()).collect(); + execvpe(&args[0], &args, &env)?; + unreachable!(); + } + }; + + let (pipe_r, pipe_w) = pipe()?; + let mut flags = fcntl(pipe_w, FcntlArg::F_GETFL)?; + flags |= libc::O_NONBLOCK; + fcntl(pipe_w, FcntlArg::F_SETFL(OFlag::from_bits(flags).unwrap()))?; + + // Intercept signal handlers + + let old_handlers = set_signals( + [ + signal_hook::SIGWINCH, + signal_hook::SIGCHLD, + signal_hook::SIGHUP, + signal_hook::SIGTERM, + signal_hook::SIGQUIT, + ] + .iter() + .map(|sig| (*sig, move || eprintln!("HIT SIGNAL {:?}", *sig))), + )?; + + // set_pty_size()?; + + { + let _term = RawTerm::init(STDIN_FILENO)?; + copy(pipe_r)?; + } + unset_signals(old_handlers)?; + + waitpid(child_pid, None)?; + + Ok(()) +} + +fn set_signals(signals_list: I) -> Result, io::Error> +where + I: Iterator, + F: Fn() + Sync + Send + 'static, +{ + let mut old_handlers = Vec::new(); + for (sig, handler) in signals_list { + old_handlers.push(unsafe { signal_hook::register(sig, handler) }?); + } + Ok(old_handlers) +} + +fn unset_signals(handlers_list: Vec) -> Result<(), io::Error> { + for handler in handlers_list { + signal_hook::unregister(handler); + } + Ok(()) +} + +struct RawTerm(RawFd, Termios); + +impl RawTerm { + pub fn init(fd: RawFd) -> Result { + use nix::sys::termios::*; + let saved_mode = tcgetattr(fd)?; + let mut mode = saved_mode.clone(); + mode.input_flags &= !(InputFlags::BRKINT + | InputFlags::ICRNL + | InputFlags::INPCK + | InputFlags::ISTRIP + | InputFlags::IXON); + mode.output_flags &= !OutputFlags::OPOST; + mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB); + mode.control_flags |= ControlFlags::CS8; + mode.local_flags &= !(LocalFlags::ECHO + | LocalFlags::ICANON + | LocalFlags::IEXTEN + | LocalFlags::ISIG); + mode.control_chars[libc::VMIN] = 1; + mode.control_chars[libc::VTIME] = 0; + tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?; + Ok(RawTerm(fd, saved_mode)) + } +} + +impl Drop for RawTerm { + fn drop(&mut self) { + tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap(); + } +} +*/