This commit is contained in:
Michael Zhang 2023-01-07 18:31:39 -06:00
parent 2997304edc
commit 9108e7d28d
9 changed files with 377 additions and 359 deletions

158
Cargo.lock generated
View file

@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.57" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]] [[package]]
name = "asciinema" name = "asciinema"
@ -43,7 +43,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi 0.1.19",
"libc", "libc",
"winapi", "winapi",
] ]
@ -68,9 +68,9 @@ checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.73" version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -95,9 +95,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -110,9 +110,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -120,15 +120,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -137,15 +137,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -154,21 +154,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.21" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -200,6 +200,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -211,9 +220,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
@ -223,15 +232,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.125" version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.7" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@ -297,19 +306,19 @@ dependencies = [
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.1" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi 0.2.6",
"libc", "libc",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.10.0" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@ -324,9 +333,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"instant", "instant",
@ -374,36 +383,36 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.38" version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [ dependencies = [
"unicode-xid", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.18" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
@ -413,15 +422,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.137" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.137" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -430,9 +439,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.81" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -460,15 +469,18 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.8.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -502,13 +514,13 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.92" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"unicode-xid", "unicode-ident",
] ]
[[package]] [[package]]
@ -531,18 +543,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -597,22 +609,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-ident"
version = "1.9.0" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]] [[package]]
name = "vec_map" name = "vec_map"

22
flake.lock generated
View file

@ -2,9 +2,7 @@
"nodes": { "nodes": {
"fenix": { "fenix": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": "nixpkgs",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
@ -36,6 +34,22 @@
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": {
"lastModified": 1672953546,
"narHash": "sha256-oz757DnJ1ITvwyTovuwG3l9cX6j9j6/DH9eH+cXFJmc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a518c77148585023ff56022f09c4b2c418a51ef5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1667629849, "lastModified": 1667629849,
"narHash": "sha256-P+v+nDOFWicM4wziFK9S/ajF2lc0N2Rg9p6Y35uMoZI=", "narHash": "sha256-P+v+nDOFWicM4wziFK9S/ajF2lc0N2Rg9p6Y35uMoZI=",
@ -53,7 +67,7 @@
"inputs": { "inputs": {
"fenix": "fenix", "fenix": "fenix",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs_2"
} }
}, },
"rust-analyzer-src": { "rust-analyzer-src": {

View file

@ -1,8 +1,5 @@
{ {
inputs = { inputs = { fenix.url = "github:nix-community/fenix"; };
fenix.url = "github:nix-community/fenix";
fenix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, flake-utils, fenix }: outputs = { self, nixpkgs, flake-utils, fenix }:
flake-utils.lib.eachDefaultSystem (system: flake-utils.lib.eachDefaultSystem (system:
@ -12,7 +9,7 @@
overlays = [ fenix.overlays.default ]; overlays = [ fenix.overlays.default ];
}; };
toolchain = pkgs.fenix.default; toolchain = pkgs.fenix.stable;
flakePkgs = { flakePkgs = {
asciinema = pkgs.callPackage ./. { inherit toolchain; }; asciinema = pkgs.callPackage ./. { inherit toolchain; };
@ -23,9 +20,9 @@
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
inputsFrom = with packages; [ asciinema ]; inputsFrom = with packages; [ asciinema ];
packages = packages = (with pkgs; [ cargo-watch cargo-deny cargo-edit ])
(with pkgs; [ cargo-watch cargo-deny cargo-edit sqlx-cli sqlite ]) ++ (with toolchain; [ cargo rustc rustfmt ]);
++ (with toolchain; [ cargo rustc ]); CARGO_UNSTABLE_SPARSE_REGISTRY = "true";
}; };
}); });
} }

View file

