diff --git a/Cargo.lock b/Cargo.lock index b9ad7ea..31e39ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" dependencies = [ "arrayvec", ] @@ -251,9 +251,9 @@ checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ "jobserver", "libc", @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive", @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -557,20 +557,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - [[package]] name = "either" -version = "1.14.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" @@ -616,20 +607,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "fast_image_resize" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55264ccc579fc127eebf6c6c1841d0c160d79a44c8f6f97047b7bc4a9c0d1a5" -dependencies = [ - "bytemuck", - "cfg-if", - "document-features", - "image", - "num-traits", - "thiserror 1.0.69", -] - [[package]] name = "fdeflate" version = "0.3.7" @@ -641,9 +618,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -795,19 +772,7 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "wasi", ] [[package]] @@ -987,9 +952,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libdbus-sys" @@ -1048,12 +1013,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - [[package]] name = "lock_api" version = "0.4.12" @@ -1066,9 +1025,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "loop9" @@ -1112,9 +1071,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", "simd-adler32", @@ -1363,7 +1322,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -1431,19 +1390,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.2", - "zerocopy 0.8.21", + "rand_chacha", + "rand_core", ] [[package]] @@ -1453,17 +1401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.2", + "rand_core", ] [[package]] @@ -1472,17 +1410,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" -dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.21", + "getrandom", ] [[package]] @@ -1511,8 +1439,8 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "simd_helpers", "system-deps", "thiserror 1.0.69", @@ -1557,9 +1485,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.8.0", ] @@ -1632,7 +1560,7 @@ dependencies = [ "dbus", "objc", "pipewire", - "rand 0.8.5", + "rand", "screencapturekit", "screencapturekit-sys", "sysinfo", @@ -1672,18 +1600,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -1715,11 +1643,10 @@ dependencies = [ [[package]] name = "servicepoint-cli" -version = "0.3.0" +version = "0.2.1" dependencies = [ "clap", "env_logger", - "fast_image_resize", "image", "log", "scap", @@ -1929,16 +1856,17 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" dependencies = [ + "byteorder", "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.9.0", + "rand", "sha1", "thiserror 2.0.11", "utf-8", @@ -1946,15 +1874,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-segmentation" @@ -2015,15 +1943,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2282,22 +2201,13 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -2323,16 +2233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" -dependencies = [ - "zerocopy-derive 0.8.21", + "zerocopy-derive", ] [[package]] @@ -2346,17 +2247,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zerocopy-derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index a8cd60f..8076574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "servicepoint-cli" description = "A command line interface for the ServicePoint display." -version = "0.3.0" +version = "0.2.1" edition = "2021" rust-version = "1.80.0" publish = true @@ -19,4 +19,3 @@ env_logger = "0.11" log = "0.4" scap = "0.0.8" image = "0.25.5" -fast_image_resize = { version = "5.1.2", features = ["image"] } diff --git a/README.md b/README.md index be1dab7..439cf6d 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ cargo run -- Usage: servicepoint-cli [OPTIONS] Commands: - reset-everything Reset both pixels and brightness [aliases: r] - pixels Commands for manipulating pixels [aliases: p] - brightness Commands for manipulating the brightness [aliases: b] - text Commands for sending text to the screen [aliases: t] + reset-everything [aliases: r] + pixels [aliases: p] + brightness [aliases: b] + stream [aliases: s] help Print this message or the help of the given subcommand(s) Options: @@ -51,93 +51,62 @@ Options: -V, --version Print version ``` -### Pixels +### Stream ``` -Commands for manipulating pixels - -Usage: servicepoint-cli pixels +Usage: servicepoint-cli stream Commands: - off Reset all pixels to the default (off) state [aliases: r, reset, clear] - flip Invert the state of all pixels [aliases: f] - on Set all pixels to the on state - image Send an image file (e.g. jpeg or png) to the display. [aliases: i] - screen Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. [aliases: s] -``` - -#### Image - -``` -Send an image file (e.g. jpeg or png) to the display. - -Usage: servicepoint-cli pixels image [OPTIONS] - -Arguments: - - -Options: - --no-hist Disable histogram correction - --no-blur Disable blur - --no-sharp Disable sharpening - --no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on. - --no-spacers Do not remove the spacers from the image. - --no-aspect Do not keep aspect ratio when resizing. + stdin Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin` + screen Stream the default source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. + help Print this message or the help of the given subcommand(s) ``` #### Screen ``` -Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. - -Usage: servicepoint-cli pixels screen [OPTIONS] +Usage: servicepoint-cli stream screen [OPTIONS] Options: - -p, --pointer Show mouse pointer in video feed - --no-hist Disable histogram correction - --no-blur Disable blur - --no-sharp Disable sharpening - --no-dither Disable dithering. Brightness will be adjusted so that around half of the pixels are on. - --no-spacers Do not remove the spacers from the image. - --no-aspect Do not keep aspect ratio when resizing. + -n, --no-dither Disable dithering + -p, --pointer Show mouse pointer in video feed + -h, --help Print help +``` + +#### Stdin + +``` +Usage: servicepoint-cli stream stdin [OPTIONS] + +Options: + -s, --slow + -h, --help Print help ``` ### Brightness ``` -Commands for manipulating the brightness - Usage: servicepoint-cli brightness Commands: max Reset brightness to the default (max) level [aliases: r, reset] set Set one brightness for the whole screen [aliases: s] min Set brightness to lowest possible level. + help Print this message or the help of the given subcommand(s) ``` -### Text +### Pixels ``` -Commands for sending text to the screen - -Usage: servicepoint-cli text +Usage: servicepoint-cli pixels Commands: - stdin Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin` + off Reset all pixels to the default (off) state [aliases: r, reset] + invert Invert the state of all pixels [aliases: i] + on Set all pixels to the on state + help Print this message or the help of the given subcommand(s) ``` -#### Stdin - -``` -Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin` - -Usage: servicepoint-cli stream stdin [OPTIONS] - -Options: - -s, --slow Wait for a short amount of time before sending the next line -``` - - ## Contributing If you have ideas on how to improve the code, add features or improve documentation feel free to open a pull request. diff --git a/flake.lock b/flake.lock index e99b4ca..abacd0c 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1739824009, - "narHash": "sha256-fcNrCMUWVLMG3gKC5M9CBqVOAnJtyRvGPxptQFl5mVg=", + "lastModified": 1736429655, + "narHash": "sha256-BwMekRuVlSB9C0QgwKMICiJ5EVbLGjfe4qyueyNQyGI=", "owner": "nix-community", "repo": "naersk", - "rev": "e5130d37369bfa600144c2424270c96f0ef0e11d", + "rev": "0621e47bd95542b8e1ce2ee2d65d6a1f887a13ce", "type": "github" }, "original": { @@ -37,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740603184, - "narHash": "sha256-t+VaahjQAWyA+Ctn2idyo1yxRIYpaDxMgHkgCNiMJa4=", + "lastModified": 1736549401, + "narHash": "sha256-ibkQrMHxF/7TqAYcQE+tOnIsSEzXmMegzyBWza6uHKM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f44bd8ca21e026135061a0a57dcf3d0775b67a49", + "rev": "1dab772dd4a68a7bba5d9460685547ff8e17d899", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 405734a..8bf4958 100644 --- a/flake.nix +++ b/flake.nix @@ -103,7 +103,6 @@ cargo-expand ]; }) - pkgs.cargo-flamegraph ]; LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}"; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; diff --git a/src/brightness.rs b/src/brightness.rs deleted file mode 100644 index adead44..0000000 --- a/src/brightness.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::cli::BrightnessCommand; -use log::info; -use servicepoint::{Brightness, Command, Connection}; - -pub(crate) fn brightness(connection: &Connection, brightness_command: BrightnessCommand) { - match brightness_command { - BrightnessCommand::Max => brightness_set(connection, Brightness::MAX), - BrightnessCommand::Min => brightness_set(connection, Brightness::MIN), - BrightnessCommand::Set { brightness } => { - brightness_set(connection, Brightness::saturating_from(brightness)) - } - } -} - -pub(crate) fn brightness_set(connection: &Connection, brightness: Brightness) { - connection - .send(Command::Brightness(brightness)) - .expect("Failed to set brightness"); - info!("set brightness to {brightness:?}"); -} diff --git a/src/cli.rs b/src/cli.rs index 365ff5a..fa420d6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -40,10 +40,10 @@ pub enum Mode { #[clap(subcommand)] brightness_command: BrightnessCommand, }, - #[command(visible_alias = "t")] - Text { + #[command(visible_alias = "s")] + Stream { #[clap(subcommand)] - text_command: TextCommand, + stream_command: StreamCommand, }, } @@ -53,36 +53,13 @@ pub enum PixelCommand { #[command( visible_alias = "r", visible_alias = "reset", - visible_alias = "clear", about = "Reset all pixels to the default (off) state" )] Off, - #[command(visible_alias = "f", about = "Invert the state of all pixels")] - Flip, + #[command(visible_alias = "i", about = "Invert the state of all pixels")] + Invert, #[command(about = "Set all pixels to the on state")] On, - #[command( - visible_alias = "i", - about = "Send an image file (e.g. jpeg or png) to the display." - )] - Image { - #[command(flatten)] - send_image_options: SendImageOptions, - #[command(flatten)] - image_processing_options: ImageProcessingOptions, - }, - #[command( - visible_alias = "s", - about = "Stream the default screen capture source to the display. \ - On Linux Wayland, this pops up a screen or window chooser, \ - but it also may directly start streaming your main screen." - )] - Screen { - #[command(flatten)] - stream_options: StreamScreenOptions, - #[command(flatten)] - image_processing: ImageProcessingOptions, - }, } #[derive(clap::Parser, std::fmt::Debug)] @@ -111,24 +88,28 @@ pub enum Protocol { } #[derive(clap::Parser, std::fmt::Debug)] -#[clap(about = "Commands for sending text to the screen")] -pub enum TextCommand { - #[command( +#[clap(about = "Continuously send data to the display")] +pub enum StreamCommand { + #[clap( about = "Pipe text to the display, example: `journalctl | servicepoint-cli stream stdin`" )] Stdin { - #[arg( - long, - short, - default_value_t = false, - help = "Wait for a short amount of time before sending the next line" - )] + #[arg(long, short, default_value_t = false)] slow: bool, }, + #[clap(about = "Stream the default source to the display. \ + On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen.")] + Screen { + #[command(flatten)] + options: StreamScreenOptions, + }, } #[derive(clap::Parser, std::fmt::Debug, Clone)] pub struct StreamScreenOptions { + #[arg(long, short, default_value_t = false, help = "Disable dithering")] + pub no_dither: bool, + #[arg( long, short, @@ -137,33 +118,3 @@ pub struct StreamScreenOptions { )] pub pointer: bool, } - -#[derive(clap::Parser, std::fmt::Debug, Clone)] -pub struct ImageProcessingOptions { - #[arg(long, help = "Disable histogram correction")] - pub no_hist: bool, - - #[arg(long, help = "Disable blur")] - pub no_blur: bool, - - #[arg(long, help = "Disable sharpening")] - pub no_sharp: bool, - - #[arg( - long, - help = "Disable dithering. Brightness will be adjusted so that around half of the pixels are on." - )] - pub no_dither: bool, - - #[arg(long, help = "Do not remove the spacers from the image.")] - pub no_spacers: bool, - - #[arg(long, help = "Do not keep aspect ratio when resizing.")] - pub no_aspect: bool, -} - -#[derive(clap::Parser, std::fmt::Debug, Clone)] -pub struct SendImageOptions { - #[arg()] - pub file_name: String, -} diff --git a/src/execute.rs b/src/execute.rs new file mode 100644 index 0000000..ad91eba --- /dev/null +++ b/src/execute.rs @@ -0,0 +1,73 @@ +use crate::cli::{BrightnessCommand, Mode, PixelCommand, StreamCommand}; +use crate::stream_stdin::stream_stdin; +use crate::stream_window::stream_window; +use log::info; +use servicepoint::{BitVec, Brightness, Command, CompressionCode, Connection, PIXEL_COUNT}; + +pub fn execute_mode(mode: Mode, connection: Connection) { + match mode { + Mode::ResetEverything => { + brightness_reset(&connection); + pixels_reset(&connection); + } + Mode::Pixels { pixel_command } => pixels(&connection, pixel_command), + Mode::Brightness { brightness_command } => brightness(&connection, brightness_command), + Mode::Stream { stream_command } => match stream_command { + StreamCommand::Stdin { slow } => stream_stdin(connection, slow), + StreamCommand::Screen { options } => stream_window(&connection, options), + }, + } +} + +fn pixels(connection: &Connection, pixel_command: PixelCommand) { + match pixel_command { + PixelCommand::Off => pixels_reset(connection), + PixelCommand::Invert => pixels_invert(connection), + PixelCommand::On => pixels_on(connection) + } +} + +fn pixels_on(connection: &Connection) { + let mask = BitVec::repeat(true, PIXEL_COUNT); + connection + .send(Command::BitmapLinearXor(0, mask, CompressionCode::Lzma)) + .expect("could not send command") +} + +fn pixels_invert(connection: &Connection) { + let mask = BitVec::repeat(true, PIXEL_COUNT); + connection + .send(Command::BitmapLinearXor(0, mask, CompressionCode::Lzma)) + .expect("could not send command") +} + +fn brightness(connection: &Connection, brightness_command: BrightnessCommand) { + match brightness_command { + BrightnessCommand::Max => brightness_reset(connection), + BrightnessCommand::Min => brightness_set(connection, Brightness::MIN), + BrightnessCommand::Set { brightness } => { + brightness_set(connection, Brightness::saturating_from(brightness)) + } + } +} + +fn pixels_reset(connection: &Connection) { + connection + .send(Command::Clear) + .expect("failed to clear pixels"); + info!("Reset pixels"); +} + +fn brightness_reset(connection: &Connection) { + connection + .send(Command::Brightness(Brightness::MAX)) + .expect("Failed to reset brightness to maximum"); + info!("Reset brightness"); +} + +fn brightness_set(connection: &Connection, brightness: Brightness) { + connection + .send(Command::Brightness(brightness)) + .expect("Failed to set brightness"); + info!("set brightness to {brightness:?}"); +} diff --git a/src/image_processing.rs b/src/image_processing.rs deleted file mode 100644 index 4e6baa8..0000000 --- a/src/image_processing.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::{ - cli::ImageProcessingOptions, - ledwand_dither::{blur, histogram_correction, median_brightness, ostromoukhov_dither, sharpen}, -}; -use fast_image_resize::{ResizeOptions, Resizer}; -use image::{DynamicImage, GrayImage}; -use log::{debug, trace}; -use servicepoint::{Bitmap, Grid, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_HEIGHT, TILE_SIZE}; -use std::{default::Default, time::Instant}; - -#[derive(Debug)] -pub struct ImageProcessingPipeline { - options: ImageProcessingOptions, - resizer: Resizer, - render_size: (u32, u32), -} - -const SPACER_HEIGHT: usize = TILE_SIZE / 2; - -impl ImageProcessingPipeline { - pub fn new(options: ImageProcessingOptions) -> Self { - debug!("Creating image pipeline: {:?}", options); - - let height = PIXEL_HEIGHT - + if options.no_spacers { - 0 - } else { - SPACER_HEIGHT * (TILE_HEIGHT - 1) - }; - - Self { - options, - resizer: Resizer::new(), - render_size: (PIXEL_WIDTH as u32, height as u32), - } - } - - pub fn process(&mut self, frame: DynamicImage) -> Bitmap { - let start_time = Instant::now(); - - let frame = self.resize_grayscale(frame); - let frame = self.grayscale_processing(frame); - let mut result = self.grayscale_to_bitmap(frame); - - if !self.options.no_spacers { - result = Self::remove_spacers(result); - } - - trace!("pipeline took {:?}", start_time.elapsed()); - result - } - - fn resize_grayscale(&mut self, frame: DynamicImage) -> GrayImage { - let start_time = Instant::now(); - - let (scaled_width, scaled_height) = if self.options.no_aspect { - self.render_size - } else { - self.calc_scaled_size_keep_aspect((frame.width(), frame.height())) - }; - let mut dst_image = DynamicImage::new(scaled_width, scaled_height, frame.color()); - - self.resizer - .resize(&frame, &mut dst_image, &ResizeOptions::default()) - .expect("image resize failed"); - - trace!("resizing took {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let result = dst_image.into_luma8(); - trace!("grayscale took {:?}", start_time.elapsed()); - - result - } - - fn grayscale_processing(&self, mut frame: GrayImage) -> GrayImage { - let start_time = Instant::now(); - if !self.options.no_hist { - histogram_correction(&mut frame); - } - - let mut orig = frame.clone(); - - if !self.options.no_blur { - blur(&orig, &mut frame); - std::mem::swap(&mut frame, &mut orig); - } - - if !self.options.no_sharp { - sharpen(&orig, &mut frame); - std::mem::swap(&mut frame, &mut orig); - } - - trace!("image processing took {:?}", start_time.elapsed()); - orig - } - - fn grayscale_to_bitmap(&self, orig: GrayImage) -> Bitmap { - let start_time = Instant::now(); - let result = if self.options.no_dither { - let cutoff = median_brightness(&orig); - let bits = orig.iter().map(move |x| x > &cutoff).collect(); - Bitmap::from_bitvec(orig.width() as usize, bits) - } else { - ostromoukhov_dither(orig, u8::MAX / 2) - }; - trace!("bitmap conversion took {:?}", start_time.elapsed()); - result - } - - fn remove_spacers(source: Bitmap) -> Bitmap { - let start_time = Instant::now(); - - let width = source.width(); - let result_height = Self::calc_height_without_spacers(source.height()); - let mut result = Bitmap::new(width, result_height); - - let mut source_y = 0; - for result_y in 0..result_height { - for x in 0..width { - result.set(x, result_y, source.get(x, source_y)); - } - - if result_y != 0 && result_y % TILE_SIZE == 0 { - source_y += SPACER_HEIGHT; - } - source_y += 1; - } - - trace!("removing spacers took {:?}", start_time.elapsed()); - result - } - - fn calc_height_without_spacers(height: usize) -> usize { - let full_tile_rows_with_spacers = height / (TILE_SIZE + SPACER_HEIGHT); - let remaining_pixel_rows = height % (TILE_SIZE + SPACER_HEIGHT); - let total_spacer_height = full_tile_rows_with_spacers * SPACER_HEIGHT - + remaining_pixel_rows.saturating_sub(TILE_SIZE); - let height_without_spacers = height - total_spacer_height; - trace!( - "spacers take up {total_spacer_height}, resulting in final height {height_without_spacers}" - ); - height_without_spacers - } - - fn calc_scaled_size_keep_aspect(&self, source: (u32, u32)) -> (u32, u32) { - let (source_width, source_height) = source; - let (target_width, target_height) = self.render_size; - debug_assert_eq!(target_width % TILE_SIZE as u32, 0); - - let width_scale = target_width as f32 / source_width as f32; - let height_scale = target_height as f32 / source_height as f32; - let scale = f32::min(width_scale, height_scale); - - let height = (source_height as f32 * scale) as u32; - let mut width = (source_width as f32 * scale) as u32; - - if width % TILE_SIZE as u32 != 0 { - // because we do not have many pixels, round up even if it is a worse fit - width += 8 - width % 8; - } - - let result = (width, height); - trace!( - "scaling {:?} to {:?} to fit {:?}", - source, - result, - self.render_size - ); - result - } -} diff --git a/src/ledwand_dither.rs b/src/ledwand_dither.rs deleted file mode 100644 index d0e4b43..0000000 --- a/src/ledwand_dither.rs +++ /dev/null @@ -1,507 +0,0 @@ -//! Based on https://github.com/WarkerAnhaltRanger/CCCB_Ledwand - -use image::GrayImage; -use servicepoint::{BitVec, Bitmap, PIXEL_HEIGHT}; - -type GrayHistogram = [usize; 256]; - -struct HistogramCorrection { - pre_offset: f32, - post_offset: f32, - factor: f32, -} - -pub fn histogram_correction(image: &mut GrayImage) { - let histogram = make_histogram(image); - let correction = determine_histogram_correction(image, histogram); - apply_histogram_correction(image, correction) -} - -fn make_histogram(image: &GrayImage) -> GrayHistogram { - let mut histogram = [0; 256]; - for pixel in image.pixels() { - histogram[pixel.0[0] as usize] += 1; - } - histogram -} - -fn determine_histogram_correction( - image: &GrayImage, - histogram: GrayHistogram, -) -> HistogramCorrection { - let adjustment_pixels = image.len() / PIXEL_HEIGHT; - - let mut num_pixels = 0; - let mut brightness = 0; - - let mincut = loop { - num_pixels += histogram[brightness as usize]; - brightness += 1; - if num_pixels >= adjustment_pixels { - break u8::min(brightness, 20); - } - }; - - let minshift = loop { - num_pixels += histogram[brightness as usize]; - brightness += 1; - if num_pixels >= 2 * adjustment_pixels { - break u8::min(brightness, 64); - } - }; - - brightness = u8::MAX; - num_pixels = 0; - let maxshift = loop { - num_pixels += histogram[brightness as usize]; - brightness -= 1; - if num_pixels >= 2 * adjustment_pixels { - break u8::max(brightness, 192); - } - }; - - let pre_offset = -(mincut as f32 / 2.); - let post_offset = -(minshift as f32); - let factor = (255.0 - post_offset) / maxshift as f32; - HistogramCorrection { - pre_offset, - post_offset, - factor, - } -} - -fn apply_histogram_correction(image: &mut GrayImage, correction: HistogramCorrection) { - for pixel in image.pixels_mut() { - let pixel = &mut pixel.0[0]; - let value = - (*pixel as f32 + correction.pre_offset) * correction.factor + correction.post_offset; - *pixel = value.clamp(0f32, u8::MAX as f32) as u8; - } -} - -pub fn median_brightness(image: &GrayImage) -> u8 { - let histogram = make_histogram(image); - let midpoint = image.len() / 2; - - debug_assert_eq!( - image.len(), - histogram.iter().copied().map(usize::from).sum() - ); - - let mut num_pixels = 0; - for brightness in u8::MIN..=u8::MAX { - num_pixels += histogram[brightness as usize]; - if num_pixels >= midpoint { - return brightness; - } - } - - unreachable!("Somehow less pixels where counted in the histogram than exist in the image") -} - -pub fn blur(source: &GrayImage, destination: &mut GrayImage) { - assert_eq!(source.len(), destination.len()); - - copy_border(source, destination); - blur_inner_pixels(source, destination); -} - -pub fn sharpen(source: &GrayImage, destination: &mut GrayImage) { - assert_eq!(source.len(), destination.len()); - - copy_border(source, destination); - sharpen_inner_pixels(source, destination); -} - -fn copy_border(source: &GrayImage, destination: &mut GrayImage) { - let last_row = source.height() - 1; - for x in 0..source.width() { - destination[(x, 0)] = source[(x, 0)]; - destination[(x, last_row)] = source[(x, last_row)]; - } - let last_col = source.width() - 1; - for y in 0..source.height() { - destination[(0, y)] = source[(0, y)]; - destination[(last_col, y)] = source[(last_col, y)]; - } -} - -fn blur_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { - for y in 1..source.height() - 2 { - for x in 1..source.width() - 2 { - let weighted_sum = source.get_pixel(x - 1, y - 1).0[0] as u32 - + source.get_pixel(x, y - 1).0[0] as u32 - + source.get_pixel(x + 1, y - 1).0[0] as u32 - + source.get_pixel(x - 1, y).0[0] as u32 - + 8 * source.get_pixel(x, y).0[0] as u32 - + source.get_pixel(x + 1, y).0[0] as u32 - + source.get_pixel(x - 1, y + 1).0[0] as u32 - + source.get_pixel(x, y + 1).0[0] as u32 - + source.get_pixel(x + 1, y + 1).0[0] as u32; - let blurred = weighted_sum / 16; - destination.get_pixel_mut(x, y).0[0] = - blurred.clamp(u8::MIN as u32, u8::MAX as u32) as u8; - } - } -} - -fn sharpen_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { - for y in 1..source.height() - 2 { - for x in 1..source.width() - 2 { - let weighted_sum = -(source.get_pixel(x - 1, y - 1).0[0] as i32) - - source.get_pixel(x, y - 1).0[0] as i32 - - source.get_pixel(x + 1, y - 1).0[0] as i32 - - source.get_pixel(x - 1, y).0[0] as i32 - + 9 * source.get_pixel(x, y).0[0] as i32 - - source.get_pixel(x + 1, y).0[0] as i32 - - source.get_pixel(x - 1, y + 1).0[0] as i32 - - source.get_pixel(x, y + 1).0[0] as i32 - - source.get_pixel(x + 1, y + 1).0[0] as i32; - destination.get_pixel_mut(x, y).0[0] = - weighted_sum.clamp(u8::MIN as i32, u8::MAX as i32) as u8; - } - } -} - -pub(crate) fn ostromoukhov_dither(source: GrayImage, bias: u8) -> Bitmap { - let width = source.width(); - let height = source.height(); - assert_eq!(width % 8, 0); - - let mut source = source.into_raw(); - let mut destination = BitVec::repeat(false, source.len()); - - for y in 0..height as usize { - let start = y * width as usize; - if y % 2 == 0 { - for x in start..start + width as usize { - ostromoukhov_dither_pixel( - &mut source, - &mut destination, - x, - width as usize, - y == (height - 1) as usize, - 1, - bias, - ); - } - } else { - for x in (start..start + width as usize).rev() { - ostromoukhov_dither_pixel( - &mut source, - &mut destination, - x, - width as usize, - y == (height - 1) as usize, - -1, - bias, - ); - } - } - } - - Bitmap::from_bitvec(width as usize, destination) -} - -#[inline] -fn ostromoukhov_dither_pixel( - source: &mut [u8], - destination: &mut BitVec, - position: usize, - width: usize, - last_row: bool, - direction: isize, - bias: u8, -) { - let (destination_value, error) = gray_to_bit(source[position], bias); - destination.set(position, destination_value); - - let mut diffuse = |to: usize, mat: i16| { - let diffuse_value = source[to] as i16 + mat; - source[to] = diffuse_value.clamp(u8::MIN.into(), u8::MAX.into()) as u8; - }; - - let lookup = if destination_value { - ERROR_DIFFUSION_MATRIX[error as usize].map(move |i| -i) - } else { - ERROR_DIFFUSION_MATRIX[error as usize] - }; - diffuse((position as isize + direction) as usize, lookup[0]); - - if !last_row { - diffuse( - ((position + width) as isize - direction) as usize, - lookup[1], - ); - diffuse(((position + width) as isize) as usize, lookup[2]); - } -} - -fn gray_to_bit(old_pixel: u8, bias: u8) -> (bool, u8) { - let destination_value = old_pixel > bias; - let error = if destination_value { - 255 - old_pixel - } else { - old_pixel - }; - (destination_value, error) -} - -const ERROR_DIFFUSION_MATRIX: [[i16; 3]; 256] = [ - [0, 1, 0], - [1, 0, 0], - [1, 0, 1], - [2, 0, 1], - [2, 0, 2], - [3, 0, 2], - [4, 0, 2], - [4, 1, 2], - [5, 1, 2], - [5, 2, 2], - [5, 3, 2], - [6, 3, 2], - [6, 3, 3], - [7, 3, 3], - [7, 4, 3], - [8, 4, 3], - [8, 5, 3], - [9, 5, 3], - [9, 5, 4], - [10, 6, 3], - [10, 6, 4], - [11, 7, 3], - [11, 7, 4], - [11, 8, 4], - [12, 7, 5], - [12, 7, 6], - [12, 7, 7], - [12, 7, 8], - [12, 7, 9], - [13, 7, 9], - [13, 7, 10], - [13, 7, 11], - [13, 7, 12], - [14, 7, 12], - [14, 8, 12], - [15, 8, 12], - [15, 9, 12], - [16, 9, 12], - [16, 10, 12], - [17, 10, 12], - [17, 11, 12], - [18, 12, 11], - [19, 12, 11], - [19, 13, 11], - [20, 13, 11], - [20, 14, 11], - [21, 15, 10], - [22, 15, 10], - [22, 17, 9], - [23, 17, 9], - [24, 18, 8], - [24, 19, 8], - [25, 19, 8], - [26, 20, 7], - [26, 21, 7], - [27, 22, 6], - [28, 23, 5], - [28, 24, 5], - [29, 25, 4], - [30, 26, 3], - [31, 26, 3], - [31, 28, 2], - [32, 28, 2], - [33, 29, 1], - [34, 30, 0], - [33, 31, 1], - [32, 33, 1], - [32, 33, 2], - [31, 34, 3], - [30, 36, 3], - [29, 37, 4], - [29, 37, 5], - [28, 39, 5], - [32, 34, 7], - [37, 29, 8], - [42, 23, 10], - [46, 19, 11], - [51, 13, 12], - [52, 14, 13], - [53, 13, 12], - [53, 14, 13], - [54, 14, 13], - [55, 14, 13], - [55, 14, 13], - [56, 15, 14], - [57, 14, 13], - [56, 15, 15], - [55, 17, 15], - [54, 18, 16], - [53, 20, 16], - [52, 21, 17], - [52, 22, 17], - [51, 24, 17], - [50, 25, 18], - [49, 27, 18], - [47, 29, 19], - [48, 29, 19], - [48, 29, 20], - [49, 29, 20], - [49, 30, 20], - [50, 31, 20], - [50, 31, 20], - [51, 31, 20], - [51, 31, 21], - [52, 31, 21], - [52, 32, 21], - [53, 32, 21], - [53, 32, 22], - [55, 32, 21], - [56, 31, 22], - [58, 31, 21], - [59, 30, 22], - [61, 30, 21], - [62, 29, 22], - [64, 29, 21], - [65, 28, 22], - [67, 28, 21], - [68, 27, 22], - [70, 27, 21], - [71, 26, 22], - [73, 26, 21], - [75, 25, 21], - [76, 25, 21], - [78, 24, 21], - [80, 23, 21], - [81, 23, 21], - [83, 22, 21], - [85, 21, 20], - [85, 22, 21], - [85, 22, 22], - [84, 24, 22], - [84, 24, 23], - [84, 25, 23], - [83, 27, 23], - [83, 28, 23], - [82, 29, 24], - [82, 30, 24], - [81, 31, 25], - [80, 32, 26], - [80, 33, 26], - [79, 35, 26], - [79, 36, 26], - [78, 37, 27], - [77, 38, 28], - [77, 39, 28], - [76, 41, 28], - [75, 42, 29], - [75, 43, 29], - [74, 44, 30], - [74, 45, 30], - [75, 46, 30], - [75, 46, 30], - [76, 46, 30], - [76, 46, 31], - [77, 46, 31], - [77, 47, 31], - [78, 47, 31], - [78, 47, 32], - [79, 47, 32], - [79, 48, 32], - [80, 49, 32], - [83, 46, 32], - [86, 44, 32], - [90, 42, 31], - [93, 40, 31], - [96, 39, 30], - [100, 36, 30], - [103, 35, 29], - [106, 33, 29], - [110, 30, 29], - [113, 29, 28], - [114, 29, 28], - [115, 29, 28], - [115, 29, 28], - [116, 30, 29], - [117, 29, 28], - [117, 30, 29], - [118, 30, 29], - [119, 30, 29], - [109, 43, 27], - [100, 57, 23], - [90, 71, 20], - [80, 85, 17], - [70, 99, 14], - [74, 98, 12], - [78, 97, 10], - [81, 96, 9], - [85, 95, 7], - [89, 94, 5], - [92, 93, 4], - [96, 92, 2], - [100, 91, 0], - [100, 90, 2], - [100, 88, 5], - [100, 87, 7], - [99, 86, 10], - [99, 85, 12], - [99, 84, 14], - [99, 82, 17], - [98, 81, 20], - [98, 80, 22], - [98, 79, 24], - [98, 77, 27], - [98, 76, 29], - [97, 75, 32], - [97, 73, 35], - [97, 72, 37], - [96, 71, 40], - [96, 69, 43], - [96, 67, 46], - [96, 66, 48], - [95, 65, 51], - [95, 63, 54], - [95, 61, 57], - [94, 60, 60], - [94, 58, 63], - [94, 57, 65], - [93, 55, 69], - [93, 54, 71], - [93, 52, 74], - [92, 51, 77], - [92, 49, 80], - [91, 47, 84], - [91, 46, 86], - [93, 49, 82], - [96, 52, 77], - [98, 55, 73], - [101, 58, 68], - [104, 61, 63], - [106, 65, 58], - [109, 68, 53], - [111, 71, 49], - [114, 74, 44], - [116, 78, 39], - [118, 76, 40], - [119, 74, 42], - [120, 73, 43], - [122, 71, 44], - [123, 69, 46], - [124, 67, 48], - [125, 66, 49], - [127, 64, 50], - [128, 62, 52], - [129, 60, 54], - [131, 58, 55], - [132, 57, 56], - [136, 47, 63], - [139, 38, 70], - [143, 29, 76], - [147, 19, 83], - [151, 9, 90], - [154, 0, 97], - [160, 0, 92], - [171, 0, 82], - [183, 0, 71], - [184, 0, 71], -]; diff --git a/src/main.rs b/src/main.rs index 4ca1df4..a9878ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,12 @@ -use crate::{ - brightness::{brightness, brightness_set}, - cli::{Cli, Mode, Protocol}, - pixels::{pixels, pixels_off}, - text::text -}; +use crate::cli::{Cli, Protocol}; use clap::Parser; use log::debug; -use servicepoint::{Brightness, Connection}; +use servicepoint::Connection; -mod brightness; mod cli; -mod image_processing; -mod ledwand_dither; -mod pixels; +mod execute; mod stream_stdin; mod stream_window; -mod text; fn main() { let cli = Cli::parse(); @@ -25,19 +16,7 @@ fn main() { let connection = make_connection(cli.destination, cli.transport); debug!("connection established: {:#?}", connection); - execute_mode(cli.command, connection); -} - -pub fn execute_mode(mode: Mode, connection: Connection) { - match mode { - Mode::ResetEverything => { - brightness_set(&connection, Brightness::MAX); - pixels_off(&connection); - } - Mode::Pixels { pixel_command } => pixels(&connection, pixel_command), - Mode::Brightness { brightness_command } => brightness(&connection, brightness_command), - Mode::Text { text_command} => text(&connection, text_command), - } + execute::execute_mode(cli.command, connection); } fn make_connection(destination: String, transport: Protocol) -> Connection { diff --git a/src/pixels.rs b/src/pixels.rs deleted file mode 100644 index da1aa6d..0000000 --- a/src/pixels.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{ - image_processing::ImageProcessingPipeline, - cli::{ImageProcessingOptions, PixelCommand, SendImageOptions}, - stream_window::stream_window -}; -use log::info; -use servicepoint::{BitVec, Command, CompressionCode, Connection, Origin, PIXEL_COUNT}; - -pub(crate) fn pixels(connection: &Connection, pixel_command: PixelCommand) { - match pixel_command { - PixelCommand::Off => pixels_off(connection), - PixelCommand::Flip => pixels_invert(connection), - PixelCommand::On => pixels_on(connection), - PixelCommand::Image { - image_processing_options: processing_options, - send_image_options: image_options, - } => pixels_image(connection, image_options, processing_options), - PixelCommand::Screen { - stream_options, - image_processing, - } => stream_window(connection, stream_options, image_processing), - } -} - -fn pixels_on(connection: &Connection) { - let mask = BitVec::repeat(true, PIXEL_COUNT); - connection - .send(Command::BitmapLinear(0, mask, CompressionCode::Lzma)) - .expect("could not send command"); - info!("turned on all pixels") -} - -fn pixels_invert(connection: &Connection) { - let mask = BitVec::repeat(true, PIXEL_COUNT); - connection - .send(Command::BitmapLinearXor(0, mask, CompressionCode::Lzma)) - .expect("could not send command"); - info!("inverted all pixels"); -} - -pub(crate) fn pixels_off(connection: &Connection) { - connection - .send(Command::Clear) - .expect("failed to clear pixels"); - info!("reset pixels"); -} - -fn pixels_image( - connection: &Connection, - options: SendImageOptions, - processing_options: ImageProcessingOptions, -) { - let image = image::open(&options.file_name).expect("failed to open image file"); - let mut pipeline = ImageProcessingPipeline::new(processing_options); - let bitmap = pipeline.process(image); - connection - .send(Command::BitmapLinearWin( - Origin::ZERO, - bitmap, - CompressionCode::default(), - )) - .expect("failed to send image command"); - info!("sent image to display"); -} diff --git a/src/stream_stdin.rs b/src/stream_stdin.rs index b8b6cfb..a349aa2 100644 --- a/src/stream_stdin.rs +++ b/src/stream_stdin.rs @@ -2,7 +2,7 @@ use log::warn; use servicepoint::*; use std::thread::sleep; -pub(crate) fn stream_stdin(connection: &Connection, slow: bool) { +pub(crate) fn stream_stdin(connection: Connection, slow: bool) { warn!("This mode will break when using multi-byte characters and does not support ANSI escape sequences yet."); let mut app = App { connection, @@ -13,14 +13,14 @@ pub(crate) fn stream_stdin(connection: &Connection, slow: bool) { app.run() } -struct App<'t> { - connection: &'t Connection, +struct App { + connection: Connection, mirror: CharGrid, y: usize, slow: bool, } -impl App<'_> { +impl App { fn run(&mut self) { self.connection .send(Command::Clear) @@ -63,16 +63,15 @@ impl App<'_> { fn send_mirror(&self) { self.connection - .send(Command::Utf8Data( + .send(Command::Cp437Data( Origin::ZERO, - self.mirror.clone(), + Cp437Grid::from(&self.mirror), )) .expect("couldn't send screen to display"); } fn single_line(&mut self, line: &str) { let mut line_grid = CharGrid::new(TILE_WIDTH, 1); - line_grid.fill(' '); Self::line_onto_grid(&mut line_grid, 0, line); Self::line_onto_grid(&mut self.mirror, self.y, line); self.connection diff --git a/src/stream_window.rs b/src/stream_window.rs index f8e41ec..e3a626a 100644 --- a/src/stream_window.rs +++ b/src/stream_window.rs @@ -1,52 +1,63 @@ -use crate::{ - cli::{ImageProcessingOptions, StreamScreenOptions}, - image_processing::ImageProcessingPipeline, +use crate::cli::StreamScreenOptions; +use image::{ + imageops::{dither, resize, BiLevel, FilterType}, + DynamicImage, ImageBuffer, Luma, Rgb, Rgba, }; -use image::{DynamicImage, ImageBuffer, Rgb, Rgba}; -use log::{debug, error, info, trace, warn}; +use log::{error, info, warn}; use scap::{ capturer::{Capturer, Options}, frame::convert_bgra_to_rgb, frame::Frame, }; -use servicepoint::{Command, CompressionCode, Connection, Origin, FRAME_PACING}; -use std::time::{Duration, Instant}; +use servicepoint::{ + Bitmap, Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH, +}; +use std::time::Duration; -pub fn stream_window( - connection: &Connection, - options: StreamScreenOptions, - processing_options: ImageProcessingOptions, -) { +pub fn stream_window(connection: &Connection, options: StreamScreenOptions) { info!("Starting capture with options: {:?}", options); + warn!("this implementation does not drop any frames - set a lower fps or disable dithering if your computer cannot keep up."); + let capturer = match start_capture(&options) { Some(value) => value, None => return, }; - let mut pipeline = ImageProcessingPipeline::new(processing_options); - + let mut bitmap = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); info!("now starting to stream images"); loop { - let start = Instant::now(); - - let frame = capture_frame(&capturer); - let frame = frame_to_image(frame); - let bitmap = pipeline.process(frame); - - trace!("bitmap ready to send in: {:?}", start.elapsed()); + let frame = get_next_frame(&capturer, options.no_dither); + for (mut dest, src) in bitmap.iter_mut().zip(frame.pixels()) { + *dest = src.0[0] > u8::MAX / 2; + } connection .send(Command::BitmapLinearWin( Origin::ZERO, bitmap.clone(), - CompressionCode::default(), + CompressionCode::Uncompressed, )) .expect("failed to send frame to display"); - - debug!("frame time: {:?}", start.elapsed()); } } +fn get_next_frame(capturer: &Capturer, no_dither: bool) -> ImageBuffer, Vec> { + let frame = capturer.get_next_frame().expect("failed to capture frame"); + let frame = frame_to_image(frame); + let frame = frame.grayscale().to_luma8(); + let mut frame = resize( + &frame, + PIXEL_WIDTH as u32, + PIXEL_HEIGHT as u32, + FilterType::Nearest, + ); + + if !no_dither { + dither(&mut frame, &BiLevel); + } + frame +} + fn start_capture(options: &StreamScreenOptions) -> Option { if !scap::is_supported() { error!("platform not supported by scap"); @@ -61,11 +72,10 @@ fn start_capture(options: &StreamScreenOptions) -> Option { } } - // all options are more like a suggestion let mut capturer = Capturer::build(Options { fps: FRAME_PACING.div_duration_f32(Duration::from_secs(1)) as u32, show_cursor: options.pointer, - output_type: scap::frame::FrameType::BGR0, + output_type: scap::frame::FrameType::BGR0, // this is more like a suggestion ..Default::default() }) .expect("failed to create screen capture"); @@ -73,16 +83,8 @@ fn start_capture(options: &StreamScreenOptions) -> Option { Some(capturer) } -fn capture_frame(capturer: &Capturer) -> Frame { - let start_time = Instant::now(); - let result = capturer.get_next_frame().expect("failed to capture frame"); - trace!("capture took: {:?}", start_time.elapsed()); - result -} - fn frame_to_image(frame: Frame) -> DynamicImage { - let start_time = Instant::now(); - let result = match frame { + match frame { Frame::BGRx(frame) => bgrx_to_rgb(frame.width, frame.height, frame.data), Frame::RGBx(frame) => DynamicImage::from( ImageBuffer::, _>::from_raw( @@ -99,9 +101,7 @@ fn frame_to_image(frame: Frame) -> DynamicImage { ), Frame::BGRA(frame) => bgrx_to_rgb(frame.width, frame.height, frame.data), Frame::YUVFrame(_) | Frame::XBGR(_) => panic!("unsupported frame format"), - }; - trace!("conversion to image took: {:?}", start_time.elapsed()); - result + } } fn bgrx_to_rgb(width: i32, height: i32, data: Vec) -> DynamicImage { diff --git a/src/text.rs b/src/text.rs deleted file mode 100644 index 247b9ad..0000000 --- a/src/text.rs +++ /dev/null @@ -1,7 +0,0 @@ -use servicepoint::Connection; -use crate::cli::TextCommand; -use crate::stream_stdin::stream_stdin; - -pub fn text(connection: &Connection, command: TextCommand) { - match command { TextCommand::Stdin { slow } => stream_stdin(connection, slow), } -}