mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-15 19:28:00 +01:00
Initial version of stream command
This commit is contained in:
526
Cargo.lock
generated
526
Cargo.lock
generated
@@ -88,10 +88,14 @@ version = "3.0.0-beta.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"avt",
|
||||
"axum",
|
||||
"clap",
|
||||
"config",
|
||||
"futures-util",
|
||||
"mime_guess",
|
||||
"nix",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"rustyline",
|
||||
"scraper",
|
||||
"serde",
|
||||
@@ -99,6 +103,9 @@ dependencies = [
|
||||
"signal-hook",
|
||||
"tempfile",
|
||||
"termion",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower-http",
|
||||
"uuid",
|
||||
"which",
|
||||
]
|
||||
@@ -143,6 +150,64 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"base64",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper 1.1.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sha1",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
@@ -176,6 +241,15 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
@@ -307,6 +381,15 @@ version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
@@ -316,6 +399,16 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.29.6"
|
||||
@@ -343,6 +436,12 @@ dependencies = [
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
@@ -356,6 +455,16 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
@@ -489,36 +598,49 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
|
||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
|
||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
@@ -535,6 +657,16 @@ dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@@ -574,7 +706,26 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
@@ -643,6 +794,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
@@ -650,7 +812,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 0.2.11",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -676,9 +861,9 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"h2 0.3.24",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@@ -690,6 +875,25 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.2",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
@@ -697,13 +901,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"http 0.2.11",
|
||||
"hyper 0.14.28",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"hyper 1.1.0",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
@@ -805,6 +1027,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
@@ -844,9 +1072,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.8"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
@@ -1063,6 +1291,26 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
@@ -1241,10 +1489,10 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"h2 0.3.24",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.28",
|
||||
"hyper-rustls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
@@ -1295,6 +1543,40 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.38",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
@@ -1364,6 +1646,12 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "13.0.0"
|
||||
@@ -1392,6 +1680,15 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -1478,6 +1775,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -1500,6 +1807,28 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
@@ -1616,6 +1945,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
@@ -1673,6 +2008,26 @@ dependencies = [
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@@ -1690,20 +2045,34 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.33.0"
|
||||
version = "1.35.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
|
||||
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
@@ -1714,6 +2083,30 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"tokio",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.10"
|
||||
@@ -1737,6 +2130,44 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bytes",
|
||||
"http 1.0.0",
|
||||
"http-body 1.0.0",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
@@ -1749,6 +2180,7 @@ version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
@@ -1768,6 +2200,31 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.0.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
@@ -1854,6 +2311,16 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
@@ -1986,6 +2453,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
||||
@@ -26,3 +26,10 @@ which = "5.0.0"
|
||||
tempfile = "3.9.0"
|
||||
scraper = { version = "0.15.0", default-features = false }
|
||||
avt = "0.9.0"
|
||||
axum = { version = "0.7.4", features = ["ws"] }
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
futures-util = "0.3.30"
|
||||
tokio-stream = { version = "0.1.14", features = ["sync"] }
|
||||
rust-embed = "8.2.0"
|
||||
mime_guess = "2.0.4"
|
||||
tower-http = "0.5.1"
|
||||
|
||||
2835
assets/asciinema-player.css
Normal file
2835
assets/asciinema-player.css
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/asciinema-player.min.js
vendored
Normal file
1
assets/asciinema-player.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
67
assets/index.html
Normal file
67
assets/index.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="asciinema-player.css">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
padding: 12pt;
|
||||
background-color: #222;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="asciinema-player.min.js"></script>
|
||||
|
||||
<script>
|
||||
const loc = window.location;
|
||||
const params = new URLSearchParams(loc.hash.replace('#', '?'));
|
||||
|
||||
let bufferTime = params.get('bufferTime');
|
||||
|
||||
if (bufferTime === null) {
|
||||
if (loc.hostname === 'localhost' || loc.hostname === '127.0.0.1') {
|
||||
bufferTime = 0.01;
|
||||
} else {
|
||||
bufferTime = 0.1;
|
||||
}
|
||||
} else {
|
||||
bufferTime = parseFloat(bufferTime);
|
||||
};
|
||||
|
||||
const src = {
|
||||
driver: 'websocket',
|
||||
url: loc.protocol.replace("http", "ws") + '//' + loc.host + '/ws',
|
||||
bufferTime
|
||||
};
|
||||
|
||||
const fit = params.get('fit');
|
||||
const terminalLineHeight = params.get('terminalLineHeight');
|
||||
|
||||
const opts = {
|
||||
logger: console,
|
||||
fit: fit === null ? 'both' : fit,
|
||||
theme: params.get('theme'),
|
||||
autoPlay: params.get('autoPlay') !== 'false',
|
||||
terminalFontFamily: params.get('terminalFontFamily'),
|
||||
terminalLineHeight: terminalLineHeight === null ? undefined : parseFloat(terminalLineHeight)
|
||||
};
|
||||
|
||||
console.debug('initializing the player', { src, opts });
|
||||
|
||||
window.player = AsciinemaPlayer.create(src, document.body, opts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@ pub mod cat;
|
||||
pub mod convert;
|
||||
pub mod play;
|
||||
pub mod rec;
|
||||
pub mod stream;
|
||||
pub mod upload;
|
||||
use crate::config::Config;
|
||||
use crate::notifier;
|
||||
|
||||
88
src/cmd/stream.rs
Normal file
88
src/cmd/stream.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use crate::config::Config;
|
||||
use crate::locale;
|
||||
use crate::logger;
|
||||
use crate::pty;
|
||||
use crate::streamer::{self, KeyBindings};
|
||||
use crate::tty;
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Cli {
|
||||
/// Enable input capture
|
||||
#[arg(long, short = 'I', alias = "stdin")]
|
||||
input: bool,
|
||||
|
||||
/// Command to stream [default: $SHELL]
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// Override terminal size for the session
|
||||
#[arg(long, value_name = "COLSxROWS")]
|
||||
tty_size: Option<pty::WinsizeOverride>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(self, config: &Config) -> Result<()> {
|
||||
locale::check_utf8_locale()?;
|
||||
|
||||
let command = self.get_command(config);
|
||||
let keys = get_key_bindings(config)?;
|
||||
let notifier = super::get_notifier(config);
|
||||
let record_input = self.input || config.cmd_stream_input();
|
||||
let exec_command = super::build_exec_command(command.as_ref().cloned());
|
||||
let exec_extra_env = super::build_exec_extra_env();
|
||||
let mut streamer = streamer::Streamer::new(record_input, keys, notifier);
|
||||
|
||||
logger::info!(
|
||||
"Streaming session started, listening on {}",
|
||||
"127.0.0.1:3000" // TODO
|
||||
);
|
||||
|
||||
if command.is_none() {
|
||||
logger::info!("Press <ctrl+d> or type 'exit' to end");
|
||||
}
|
||||
|
||||
{
|
||||
let mut tty: Box<dyn tty::Tty> = if let Ok(dev_tty) = tty::DevTty::open() {
|
||||
Box::new(dev_tty)
|
||||
} else {
|
||||
logger::info!("TTY not available, streaming in headless mode");
|
||||
Box::new(tty::NullTty::open()?)
|
||||
};
|
||||
|
||||
pty::exec(
|
||||
&exec_command,
|
||||
&exec_extra_env,
|
||||
&mut *tty,
|
||||
self.tty_size,
|
||||
&mut streamer,
|
||||
)?;
|
||||
}
|
||||
|
||||
logger::info!("Streaming session ended");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_command(&self, config: &Config) -> Option<String> {
|
||||
self.command
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or(config.cmd_stream_command())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_bindings(config: &Config) -> Result<KeyBindings> {
|
||||
let mut keys = KeyBindings::default();
|
||||
|
||||
if let Some(key) = config.cmd_stream_prefix_key()? {
|
||||
keys.prefix = key;
|
||||
}
|
||||
|
||||
if let Some(key) = config.cmd_stream_pause_key()? {
|
||||
keys.pause = key;
|
||||
}
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
@@ -31,6 +31,7 @@ pub struct Server {
|
||||
pub struct Cmd {
|
||||
rec: Rec,
|
||||
play: Play,
|
||||
stream: Stream,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
@@ -55,6 +56,16 @@ pub struct Play {
|
||||
pub next_marker_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
#[allow(unused)]
|
||||
pub struct Stream {
|
||||
pub command: Option<String>,
|
||||
pub input: bool,
|
||||
pub env: Option<String>,
|
||||
pub prefix_key: Option<String>,
|
||||
pub pause_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Notifications {
|
||||
@@ -68,6 +79,7 @@ impl Config {
|
||||
.set_default("server.url", None::<Option<String>>)?
|
||||
.set_default("cmd.rec.input", false)?
|
||||
.set_default("cmd.play.speed", None::<Option<f64>>)?
|
||||
.set_default("cmd.stream.input", false)?
|
||||
.set_default("notifications.enabled", true)?
|
||||
.add_source(config::File::with_name("/etc/asciinema/config.toml").required(false))
|
||||
.add_source(
|
||||
@@ -175,6 +187,32 @@ impl Config {
|
||||
.map(parse_key)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn cmd_stream_command(&self) -> Option<String> {
|
||||
self.cmd.stream.command.as_ref().cloned()
|
||||
}
|
||||
|
||||
pub fn cmd_stream_input(&self) -> bool {
|
||||
self.cmd.stream.input
|
||||
}
|
||||
|
||||
pub fn cmd_stream_prefix_key(&self) -> Result<Option<Key>> {
|
||||
self.cmd
|
||||
.stream
|
||||
.prefix_key
|
||||
.as_ref()
|
||||
.map(parse_key)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn cmd_stream_pause_key(&self) -> Result<Option<Key>> {
|
||||
self.cmd
|
||||
.stream
|
||||
.pause_key
|
||||
.as_ref()
|
||||
.map(parse_key)
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
|
||||
fn ask_for_server_url() -> Result<String> {
|
||||
|
||||
@@ -9,6 +9,7 @@ mod notifier;
|
||||
mod player;
|
||||
mod pty;
|
||||
mod recorder;
|
||||
mod streamer;
|
||||
mod tty;
|
||||
mod util;
|
||||
use crate::config::Config;
|
||||
@@ -35,6 +36,9 @@ enum Commands {
|
||||
/// Replay a terminal session
|
||||
Play(cmd::play::Cli),
|
||||
|
||||
/// Stream a terminal session
|
||||
Stream(cmd::stream::Cli),
|
||||
|
||||
/// Concatenate multiple recordings
|
||||
Cat(cmd::cat::Cli),
|
||||
|
||||
@@ -55,6 +59,7 @@ fn main() -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Rec(record) => record.run(&config),
|
||||
Commands::Play(play) => play.run(&config),
|
||||
Commands::Stream(stream) => stream.run(&config),
|
||||
Commands::Cat(cat) => cat.run(),
|
||||
Commands::Convert(convert) => convert.run(),
|
||||
Commands::Upload(upload) => upload.run(&config),
|
||||
|
||||
70
src/streamer/alis.rs
Normal file
70
src/streamer/alis.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use super::session;
|
||||
use anyhow::Result;
|
||||
use futures_util::{stream, Stream, StreamExt, TryStreamExt};
|
||||
use std::future;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
|
||||
|
||||
static HEADER: &str = "ALiS\x01\x00\x00\x00\x00\x00";
|
||||
static SECOND: f64 = 1_000_000.0;
|
||||
|
||||
pub async fn stream(
|
||||
clients_tx: &mpsc::Sender<session::Client>,
|
||||
) -> Result<impl Stream<Item = Result<Vec<u8>, BroadcastStreamRecvError>>> {
|
||||
let header = stream::once(future::ready(Ok(HEADER.into())));
|
||||
let events = session::stream(clients_tx).await?.map_ok(encode_event);
|
||||
|
||||
Ok(header.chain(events))
|
||||
}
|
||||
|
||||
fn encode_event(event: session::Event) -> Vec<u8> {
|
||||
use session::Event::*;
|
||||
|
||||
match event {
|
||||
Init(size, time, init) => {
|
||||
let (cols, rows): (u16, u16) = (size.0, size.1);
|
||||
let cols_bytes = cols.to_le_bytes();
|
||||
let rows_bytes = rows.to_le_bytes();
|
||||
let time_bytes = ((time as f64 / SECOND) as f32).to_le_bytes();
|
||||
let init = init.unwrap_or_else(|| "".to_owned());
|
||||
let init_len = init.len() as u32;
|
||||
let init_len_bytes = init_len.to_le_bytes();
|
||||
|
||||
let mut msg = vec![0x01]; // 1 byte
|
||||
msg.extend_from_slice(&cols_bytes); // 2 bytes
|
||||
msg.extend_from_slice(&rows_bytes); // 2 bytes
|
||||
msg.extend_from_slice(&time_bytes); // 4 bytes
|
||||
msg.extend_from_slice(&init_len_bytes); // 4 bytes
|
||||
msg.extend_from_slice(init.as_bytes()); // init_len bytes
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
Stdout(time, text) => {
|
||||
let time_bytes = ((time as f64 / SECOND) as f32).to_le_bytes();
|
||||
let text_len = text.len() as u32;
|
||||
let text_len_bytes = text_len.to_le_bytes();
|
||||
|
||||
let mut msg = vec![b'o']; // 1 byte
|
||||
msg.extend_from_slice(&time_bytes); // 4 bytes
|
||||
msg.extend_from_slice(&text_len_bytes); // 4 bytes
|
||||
msg.extend_from_slice(text.as_bytes()); // text_len bytes
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
Resize(time, size) => {
|
||||
let (cols, rows): (u16, u16) = (size.0, size.1);
|
||||
let time_bytes = ((time as f64 / SECOND) as f32).to_le_bytes();
|
||||
let cols_bytes = cols.to_le_bytes();
|
||||
let rows_bytes = rows.to_le_bytes();
|
||||
|
||||
let mut msg = vec![b'r']; // 1 byte
|
||||
msg.extend_from_slice(&time_bytes); // 4 bytes
|
||||
msg.extend_from_slice(&cols_bytes); // 2 bytes
|
||||
msg.extend_from_slice(&rows_bytes); // 2 bytes
|
||||
|
||||
msg
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/streamer/mod.rs
Normal file
214
src/streamer/mod.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
mod alis;
|
||||
mod server;
|
||||
mod session;
|
||||
use crate::config::Key;
|
||||
use crate::notifier::Notifier;
|
||||
use crate::pty;
|
||||
use crate::tty;
|
||||
use crate::util;
|
||||
use std::io;
|
||||
use std::net::TcpListener;
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct Streamer {
|
||||
record_input: bool,
|
||||
keys: KeyBindings,
|
||||
notifier: Option<Box<dyn Notifier>>,
|
||||
notifier_tx: std::sync::mpsc::Sender<String>,
|
||||
notifier_rx: Option<std::sync::mpsc::Receiver<String>>,
|
||||
notifier_handle: Option<util::JoinHandle>,
|
||||
pty_tx: mpsc::UnboundedSender<Event>,
|
||||
pty_rx: Option<mpsc::UnboundedReceiver<Event>>,
|
||||
event_loop_handle: Option<util::JoinHandle>,
|
||||
start_time: Instant,
|
||||
paused: bool,
|
||||
prefix_mode: bool,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Output(u64, String),
|
||||
Input(u64, String),
|
||||
Resize(u64, tty::TtySize),
|
||||
}
|
||||
|
||||
impl Streamer {
|
||||
pub fn new(record_input: bool, keys: KeyBindings, notifier: Box<dyn Notifier>) -> Self {
|
||||
let (notifier_tx, notifier_rx) = std::sync::mpsc::channel();
|
||||
let (pty_tx, pty_rx) = mpsc::unbounded_channel();
|
||||
|
||||
Self {
|
||||
record_input,
|
||||
keys,
|
||||
notifier: Some(notifier),
|
||||
notifier_tx,
|
||||
notifier_rx: Some(notifier_rx),
|
||||
notifier_handle: None,
|
||||
pty_tx,
|
||||
pty_rx: Some(pty_rx),
|
||||
event_loop_handle: None,
|
||||
start_time: Instant::now(),
|
||||
paused: false,
|
||||
prefix_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn elapsed_time(&self) -> u64 {
|
||||
self.start_time.elapsed().as_micros() as u64
|
||||
}
|
||||
|
||||
fn notify<S: ToString>(&self, message: S) {
|
||||
self.notifier_tx
|
||||
.send(message.to_string())
|
||||
.expect("notification send should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
impl pty::Recorder for Streamer {
|
||||
fn start(&mut self, tty_size: tty::TtySize) -> io::Result<()> {
|
||||
let pty_rx = self.pty_rx.take().unwrap();
|
||||
let (clients_tx, mut clients_rx) = mpsc::channel(1);
|
||||
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
||||
let listener = TcpListener::bind("0.0.0.0:3000")?;
|
||||
let runtime = build_tokio_runtime();
|
||||
let server = runtime.spawn(server::serve(listener, clients_tx, server_shutdown_rx));
|
||||
|
||||
self.event_loop_handle = wrap_thread_handle(thread::spawn(move || {
|
||||
runtime.block_on(async move {
|
||||
event_loop(pty_rx, &mut clients_rx, tty_size).await;
|
||||
let _ = server_shutdown_tx.send(());
|
||||
let _ = server.await;
|
||||
let _ = clients_rx.recv().await;
|
||||
});
|
||||
}));
|
||||
|
||||
let mut notifier = self.notifier.take().unwrap();
|
||||
let notifier_rx = self.notifier_rx.take().unwrap();
|
||||
|
||||
self.notifier_handle = wrap_thread_handle(thread::spawn(move || {
|
||||
for message in notifier_rx {
|
||||
let _ = notifier.notify(message);
|
||||
}
|
||||
}));
|
||||
|
||||
self.start_time = Instant::now();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output(&mut self, data: &[u8]) {
|
||||
if self.paused {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = String::from_utf8_lossy(data).to_string();
|
||||
let event = Event::Output(self.elapsed_time(), data);
|
||||
self.pty_tx.send(event).expect("output send should succeed");
|
||||
}
|
||||
|
||||
fn input(&mut self, data: &[u8]) -> bool {
|
||||
let prefix_key = self.keys.prefix.as_ref();
|
||||
let pause_key = self.keys.pause.as_ref();
|
||||
|
||||
if !self.prefix_mode && prefix_key.is_some_and(|key| data == key) {
|
||||
self.prefix_mode = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.prefix_mode || prefix_key.is_none() {
|
||||
self.prefix_mode = false;
|
||||
|
||||
if pause_key.is_some_and(|key| data == key) {
|
||||
if self.paused {
|
||||
self.paused = false;
|
||||
self.notify("Resumed streaming");
|
||||
} else {
|
||||
self.paused = true;
|
||||
self.notify("Paused streaming");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if self.record_input && !self.paused {
|
||||
// TODO ignore OSC responses
|
||||
let data = String::from_utf8_lossy(data).to_string();
|
||||
let event = Event::Input(self.elapsed_time(), data);
|
||||
self.pty_tx.send(event).expect("input send should succeed");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn resize(&mut self, size: crate::tty::TtySize) {
|
||||
let event = Event::Resize(self.elapsed_time(), size);
|
||||
self.pty_tx.send(event).expect("resize send should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
async fn event_loop(
|
||||
mut events: mpsc::UnboundedReceiver<Event>,
|
||||
clients: &mut mpsc::Receiver<session::Client>,
|
||||
tty_size: tty::TtySize,
|
||||
) {
|
||||
let mut session = session::Session::new(tty_size);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = events.recv() => {
|
||||
match event {
|
||||
Some(Event::Output(time, data)) => {
|
||||
session.output(time, data);
|
||||
}
|
||||
|
||||
Some(Event::Input(time, data)) => {
|
||||
session.input(time, data);
|
||||
}
|
||||
|
||||
Some(Event::Resize(time, new_tty_size)) => {
|
||||
session.resize(time, new_tty_size);
|
||||
}
|
||||
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
client = clients.recv() => {
|
||||
match client {
|
||||
Some(client) => {
|
||||
client.accept(session.subscribe());
|
||||
}
|
||||
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tokio_runtime() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn wrap_thread_handle(handle: thread::JoinHandle<()>) -> Option<util::JoinHandle> {
|
||||
Some(util::JoinHandle::new(handle))
|
||||
}
|
||||
|
||||
pub struct KeyBindings {
|
||||
pub prefix: Key,
|
||||
pub pause: Key,
|
||||
}
|
||||
|
||||
impl Default for KeyBindings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prefix: None,
|
||||
pause: Some(vec![0x1c]), // ^\
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/streamer/server.rs
Normal file
105
src/streamer/server.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use super::alis;
|
||||
use super::session;
|
||||
use axum::{
|
||||
extract::connect_info::ConnectInfo,
|
||||
extract::ws,
|
||||
extract::State,
|
||||
http::{header, StatusCode, Uri},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use futures_util::{stream, StreamExt};
|
||||
use rust_embed::RustEmbed;
|
||||
use std::borrow::Cow;
|
||||
use std::future;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "assets/"]
|
||||
struct Assets;
|
||||
|
||||
pub async fn serve(
|
||||
listener: std::net::TcpListener,
|
||||
clients_tx: mpsc::Sender<session::Client>,
|
||||
shutdown_rx: oneshot::Receiver<()>,
|
||||
) -> io::Result<()> {
|
||||
listener.set_nonblocking(true)?;
|
||||
let listener = tokio::net::TcpListener::from_std(listener)?;
|
||||
|
||||
|
||||
let app = Router::new()
|
||||
.route("/ws", get(ws_handler))
|
||||
.with_state(clients_tx)
|
||||
.fallback(static_handler);
|
||||
|
||||
let signal = async {
|
||||
let _ = shutdown_rx.await;
|
||||
};
|
||||
|
||||
axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.with_graceful_shutdown(signal)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn static_handler(uri: Uri) -> impl IntoResponse {
|
||||
let mut path = uri.path().trim_start_matches('/');
|
||||
|
||||
if path.is_empty() {
|
||||
path = "index.html";
|
||||
}
|
||||
|
||||
match Assets::get(path) {
|
||||
Some(content) => {
|
||||
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
||||
|
||||
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
|
||||
}
|
||||
|
||||
None => (StatusCode::NOT_FOUND, "404").into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ws_handler(
|
||||
ws: ws::WebSocketUpgrade,
|
||||
ConnectInfo(_addr): ConnectInfo<SocketAddr>,
|
||||
State(clients_tx): State<mpsc::Sender<session::Client>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
let _ = handle_socket(socket, clients_tx).await;
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_socket(
|
||||
socket: ws::WebSocket,
|
||||
clients_tx: mpsc::Sender<session::Client>,
|
||||
) -> anyhow::Result<()> {
|
||||
alis::stream(&clients_tx)
|
||||
.await?
|
||||
.map(ws_result)
|
||||
.chain(stream::once(future::ready(Ok(close_message()))))
|
||||
.forward(socket)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_message() -> ws::Message {
|
||||
ws::Message::Close(Some(ws::CloseFrame {
|
||||
code: ws::close_code::NORMAL,
|
||||
reason: Cow::from("ended"),
|
||||
}))
|
||||
}
|
||||
|
||||
fn ws_result(m: Result<Vec<u8>, BroadcastStreamRecvError>) -> Result<ws::Message, axum::Error> {
|
||||
match m {
|
||||
Ok(bytes) => Ok(ws::Message::Binary(bytes)),
|
||||
Err(e) => Err(axum::Error::new(e)),
|
||||
}
|
||||
}
|
||||
104
src/streamer/session.rs
Normal file
104
src/streamer/session.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::tty;
|
||||
use anyhow::Result;
|
||||
use futures_util::{stream, Stream, StreamExt};
|
||||
use std::{future, time::Instant};
|
||||
use tokio::sync::{broadcast, mpsc, oneshot};
|
||||
use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream};
|
||||
|
||||
pub struct Session {
|
||||
vt: avt::Vt,
|
||||
broadcast_tx: broadcast::Sender<Event>,
|
||||
stream_time: u64,
|
||||
last_event_time: Instant,
|
||||
tty_size: tty::TtySize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Event {
|
||||
Init(tty::TtySize, u64, Option<String>),
|
||||
Stdout(u64, String),
|
||||
Resize(u64, tty::TtySize),
|
||||
}
|
||||
|
||||
pub struct Client(oneshot::Sender<Subscription>);
|
||||
|
||||
pub struct Subscription {
|
||||
init: Event,
|
||||
broadcast_rx: broadcast::Receiver<Event>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(tty_size: tty::TtySize) -> Self {
|
||||
let (broadcast_tx, _) = broadcast::channel(1024);
|
||||
|
||||
Self {
|
||||
vt: build_vt(tty_size),
|
||||
broadcast_tx,
|
||||
stream_time: 0,
|
||||
last_event_time: Instant::now(),
|
||||
tty_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output(&mut self, time: u64, data: String) {
|
||||
self.vt.feed_str(&data);
|
||||
let _ = self.broadcast_tx.send(Event::Stdout(time, data));
|
||||
self.stream_time = time;
|
||||
self.last_event_time = Instant::now();
|
||||
}
|
||||
|
||||
pub fn input(&mut self, time: u64, _data: String) {
|
||||
self.stream_time = time;
|
||||
self.last_event_time = Instant::now();
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, time: u64, tty_size: tty::TtySize) {
|
||||
if tty_size != self.tty_size {
|
||||
resize_vt(&mut self.vt, &tty_size);
|
||||
let _ = self.broadcast_tx.send(Event::Resize(time, tty_size));
|
||||
self.stream_time = time;
|
||||
self.last_event_time = Instant::now();
|
||||
self.tty_size = tty_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> Subscription {
|
||||
let init = Event::Init(self.tty_size, self.elapsed_time(), Some(self.vt.dump()));
|
||||
let broadcast_rx = self.broadcast_tx.subscribe();
|
||||
|
||||
Subscription { init, broadcast_rx }
|
||||
}
|
||||
|
||||
fn elapsed_time(&self) -> u64 {
|
||||
self.stream_time + self.last_event_time.elapsed().as_micros() as u64
|
||||
}
|
||||
}
|
||||
|
||||
fn build_vt(tty_size: tty::TtySize) -> avt::Vt {
|
||||
avt::Vt::builder()
|
||||
.size(tty_size.0 as usize, tty_size.1 as usize)
|
||||
.resizable(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn resize_vt(vt: &mut avt::Vt, tty_size: &tty::TtySize) {
|
||||
vt.feed_str(&format!("\x1b[8;{};{}t", tty_size.1, tty_size.0));
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn accept(self, subscription: Subscription) {
|
||||
let _ = self.0.send(subscription);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stream(
|
||||
clients_tx: &mpsc::Sender<Client>,
|
||||
) -> Result<impl Stream<Item = Result<Event, BroadcastStreamRecvError>>> {
|
||||
let (sub_tx, sub_rx) = oneshot::channel();
|
||||
clients_tx.send(Client(sub_tx)).await?;
|
||||
let sub = sub_rx.await?;
|
||||
let init = stream::once(future::ready(Ok(sub.init)));
|
||||
let events = BroadcastStream::new(sub.broadcast_rx);
|
||||
|
||||
Ok(init.chain(events))
|
||||
}
|
||||
Reference in New Issue
Block a user