@ -4,66 +4,66 @@ use serde::ser::{Serialize, SerializeSeq, Serializer};
#[derive(Serialize)] #[derive(Serialize)]
pub struct Header { pub struct Header {
version: u32, version: u32,
width: u32, width: u32,
height: u32, height: u32,
timestamp: Option<u32>, timestamp: Option<u32>,
duration: Option<f64>, duration: Option<f64>,
idle_time_limit: Option<f64>, idle_time_limit: Option<f64>,
command: Option<String>, command: Option<String>,
title: Option<String>, title: Option<String>,
env: Option<HashMap<String, String>>, env: Option<HashMap<String, String>>,
theme: Option<Theme>, theme: Option<Theme>,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub struct Theme { pub struct Theme {
fg: String, fg: String,
bg: String, bg: String,
palette: String, palette: String,
} }
pub struct Event<'a>(pub f64, pub EventKind<'a>); pub struct Event<'a>(pub f64, pub EventKind<'a>);
pub enum EventKind<'a> { pub enum EventKind<'a> {
Output(&'a [u8]), Output(&'a [u8]),
Input(&'a [u8]), Input(&'a [u8]),
} }
impl<'a> Serialize for Event<'a> { impl<'a> Serialize for Event<'a> {
fn serialize<S: Serializer>( fn serialize<S: Serializer>(
&self, &self,
s: S, s: S,
) -> std::result::Result<S::Ok, S::Error> { ) -> std::result::Result<S::Ok, S::Error> {
let mut seq = s.serialize_seq(Some(3))?; let mut seq = s.serialize_seq(Some(3))?;
seq.serialize_element(&self.0)?; seq.serialize_element(&self.0)?;
match &self.1 { match &self.1 {
EventKind::Output(s) => { EventKind::Output(s) => {
seq.serialize_element("o")?; seq.serialize_element("o")?;
seq.serialize_element(std::str::from_utf8(s).unwrap())?; seq.serialize_element(std::str::from_utf8(s).unwrap())?;
} }
EventKind::Input(s) => { EventKind::Input(s) => {
seq.serialize_element("i")?; seq.serialize_element("i")?;
seq.serialize_element(std::str::from_utf8(s).unwrap())?; seq.serialize_element(std::str::from_utf8(s).unwrap())?;
} }
}
seq.end()
} }
seq.end()
}
} }
fn build_header(width: u32, height: u32) -> Header { fn build_header(width: u32, height: u32) -> Header {
Header { Header {
version: 2, version: 2,
width, width,
height, height,
timestamp: None, timestamp: None,
duration: None, duration: None,
idle_time_limit: None, idle_time_limit: None,
command: None, command: None,
title: None, title: None,
env: None, env: None,
theme: None, theme: None,
} }
} }

View file

@ -2,15 +2,15 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("generic nix error: {0}")] #[error("generic nix error: {0}")]
Nix(#[from] nix::Error), Nix(#[from] nix::Error),
#[error("write(3) error (fd={1}, data={2:?}): {0}")] #[error("write(3) error (fd={1}, data={2:?}): {0}")]
NixWrite(nix::Error, i32, Vec<u8>), NixWrite(nix::Error, i32, Vec<u8>),
#[error("generic io error: {0}")] #[error("generic io error: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("generic serde_json error: {0}")] #[error("generic serde_json error: {0}")]
SerdeJson(#[from] serde_json::Error), SerdeJson(#[from] serde_json::Error),
} }

View file

@ -19,22 +19,22 @@ use crate::writer::FileWriter;
#[derive(StructOpt)] #[derive(StructOpt)]
enum Command { enum Command {
/// Record terminal session. /// Record terminal session.
#[structopt(name = "rec")] #[structopt(name = "rec")]
Record, Record,
} }
fn record() -> Result<()> { fn record() -> Result<()> {
let file = File::create("output.cast")?; let file = File::create("output.cast")?;
pty::record(&["sh", "-c", "/usr/bin/zsh"], FileWriter::new(file))?; pty::record(&["sh", "-c", "/usr/bin/zsh"], FileWriter::new(file))?;
Ok(()) Ok(())
} }
fn main() -> Result<()> { fn main() -> Result<()> {
match Command::from_args() { match Command::from_args() {
Command::Record => { Command::Record => {
record()?; record()?;
}
} }
Ok(()) }
Ok(())
} }

View file

