From a40a493d39ffd7d1b092845758ed60cb03af1b06 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sun, 23 Jun 2024 08:54:41 -0700 Subject: [PATCH] Initial implementation of clipboard forwarding --- Cargo.lock | 582 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- src/client/mod.rs | 89 +++++-- src/lib.rs | 1 + src/main.rs | 26 ++- src/message.rs | 71 +++++- src/reverse.rs | 47 ++++ 7 files changed, 776 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41cec3d..44eb670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "bumpalo" version = "3.14.0" @@ -89,6 +101,32 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.5.0", + "log", + "polling", + "rustix 0.38.34", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix 0.38.34", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -122,6 +160,39 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "copypasta" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -137,13 +208,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "futures-core", "libc", @@ -163,14 +240,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "errno" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -197,17 +295,19 @@ checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "fwd" -version = "0.8.1" +version = "0.9.0" dependencies = [ "anyhow", "assert_matches", "bytes", + "copypasta", "crossterm", "home", "indoc", "log", "open", "procfs", + "rand 0.8.5", "tempdir", "thiserror", "tokio", @@ -218,6 +318,27 @@ dependencies = [ "xdg", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -230,6 +351,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -280,7 +407,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", "windows-sys 0.48.0", ] @@ -294,6 +421,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -302,9 +435,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] [[package]] name = "linux-raw-sys" @@ -312,6 +455,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.11" @@ -328,12 +477,30 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -370,10 +537,39 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.32.1" @@ -434,6 +630,33 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -449,13 +672,22 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "chrono", "flate2", "hex", "lazy_static", - "rustix", + "rustix 0.36.17", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", ] [[package]] @@ -480,6 +712,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -495,6 +748,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -510,7 +772,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -534,14 +796,33 @@ version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", "windows-sys 0.45.0", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -598,12 +879,57 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.5.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.34", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "socket2" version = "0.5.5" @@ -631,7 +957,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand", + "rand 0.4.6", "remove_dir_all", ] @@ -704,13 +1030,29 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + [[package]] name = "tui" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cassowary", "crossterm", "unicode-segmentation", @@ -805,6 +1147,102 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wayland-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34e9e6b6d4a2bb4e7e69433e0b35c7923b95d4dc8503a84d25ec917a4bbfdf07" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.34", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e63801c85358a431f986cffa74ba9599ff571fc5774ac113ed3b490c19a1133" +dependencies = [ + "bitflags 2.5.0", + "rustix 0.38.34", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a206e8b2b53b1d3fcb9428fec72bc278ce539e2fa81fe2bfc1ab27703d5187b9" +dependencies = [ + "rustix 0.38.34", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67da50b9f80159dec0ea4c11c13e24ef9e7574bd6ce24b01860a175010cea565" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105b1842da6554f91526c14a2a2172897b7f745a805d62af4ce698706be79c12" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "winapi" version = "0.3.9" @@ -869,6 +1307,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -899,6 +1346,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -911,6 +1374,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -923,6 +1392,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -935,6 +1410,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -947,6 +1434,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -959,6 +1452,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -971,6 +1470,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -983,8 +1488,53 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "x11-clipboard" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix 0.38.34", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" diff --git a/Cargo.toml b/Cargo.toml index 6b516f2..1193872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fwd" -version = "0.9" +version = "0.9.0" edition = "2021" license = "MIT" description = "Automatically forward ports to a remote server over ssh" @@ -16,11 +16,13 @@ bench = false [dependencies] anyhow = "1.0" bytes = "1" +copypasta = "0.10.1" crossterm = { version = "0.25", features = ["event-stream"] } home = "0.5.4" indoc = "1" log = { version = "0.4", features = ["std"] } open = "3" +rand = "0.8.5" thiserror = "1.0" tokio = { version = "1", features = ["io-std", "io-util", "macros", "net", "process", "rt", "rt-multi-thread", "fs"] } tokio-stream = "0.1" diff --git a/src/client/mod.rs b/src/client/mod.rs index 567a186..e13b779 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,8 +1,10 @@ use crate::message::{Message, MessageReader, MessageWriter}; use anyhow::{bail, Result}; use bytes::BytesMut; +use copypasta::{ClipboardContext, ClipboardProvider}; use log::LevelFilter; use log::{debug, error, info, warn}; +use std::collections::HashMap; use std::net::{Ipv4Addr, SocketAddrV4}; use tokio::io::{ AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, @@ -15,6 +17,14 @@ use tokio::sync::mpsc; mod config; mod ui; +// 256MB clipboard. Operating systems do not generally have a maximum size +// but I need to buffer it all in memory in order to use the `copypasta` +// crate and so I want to set a limit somewhere and this seems.... rational. +// You should use another transfer mechanism other than this if you need more +// than 256 MB of data. (Obviously future me will come along and shake his +// head at the limitations of my foresight, but oh well.) +const MAX_CLIPBOARD_SIZE: usize = 256 * 1024 * 1024; + /// Wait for the server to be ready; we know the server is there and /// listening when we see the special sync marker, which is 8 NUL bytes in a /// row. @@ -162,25 +172,22 @@ async fn client_handle_connection( /// Listen on a port that we are currently forwarding, and use the SOCKS5 /// proxy on the specified port to handle the connections. async fn client_listen(port: u16, socks_port: u16) -> Result<()> { + let listener = + TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)).await?; loop { - let listener = - TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)) - .await?; - loop { - // The second item contains the IP and port of the new - // connection, but we don't care. - let (socket, _) = listener.accept().await?; - - tokio::spawn(async move { - if let Err(e) = - client_handle_connection(socks_port, port, socket).await - { - error!("Error handling connection: {:?}", e); - } else { - debug!("Done???"); - } - }); - } + // The second item contains the IP and port of the new + // connection, but we don't care. + let (socket, _) = listener.accept().await?; + + tokio::spawn(async move { + if let Err(e) = + client_handle_connection(socks_port, port, socket).await + { + error!("Error handling connection: {:?}", e); + } else { + debug!("Done???"); + } + }); } } @@ -188,20 +195,60 @@ async fn client_handle_messages( mut reader: MessageReader, events: mpsc::Sender, ) -> Result<()> { + let mut clipboard_messages = HashMap::new(); loop { use Message::*; match reader.read().await? { Ping => (), + Ports(ports) => { - if let Err(_) = events.send(ui::UIEvent::Ports(ports)).await { - // TODO: Log + if let Err(e) = events.send(ui::UIEvent::Ports(ports)).await { + error!("Error sending ports request: {:?}", e); } } + Browse(url) => { - // TODO: Uh, security? info!("Browsing to {url}..."); _ = open::that(url); } + + ClipStart(id) => { + info!("Starting clip op {id}"); + clipboard_messages.insert(id, Vec::new()); + } + + ClipData(id, mut data) => match clipboard_messages.get_mut(&id) { + Some(bytes) => { + info!( + "Received data for clip op {id} ({len} bytes)", + len = data.len() + ); + if bytes.len() < MAX_CLIPBOARD_SIZE { + bytes.append(&mut data); + } + } + None => { + warn!("Received data for unknown clip op {id}"); + } + }, + + ClipEnd(id) => { + let Some(data) = clipboard_messages.remove(&id) else { + warn!("Received end for unknown clip op {id}"); + continue; + }; + + let Ok(data) = String::from_utf8(data) else { + warn!("Received invalid utf8 for clipboard on op {id}"); + continue; + }; + + let mut ctx = ClipboardContext::new().unwrap(); + if let Err(e) = ctx.set_contents(data) { + error!("Unable to set clipboard data for op {id}: {e:?}"); + } + } + message => error!("Unsupported: {:?}", message), }; } diff --git a/src/lib.rs b/src/lib.rs index 6143ba3..a1e99a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,5 @@ mod server; pub use client::run_client; pub use reverse::browse_url; +pub use reverse::clip_file; pub use server::run_server; diff --git a/src/main.rs b/src/main.rs index cf033ba..3e9ab16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,10 @@ connect to. On a server that already has a client connected to it you can use `fwd browse ` to open `` in the default browser of the client. +On a server that already has a client connected to it you can use `fwd clip` +to read stdin and send it to the clipboard of the client, or `fwd clip ` +to send the the contents of `file`. + Options: --version Print the version of fwd and exit --sudo, -s Run the server side of fwd with `sudo`. This allows the @@ -32,6 +36,7 @@ enum Args { Server, Client(String, bool), Browse(String), + Clip(Option), Error, } @@ -60,13 +65,21 @@ fn parse_args(args: Vec) -> Args { } else { Args::Error } - } else if rest.len() > 1 { + } else if rest.len() >= 1 { if rest[0] == "browse" { if rest.len() == 2 { Args::Browse(rest[1].to_string()) } else { Args::Error } + } else if rest[0] == "clip" { + if rest.len() == 1 { + Args::Clip(None) + } else if rest.len() == 2 { + Args::Clip(Some(rest[1].to_string())) + } else { + Args::Error + } } else { Args::Error } @@ -85,6 +98,14 @@ async fn browse_url(url: &str) { } } +async fn clip_file(file: Option) { + if let Err(e) = fwd::clip_file(file.as_deref()).await { + eprintln!("Unable to copy to the clipboard"); + eprintln!("{}", e); + std::process::exit(1); + } +} + #[tokio::main] async fn main() { match parse_args(std::env::args().collect()) { @@ -100,6 +121,9 @@ async fn main() { Args::Browse(url) => { browse_url(&url).await; } + Args::Clip(file) => { + clip_file(file).await; + } Args::Client(server, sudo) => { fwd::run_client(&server, sudo).await; } diff --git a/src/message.rs b/src/message.rs index eb5efd8..6b77615 100644 --- a/src/message.rs +++ b/src/message.rs @@ -57,6 +57,11 @@ pub enum Message { // Browse a thing Browse(String), + + // Send data to the remote clipboard + ClipStart(u64), + ClipData(u64, Vec), + ClipEnd(u64), } impl Message { @@ -103,6 +108,19 @@ impl Message { result.put_u8(0x07); put_string(result, url); } + ClipStart(id) => { + result.put_u8(0x08); + result.put_u64(*id); + } + ClipData(id, data) => { + result.put_u8(0x09); + result.put_u64(*id); + put_data(result, data); + } + ClipEnd(id) => { + result.put_u8(0x0A); + result.put_u64(*id); + } }; } @@ -132,7 +150,20 @@ impl Message { Ok(Ports(ports)) } 0x07 => Ok(Browse(get_string(cursor)?)), - b => Err(Error::Unknown(b).into()), + 0x08 => { + let id = get_u64(cursor)?; + Ok(ClipStart(id)) + } + 0x09 => { + let id = get_u64(cursor)?; + let data = get_data(cursor)?; + Ok(Self::ClipData(id, data)) + } + 0x0A => { + let id = get_u64(cursor)?; + Ok(ClipEnd(id)) + } + b => Err(Error::Unknown(b)), } } } @@ -151,6 +182,13 @@ fn get_u16(cursor: &mut Cursor<&[u8]>) -> Result { Ok(cursor.get_u16()) } +fn get_u64(cursor: &mut Cursor<&[u8]>) -> Result { + if cursor.remaining() < 8 { + return Err(Error::Incomplete); + } + Ok(cursor.get_u64()) +} + fn get_bytes(cursor: &mut Cursor<&[u8]>, length: usize) -> Result { if cursor.remaining() < length { return Err(Error::Incomplete); @@ -182,6 +220,22 @@ fn put_string(target: &mut T, str: &str) { target.put_slice(str.as_bytes()); } +fn put_data(target: &mut T, data: &[u8]) { + target.put_u16(data.len().try_into().expect("Buffer is too long")); + target.put_slice(data); +} + +fn get_data(cursor: &mut Cursor<&[u8]>) -> Result> { + let length = get_u16(cursor)?; + if cursor.remaining() < length.into() { + return Err(Error::Incomplete); + } + + let mut data: Vec = vec![0; length.into()]; + cursor.copy_to_slice(&mut data); + Ok(data) +} + // ---------------------------------------------------------------------------- // Message IO @@ -193,14 +247,14 @@ impl MessageWriter { pub fn new(writer: T) -> MessageWriter { MessageWriter { writer } } - pub async fn write(self: &mut Self, msg: Message) -> Result<()> { + pub async fn write(&mut self, msg: Message) -> Result<()> { // TODO: Optimize buffer usage please this is bad // eprintln!("? {:?}", msg); - let mut buffer = msg.encode(); + let buffer = msg.encode(); self.writer .write_u32(buffer.len().try_into().expect("Message too large")) .await?; - self.writer.write_all(&mut buffer).await?; + self.writer.write_all(&buffer).await?; self.writer.flush().await?; Ok(()) } @@ -214,7 +268,8 @@ impl MessageReader { pub fn new(reader: T) -> MessageReader { MessageReader { reader } } - pub async fn read(self: &mut Self) -> Result { + + pub async fn read(&mut self) -> Result { let frame_length = self.reader.read_u32().await?; let mut data = vec![0; frame_length.try_into().unwrap()]; self.reader.read_exact(&mut data).await?; @@ -283,6 +338,12 @@ mod message_tests { }, ])); assert_round_trip(Browse("https://google.com/".to_string())); + assert_round_trip(ClipStart(0x1234567890ABCDEF)); + assert_round_trip(ClipData( + 0x1234567890ABCDEF, + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + )); + assert_round_trip(ClipEnd(0x1234567890ABCDEF)); } #[test] diff --git a/src/reverse.rs b/src/reverse.rs index bca8c1c..8e0b8ed 100644 --- a/src/reverse.rs +++ b/src/reverse.rs @@ -1,4 +1,6 @@ use anyhow::Result; +use rand::random; +use tokio::io::{AsyncRead, AsyncReadExt}; #[cfg(target_family = "unix")] mod unix; @@ -27,3 +29,48 @@ pub async fn handle_reverse_connections( pub async fn browse_url(url: &str) -> Result<()> { send_reverse_message(Message::Browse(url.to_string())).await } + +async fn clip_reader(reader: &mut T) -> Result<()> { + let clip_id: u64 = random(); + send_reverse_message(Message::ClipStart(clip_id)).await?; + + let mut count = 0; + let mut buf = vec![0; 1024]; + loop { + let read = reader.read(&mut buf[count..]).await?; + if read == 0 { + break; + } + count += read; + if count == buf.len() { + send_reverse_message(Message::ClipData(clip_id, buf)).await?; + buf = vec![0; 1024]; + count = 0; + } + } + + if count > 0 { + buf.resize(count, 0); + send_reverse_message(Message::ClipData(clip_id, buf)).await?; + } + + send_reverse_message(Message::ClipEnd(clip_id)).await?; + Ok(()) +} + +#[inline] +pub async fn clip_file(file: Option<&str>) -> Result<()> { + // send_reverse_message(Message::Browse(url.to_string())).await + match file { + Some(path) => { + let mut file = tokio::fs::File::open(path).await?; + clip_reader(&mut file).await?; + } + None => { + let mut stdin = tokio::io::stdin(); + clip_reader(&mut stdin).await?; + } + } + + Ok(()) +}