@ -5,15 +5,15 @@ use std::os::unix::io::RawFd;
use std::time::Instant; use std::time::Instant;
use nix::{ use nix::{
fcntl::{fcntl, FcntlArg, OFlag}, fcntl::{fcntl, FcntlArg, OFlag},
ioctl_write_buf, ioctl_write_buf,
pty::{forkpty, Winsize}, pty::{forkpty, Winsize},
sys::{ sys::{
select::{select, FdSet}, select::{select, FdSet},
termios::{tcsetattr, SetArg, Termios}, termios::{tcsetattr, SetArg, Termios},
wait::waitpid, wait::waitpid,
}, },
unistd::{execvpe, isatty, pipe, read, write, ForkResult}, unistd::{execvpe, isatty, pipe, read, write, ForkResult},
}; };
use signal_hook::SigId; use signal_hook::SigId;
@ -24,181 +24,180 @@ const STDIN_FILENO: RawFd = 0;
const STDOUT_FILENO: RawFd = 1; const STDOUT_FILENO: RawFd = 1;
pub fn record(args: &[&str], writer: impl Writer + Send) -> Result<()> { pub fn record(args: &[&str], writer: impl Writer + Send) -> Result<()> {
let forkpty_result = forkpty(None, None)?; let forkpty_result = forkpty(None, None)?;
let master_fd = forkpty_result.master; let master_fd = forkpty_result.master;
let start_time = Instant::now(); let start_time = Instant::now();
let set_pty_size = || -> Result<()> { let _set_pty_size = || -> Result<()> {
ioctl_write_buf!(helper_write, libc::TIOCGWINSZ, 104, Winsize); ioctl_write_buf!(helper_write, libc::TIOCGWINSZ, 104, Winsize);
let winsize = Winsize { let winsize = Winsize {
ws_row: 24, ws_row: 24,
ws_col: 80, ws_col: 80,
ws_xpixel: 0, ws_xpixel: 0,
ws_ypixel: 0, ws_ypixel: 0,
};
if isatty(STDOUT_FILENO)? {}
unsafe { helper_write(master_fd, &[winsize]) }?;
Ok(())
}; };
if isatty(STDOUT_FILENO)? {}
let write_stdout = |data: &[u8]| -> Result<()> { unsafe { helper_write(master_fd, &[winsize]) }?;
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(()) 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<I, F>(signals_list: I) -> Result<Vec<SigId>, io::Error> fn set_signals<I, F>(signals_list: I) -> Result<Vec<SigId>, io::Error>
where where
I: Iterator<Item = (i32, F)>, I: Iterator<Item = (i32, F)>,
F: Fn() + Sync + Send + 'static, F: Fn() + Sync + Send + 'static,
{ {
let mut old_handlers = Vec::new(); let mut old_handlers = Vec::new();
for (sig, handler) in signals_list { for (sig, handler) in signals_list {
old_handlers.push(unsafe { signal_hook::register(sig, handler) }?); old_handlers.push(unsafe { signal_hook::register(sig, handler) }?);
} }
Ok(old_handlers) Ok(old_handlers)
} }
fn unset_signals(handlers_list: Vec<SigId>) -> Result<(), io::Error> { fn unset_signals(handlers_list: Vec<SigId>) -> Result<(), io::Error> {
for handler in handlers_list { for handler in handlers_list {
signal_hook::unregister(handler); signal_hook::unregister(handler);
} }
Ok(()) Ok(())
} }
struct RawTerm(RawFd, Termios); struct RawTerm(RawFd, Termios);
impl RawTerm { impl RawTerm {
pub fn init(fd: RawFd) -> Result<Self> { pub fn init(fd: RawFd) -> Result<Self> {
use nix::sys::termios::*; use nix::sys::termios::*;
let saved_mode = tcgetattr(fd)?; let saved_mode = tcgetattr(fd)?;
let mut mode = saved_mode.clone(); let mut mode = saved_mode.clone();
mode.input_flags &= !(InputFlags::BRKINT mode.input_flags &= !(InputFlags::BRKINT
| InputFlags::ICRNL | InputFlags::ICRNL
| InputFlags::INPCK | InputFlags::INPCK
| InputFlags::ISTRIP | InputFlags::ISTRIP
| InputFlags::IXON); | InputFlags::IXON);
mode.output_flags &= !OutputFlags::OPOST; mode.output_flags &= !OutputFlags::OPOST;
mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB); mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB);
mode.control_flags |= ControlFlags::CS8; mode.control_flags |= ControlFlags::CS8;
mode.local_flags &= !(LocalFlags::ECHO mode.local_flags &= !(LocalFlags::ECHO
| LocalFlags::ICANON | LocalFlags::ICANON
| LocalFlags::IEXTEN | LocalFlags::IEXTEN
| LocalFlags::ISIG); | LocalFlags::ISIG);
mode.control_chars[libc::VMIN] = 1; mode.control_chars[libc::VMIN] = 1;
mode.control_chars[libc::VTIME] = 0; mode.control_chars[libc::VTIME] = 0;
tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?; tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?;
Ok(RawTerm(fd, saved_mode)) Ok(RawTerm(fd, saved_mode))
} }
} }
impl Drop for RawTerm { impl Drop for RawTerm {
fn drop(&mut self) { fn drop(&mut self) {
tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap(); tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap();
} }
} }

View file

@ -1,34 +1,30 @@
use std::collections::HashMap;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use crate::asciicast::Header;
use crate::term;
pub struct RecordOptions { pub struct RecordOptions {
pub path: PathBuf, pub path: PathBuf,
pub append: Option<bool>, pub append: Option<bool>,
pub command: Option<String>, pub command: Option<String>,
pub capture_env: Option<Vec<String>>, pub capture_env: Option<Vec<String>>,
pub title: Option<String>, pub title: Option<String>,
} }
pub fn record(options: RecordOptions) { pub fn record(options: RecordOptions) {
let command = options let _command = options
.command .command
.unwrap_or_else(|| env::var("SHELL").unwrap_or_else(|_| "sh".to_string())); .unwrap_or_else(|| env::var("SHELL").unwrap_or_else(|_| "sh".to_string()));
let command_env = env::vars(); let _command_env = env::vars();
// let header_env = HashMap::new(); // let header_env = HashMap::new();
// let (width, height) = term::get_size(); // let (width, height) = term::get_size();
// let header = Header { // let header = Header {
// version: 2, // version: 2,
// width, // width,
// height, // height,
// title: options.title, // title: options.title,
// }; // };
} }

View file

@ -8,38 +8,38 @@ use crate::asciicast::{Event, EventKind};
use crate::errors::Result; use crate::errors::Result;
pub trait Writer: Clone { pub trait Writer: Clone {
fn write_line(&mut self, line: String) -> Result<(), io::Error>; fn write_line(&mut self, line: String) -> Result<(), io::Error>;
fn write_stdin(&mut self, time: f64, data: &[u8]) -> Result<()> { fn write_stdin(&mut self, time: f64, data: &[u8]) -> Result<()> {
let output = EventKind::Input(data); let output = EventKind::Input(data);
let event = Event(time, output); let event = Event(time, output);
self.write_line(serde_json::to_string(&event)?)?; self.write_line(serde_json::to_string(&event)?)?;
Ok(()) Ok(())
} }
fn write_stdout(&mut self, time: f64, data: &[u8]) -> Result<()> { fn write_stdout(&mut self, time: f64, data: &[u8]) -> Result<()> {
let output = EventKind::Output(data); let output = EventKind::Output(data);
let event = Event(time, output); let event = Event(time, output);
self.write_line(serde_json::to_string(&event)?)?; self.write_line(serde_json::to_string(&event)?)?;
Ok(()) Ok(())
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct FileWriter(Arc<Mutex<File>>); pub struct FileWriter(Arc<Mutex<File>>);
impl FileWriter { impl FileWriter {
pub fn new(file: File) -> Self { pub fn new(file: File) -> Self {
FileWriter(Arc::new(Mutex::new(file))) FileWriter(Arc::new(Mutex::new(file)))
} }
} }
impl Writer for FileWriter { impl Writer for FileWriter {
fn write_line(&mut self, line: String) -> Result<(), io::Error> { fn write_line(&mut self, line: String) -> Result<(), io::Error> {
let mut writer = self.0.lock(); let mut writer = self.0.lock();
writer.write_all(line.as_bytes())?; writer.write_all(line.as_bytes())?;
writer.write(b"\n")?; writer.write(b"\n")?;
writer.flush()?; writer.flush()?;
Ok(()) Ok(())
} }
} }