From e59025f645b13236cfd88e5f5e77b6692a3fc690 Mon Sep 17 00:00:00 2001 From: Nickiel12 <35903114+Nickiel12@users.noreply.github.com> Date: Sat, 18 May 2024 13:05:22 -0700 Subject: [PATCH] commiting these before I leave --- Cargo.lock | 540 ------------------ Cargo.toml | 1 - src/config.rs | 28 +- src/coordinator/mod.rs | 73 ++- .../process_box_string.rs | 2 +- src/coordinator/remote_video_processor.rs | 200 +++++++ src/main.rs | 28 +- src/remote_sources/mod.rs | 7 +- src/remote_sources/shared_video_pipe.rs | 217 ------- src/ui/mod.rs | 149 +++-- src/ui/settings_modal.rs | 157 +++++ src/ui/socket_panel.rs | 53 +- src/ui/tracker_panel.rs | 16 +- style.css | 4 + 14 files changed, 559 insertions(+), 916 deletions(-) rename src/{remote_sources => coordinator}/process_box_string.rs (100%) create mode 100644 src/coordinator/remote_video_processor.rs delete mode 100644 src/remote_sources/shared_video_pipe.rs create mode 100644 src/ui/settings_modal.rs diff --git a/Cargo.lock b/Cargo.lock index ea557c7..ae11503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aligned-vec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -44,29 +38,6 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "async-channel" version = "2.2.0" @@ -103,29 +74,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "av1-grain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" -dependencies = [ - "arrayvec", -] - [[package]] name = "backtrace" version = "0.3.71" @@ -147,12 +95,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -168,12 +110,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitstream-io" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" - [[package]] name = "block-buffer" version = "0.10.4" @@ -183,36 +119,18 @@ dependencies = [ "generic-array", ] -[[package]] -name = "built" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" - [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" - [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "0.4.12" @@ -258,10 +176,6 @@ name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" -dependencies = [ - "jobserver", - "libc", -] [[package]] name = "cfg-expr" @@ -297,12 +211,6 @@ dependencies = [ "windows-targets 0.52.4", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "concurrent-queue" version = "2.4.0" @@ -386,34 +294,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -517,31 +397,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "exr" -version = "1.72.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fdeflate" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" -dependencies = [ - "simd-adler32", -] - [[package]] name = "field-offset" version = "0.3.6" @@ -552,25 +407,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "flate2" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -766,16 +602,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gilrs" version = "0.10.6" @@ -1192,16 +1018,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hashbrown" version = "0.13.2" @@ -1276,45 +1092,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "image-webp", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" -dependencies = [ - "byteorder-lite", - "thiserror", -] - -[[package]] -name = "imgref" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" - [[package]] name = "indexmap" version = "2.2.5" @@ -1345,17 +1122,6 @@ dependencies = [ "libc", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "interprocess" version = "2.0.0" @@ -1404,15 +1170,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - [[package]] name = "joystick-controller-client" version = "1.1.0" @@ -1428,7 +1185,6 @@ dependencies = [ "gstreamer-app", "gstreamer-video", "gtk4", - "image", "interprocess", "log", "serde", @@ -1438,12 +1194,6 @@ dependencies = [ "toml", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" version = "0.3.69" @@ -1470,29 +1220,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libfuzzer-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - [[package]] name = "libudev-sys" version = "0.1.4" @@ -1509,31 +1242,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "mach2" version = "0.4.2" @@ -1543,16 +1257,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.7.1" @@ -1581,7 +1285,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -1601,12 +1304,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "nix" version = "0.28.0" @@ -1629,40 +1326,12 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -1679,7 +1348,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", "num-integer", "num-traits", ] @@ -1857,19 +1525,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "png" -version = "0.17.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -1924,40 +1579,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "profiling" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" -dependencies = [ - "quote", - "syn 2.0.53", -] - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.35" @@ -1997,91 +1618,12 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand", - "rand_chacha", - "simd_helpers", - "system-deps", - "thiserror", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "recvmsg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" -[[package]] -name = "rgb" -version = "0.8.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" -dependencies = [ - "bytemuck", -] - [[package]] name = "ron" version = "0.8.1" @@ -2131,12 +1673,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "semver" version = "1.0.22" @@ -2205,21 +1741,6 @@ dependencies = [ "digest", ] -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "simplelog" version = "0.12.2" @@ -2256,15 +1777,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "syn" version = "1.0.109" @@ -2347,17 +1859,6 @@ dependencies = [ "syn 2.0.53", ] -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "time" version = "0.3.34" @@ -2586,17 +2087,6 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" -[[package]] -name = "v_frame" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - [[package]] name = "vec_map" version = "0.8.2" @@ -2685,12 +2175,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - [[package]] name = "widestring" version = "1.1.0" @@ -2924,27 +2408,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" -dependencies = [ - "zune-core", -] diff --git a/Cargo.toml b/Cargo.toml index 5debb58..3750562 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,3 @@ tokio = { version = "1.37.0", features = ["rt-multi-thread", "time"] } tokio-tungstenite = "0.21.0" toml = "0.8.12" interprocess = { version = "2.0.0", features = ["tokio"] } -image = "0.25.1" diff --git a/src/config.rs b/src/config.rs index 8420773..93e05aa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,34 @@ use config::{Config, FileFormat}; use err_derive::Error; use log::{error, info}; +use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::Write; -use crate::ui::AppState; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AppConfig { + pub camera_ip: String, + pub camera_port: u32, -pub fn load_config() -> AppState { + pub tracker_ip: String, + pub tracker_port: u32, + pub tracker_refresh_rate_millis: u32, +} + +impl Default for AppConfig { + fn default() -> Self { + AppConfig { + camera_ip: "10.0.0.33".to_string(), + camera_port: 8765, + + tracker_ip: "10.0.0.210".to_string(), + tracker_port: 8765, + tracker_refresh_rate_millis: 100, + } + } +} + +pub fn load_config() -> AppConfig { let settings = Config::builder() .add_source(config::File::new("./settings.toml", FileFormat::Toml)) .build(); @@ -24,7 +46,7 @@ pub enum SaveConfigError { IoError(#[cause] std::io::Error), } -pub fn save_config(config: &AppState) -> Result<(), SaveConfigError> { +pub fn save_config(config: &AppConfig) -> Result<(), SaveConfigError> { let toml_str = toml::to_string(&config)?; let mut file = File::create("./settings.toml")?; file.write_all(toml_str.as_bytes())?; diff --git a/src/coordinator/mod.rs b/src/coordinator/mod.rs index 1b6aae3..bf6b888 100644 --- a/src/coordinator/mod.rs +++ b/src/coordinator/mod.rs @@ -13,15 +13,21 @@ use futures_util::{ }; use gstreamer::prelude::ElementExt; use gstreamer::State; -use log::{error, info, debug}; +use log::{error, info}; use tokio::net::TcpStream; use tokio::runtime::Handle; +use tokio::sync::RwLock; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; +mod process_box_string; +mod remote_video_processor; +use crate::config::AppConfig; use crate::remote_sources::TrackerState; use crate::{gstreamer_pipeline, remote_sources}; use crate::{joystick_source::joystick_loop, ui::GuiUpdate}; +use self::remote_video_processor::remote_video_loop; + const PRIORITY_TIMEOUT: Duration = Duration::from_secs(2); #[derive(Clone)] @@ -38,13 +44,21 @@ pub enum ConnectionType { } pub enum ApplicationEvent { - StartSocket(String), + StartCameraSocket, + StartTrackerSocket, SocketMessage(Message), MoveEvent(MoveEvent, ConnectionType), EnableAutomatic(bool), } +struct SocketState { + pub is_connected: Arc, + pub stay_connected: Arc, +} + struct CoordState<'a> { + pub settings: Arc>, + pub sck_outbound: Option>, Message>>, pub sck_alive_server: Arc, pub sck_alive_recvr: Arc, @@ -71,8 +85,11 @@ impl<'a> CoordState<'a> { to_gui: Sender, rt: Handle, tracker_state: Arc>, + settings: Arc>, ) -> Self { let this = CoordState { + settings, + sck_outbound: None, sck_alive_recvr: Arc::new(AtomicBool::new(false)), sck_alive_server: Arc::new(AtomicBool::new(false)), @@ -91,14 +108,6 @@ impl<'a> CoordState<'a> { tracker_state, }; - this.rt.spawn( - crate::remote_sources::shared_video_pipe::create_outbound_pipe( - this.pipeline.sink_frame.clone(), - this.to_mec.clone(), - this.keep_windows_pipe_alive.clone(), - this.tracker_state.clone(), - ), - ); this } @@ -116,10 +125,16 @@ impl<'a> CoordState<'a> { self.sck_outbound.is_some() } - async fn socket_start(&mut self, conn: String) { + async fn socket_start(&mut self) { info!("Starting socket"); - match connect_async(conn).await { + let conn_string: String = { + let read_settings = self.settings.read().await; + + format!("ws://{}:{}", read_settings.camera_ip, read_settings.camera_port.to_string()) + }; + + match connect_async(conn_string).await { Ok((val, _)) => { info!("Socket connection to camera made successfully"); @@ -146,6 +161,26 @@ impl<'a> CoordState<'a> { self.sck_alive_recvr.store(false, Ordering::SeqCst); } + pub async fn start_video_loop(&mut self) { + let conn_string: String = { + let read_settings = self.settings.read().await; + + format!("ws://{}:{}", read_settings.tracker_ip, read_settings.tracker_port.to_string()) + }; + + self.rt.spawn( + remote_video_loop( + conn_string, + self.pipeline.sink_frame.clone(), + self.to_mec.clone(), + self.keep_windows_pipe_alive.clone(), + self.tracker_state.clone(), + self.rt.clone() + ), + ); + + } + pub async fn check_states(&mut self) { if !self.joystick_loop_alive.load(Ordering::SeqCst) { info!("Restarting joystick loop"); @@ -155,6 +190,10 @@ impl<'a> CoordState<'a> { )); } + if !self.keep_windows_pipe_alive.load(Ordering::SeqCst) { + + } + if !self.sck_alive_server.load(Ordering::SeqCst) { info!("Restarting socket server"); self.sck_alive_server.store(true, Ordering::SeqCst); @@ -197,12 +236,13 @@ pub async fn start_coordinator( to_gui: Sender, runtime: Handle, tracker_state: Arc>, + settings: Arc>, ) { info!("Starting coordinator!"); let mec = pin!(mec); - let mut state = CoordState::new(mec, to_mec, to_gui, runtime, tracker_state); + let mut state = CoordState::new(mec, to_mec, to_gui, runtime, tracker_state, settings); state .pipeline @@ -226,8 +266,11 @@ pub async fn start_coordinator( state.check_states().await; match msg { - ApplicationEvent::StartSocket(conn) => { - state.socket_start(conn).await; + ApplicationEvent::StartCameraSocket => { + state.socket_start().await; + } + ApplicationEvent::StartTrackerSocket => { + state.start_video_loop().await; } ApplicationEvent::SocketMessage(socket_message) => { if let Err(e) = state.to_gui.send(GuiUpdate::SocketState(true)).await { diff --git a/src/remote_sources/process_box_string.rs b/src/coordinator/process_box_string.rs similarity index 100% rename from src/remote_sources/process_box_string.rs rename to src/coordinator/process_box_string.rs index 153b409..340b585 100644 --- a/src/remote_sources/process_box_string.rs +++ b/src/coordinator/process_box_string.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; -use crate::ui::NormalizedBoxCoords; use super::TrackerState; +use crate::ui::NormalizedBoxCoords; pub fn process_incoming_string( message: String, diff --git a/src/coordinator/remote_video_processor.rs b/src/coordinator/remote_video_processor.rs new file mode 100644 index 0000000..a6b66b4 --- /dev/null +++ b/src/coordinator/remote_video_processor.rs @@ -0,0 +1,200 @@ +use std::{ + cmp::{max, min}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + time::Duration, +}; + +use async_channel::Sender; +use futures_util::{stream::SplitStream, SinkExt, StreamExt, TryStreamExt}; +use gstreamer_app::AppSink; +use gstreamer_video::{video_frame::Readable, VideoFrame, VideoInfo}; +use log::{error, info}; +use tokio::{ + net::TcpStream, + runtime::Handle, + time::{sleep_until, Instant}, +}; +use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; + +use crate::remote_sources::TrackerState; + +use super::{process_box_string::process_incoming_string, ApplicationEvent}; + +pub async fn remote_video_loop( + conn_string: String, + appsink: Arc>, + to_mec: Sender, + keep_alive: Arc, + tracker_state: Arc>, + runtime: Handle, +) { + let video_info = + gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgb, 640, 480) + .build() + .expect("Could not build video info!"); + + loop { + match connect_async(&conn_string).await { + Err(e) => { + error!("Could not connect to remote video loop! Trying again in 5 seconds: {e}"); + sleep_until(Instant::now() + Duration::from_secs(5)).await; + } + Ok((connection, _)) => { + let (mut sender, recvr) = connection.split(); + + runtime.spawn(listen_to_messages( + recvr, + to_mec.clone(), + tracker_state.clone(), + keep_alive.clone(), + )); + + loop { + // Do this in an encloser to not keep a lock on the appsink + let video_frame = match { + let appsnk = match appsink.lock() { + Ok(e) => e, + Err(e) => { + error!("Unrecoverable error: Could not get a lock on the appsink in remote video loop {e}"); + return; + } + }; + + get_video_frame(&appsnk, &video_info) + } { + Ok(e) => e, + Err(e) => { + error!("Could not get video frame! {e}"); + if let Err(e) = sender.close().await { + error!("Could not close socket to remote computer: {e}") + } + keep_alive.store(false, Ordering::SeqCst); + return; + } + }; + + if let Err(e) = sender + .send(Message::binary( + video_frame + .plane_data(0) + .expect("Could not get video frame data"), + )) + .await + { + error!("There was an error sending the video frame to the server: {e}"); + if let Err(e) = sender.close().await { + error!("Could not close socket to remote computer: {e}") + } + keep_alive.store(false, Ordering::SeqCst); + return; + } + + // rate limit updates + sleep_until(Instant::now() + Duration::from_millis(50)).await; + + } + } + } + if !keep_alive.load(Ordering::SeqCst) { + info!("Shutting down remote video loop"); + break; + } + } +} + +async fn listen_to_messages( + mut recvr: SplitStream>>, + to_mec: Sender, + tracker_state: Arc>, + keep_alive: Arc, +) { + while keep_alive.load(Ordering::SeqCst) { + match recvr.try_next().await { + Ok(Some(message)) => { + let (x_off, y_off, do_send) = + process_incoming_string(message.to_string(), &tracker_state) + .and_then(|_| calculate_tracking(&tracker_state)) + .unwrap_or((0, 0, false)); + + if do_send { + if let Err(e) = to_mec + .send(ApplicationEvent::MoveEvent( + super::MoveEvent { x: x_off, y: y_off }, + super::ConnectionType::Automated, + )) + .await + { + error!("Could not send message to MEC, assuming critical failure: {e}"); + keep_alive.store(false, Ordering::SeqCst); + return; + } + } + } + Ok(None) => info!("Recieved an empty message from the remote computer"), + Err(e) => { + error!("Got an error on while recieving from remote computer: {e}"); + } + } + } +} + +fn get_video_frame( + appsink: &AppSink, + video_info: &VideoInfo, +) -> Result, String> { + let sample = appsink + .pull_sample() + .map_err(|e| format!("Could not pull appsink sample: {e}"))?; + let buffer = sample.buffer_owned().unwrap(); + gstreamer_video::VideoFrame::from_buffer_readable(buffer, video_info) + .map_err(|_| format!("Unable to make video frame from buffer!")) +} + +fn calculate_tracking( + tracker_state: &Arc>, +) -> core::result::Result<(i32, i32, bool), String> { + #[cfg(feature = "tracker-state-debug")] + debug!("Getting lock on tracker state for caculate tracking"); + if let Ok(mut ts) = tracker_state.lock() { + // if ts.last_detect + Duration::from_secs(2) < Instant::now() && !ts.identity_boxes.is_empty() { + // info!("Setting new target: {}", ts.identity_boxes[0].id); + // ts.tracking_id = ts.identity_boxes[0].id; + // } + + if let Some(target_box) = ts.identity_boxes.iter().find(|e| e.id == ts.tracking_id) { + let x_adjust = calc_x_adjust(target_box.x1, target_box.x2); + let y_adjust = calc_y_adjust(target_box.y1); + ts.last_detect = std::time::Instant::now(); + Ok((x_adjust, y_adjust, ts.enabled)) + } else { + Err("Couldn't find target in results".to_string()) + } + } else { + Err("Couldn't lock tracker state".to_string()) + } +} + +fn calc_x_adjust(x1: f32, x2: f32) -> i32 { + let dist_from_center = ((x1 + x2) / 2.0) - 0.5; + let mut x_adjust = ((dist_from_center / 0.5 * 2.0) * 100.0) as i32; + if x_adjust < 15 && x_adjust > -15 { + x_adjust = 0; + } + min(max(x_adjust, -100), 100) +} + +fn calc_y_adjust(y1: f32) -> i32 { + // All values are normalized, then multiplied by 1000. 500 == 50% of the screen + let mut y_adjust = ((y1 - 0.1) * 250.0) as i32; + if y_adjust < 0 { + y_adjust -= 20; + } else if y_adjust < 30 { + y_adjust = 0; + } else { + y_adjust = (y_adjust as f32 * 0.75) as i32; + } + min(max(y_adjust, -100), 100) +} diff --git a/src/main.rs b/src/main.rs index be8feb6..3447bf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,17 @@ -use gtk::gdk::Display; use gtk::{glib, Application}; -use gtk::{prelude::*, CssProvider}; use log::{error, info}; +use gtk::prelude::{ApplicationExt, ApplicationExtManual}; use simplelog::SimpleLogger; -use std::env; -use tokio::runtime; +use std::{env, sync::Arc}; +use tokio::{runtime, sync::RwLock}; + +use crate::config::load_config; -mod config; mod coordinator; mod gstreamer_pipeline; mod joystick_source; mod remote_sources; +mod config; mod ui; const APP_ID: &str = "net.nickiel.joystick-controller-client"; @@ -23,8 +24,9 @@ fn main() -> glib::ExitCode { error!("Failed to init the simplelogger!: {e}"); } - gstreamer::init().expect("Unable to start gstreamer"); + let config = Arc::new(RwLock::new(load_config())); + gstreamer::init().expect("Unable to start gstreamer"); gstgtk4::plugin_register_static().expect("Unable to register gtk4 plugin"); let rt = runtime::Runtime::new().expect("Could not start tokio runtime"); @@ -32,10 +34,10 @@ fn main() -> glib::ExitCode { let app = Application::builder().application_id(APP_ID).build(); - app.connect_startup(|_| load_css()); + app.connect_startup(ui::on_activate); app.connect_activate(move |app| { - ui::build_ui(app, handle.clone()); + ui::build_ui(app, config.clone(), handle.clone()); }); let exit_code = app.run(); @@ -49,13 +51,3 @@ fn main() -> glib::ExitCode { exit_code } -fn load_css() { - let provider = CssProvider::new(); - provider.load_from_string(include_str!("../style.css")); - - gtk::style_context_add_provider_for_display( - &Display::default().expect("Could not connect to a display"), - &provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ) -} diff --git a/src/remote_sources/mod.rs b/src/remote_sources/mod.rs index bb169ab..b858d0c 100644 --- a/src/remote_sources/mod.rs +++ b/src/remote_sources/mod.rs @@ -20,11 +20,12 @@ use tokio_tungstenite::{ tungstenite::{Error, Message, Result}, }; -mod process_box_string; mod remote_source; -pub mod shared_video_pipe; -use crate::{coordinator::{ApplicationEvent, ConnectionType}, ui::NormalizedBoxCoords}; +use crate::{ + coordinator::{ApplicationEvent, ConnectionType}, + ui::NormalizedBoxCoords, +}; pub struct TrackerState { pub tracking_id: u32, diff --git a/src/remote_sources/shared_video_pipe.rs b/src/remote_sources/shared_video_pipe.rs deleted file mode 100644 index 87474c4..0000000 --- a/src/remote_sources/shared_video_pipe.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::{ - cmp::{max, min}, - sync::{atomic::AtomicBool, Arc, Mutex}, - time::{Duration, Instant}, -}; - -use async_channel::Sender; -use gstreamer_app::AppSink; -use gstreamer_video::{video_frame::Readable, VideoFrame, VideoInfo}; -use interprocess::os::windows::named_pipe::{ - pipe_mode::{self, Bytes}, - tokio::{DuplexPipeStream, PipeStream}, -}; -use log::{error, info, debug}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -use crate::{ - coordinator::{ApplicationEvent, MoveEvent}, - remote_sources::process_box_string, -}; - -use super::TrackerState; - -struct LoopState { - pub appsink: Arc>, - pub mec: Sender, - pub tracker_state: Arc>, - - pub pipe: PipeStream, - - pub video_info: VideoInfo, - pub byte_buffer: Vec, - pub len_buf: [u8; 4], -} - -impl LoopState { - fn get_video_frame(&mut self) -> Result, String> { - let sample = self - .appsink - .lock() - .map_err(|e| format!("Could not get a lock on the appsink: {e}"))? - .pull_sample() - .map_err(|e| format!("Could not pull appsink sample: {e}"))?; - let buffer = sample.buffer_owned().unwrap(); - gstreamer_video::VideoFrame::from_buffer_readable(buffer, &self.video_info) - .map_err(|_| format!("Unable to make video frame from buffer!")) - } - - async fn read_return_message(&mut self) -> Result { - // Read message size from the pipe - if let Err(e) = self.pipe.read_exact(&mut self.len_buf).await { - return Err(format!( - "Couldn't read message length from the windows pipe: {e}" - )); - } - let length = u32::from_le_bytes(self.len_buf); - self.byte_buffer.resize(length as usize, 0); - // Read the message of message length from the pipe - if let Err(e) = self.pipe.read_exact(&mut self.byte_buffer).await { - return Err(format!( - "Couldn't read the message from the windows pipe: {e}" - )); - } - Ok(String::from_utf8_lossy(&self.byte_buffer).to_string()) - } -} - -pub async fn create_outbound_pipe( - appsink: Arc>, - mec: Sender, - keep_alive: Arc, - tracker_state: Arc>, -) { - if let Ok(pipe) = - DuplexPipeStream::::connect_by_path(r"\\.\pipe\example_pipe").await - { - let mut state = LoopState { - appsink, - mec, - tracker_state, - - pipe, - - video_info: gstreamer_video::VideoInfo::builder( - gstreamer_video::VideoFormat::Rgb, - 640, - 480, - ) - .build() - .expect("Couldn't build video info!"), - byte_buffer: Vec::new(), - len_buf: [0; 4], - }; - - let mut prev_tick = Instant::now(); - - loop { - if !keep_alive.load(std::sync::atomic::Ordering::SeqCst) { - break; - } - - if prev_tick + Duration::from_millis(30) < Instant::now() { - tokio::time::sleep(prev_tick + Duration::from_millis(30) - Instant::now()).await; - } - prev_tick = Instant::now(); - - state.len_buf = [0; 4]; - - let video_frame = match state.get_video_frame() { - Ok(e) => e, - Err(e) => { - error!("{}", e); - break; - } - }; - - // Send video frame to pipe - if let Err(e) = send_to_pipe(&mut state.pipe, video_frame.plane_data(0).unwrap()).await - { - error!("Error in sending to the pipe: {e}"); - break; - } - - let message = match state.read_return_message().await { - Ok(e) => e, - Err(e) => { - error!("{}", e); - break; - } - }; - - let x_off: i32; - let y_off: i32; - - // Load the tracking boxes into identity_boxes, then do the adjustment calcuations on the updated tracking info (side-effects) - (x_off, y_off) = process_box_string::process_incoming_string( - message.to_string(), - &state.tracker_state, - ) - .and_then(|_| calculate_tracking(&state.tracker_state)) - .unwrap_or((0, 0)); - - if let Err(e) = state - .mec - .send(ApplicationEvent::MoveEvent( - MoveEvent { x: x_off, y: y_off }, - crate::coordinator::ConnectionType::Automated, - )) - .await - { - error!("MEC Unavailable, closing the connection on the pipe: {e}"); - break; - } - } - - info!("Windows pipe has been shut down"); - - keep_alive.store(false, std::sync::atomic::Ordering::SeqCst); - if let Err(e) = state.pipe.shutdown().await { - error!("Couldn't shut down pipe: {e}"); - } - } -} - -async fn send_to_pipe<'a>( - pipe: &mut PipeStream, - message: &'a [u8], -) -> Result<(), Box> { - pipe.write_all(message).await?; - Ok(()) -} - -fn calculate_tracking( - tracker_state: &Arc>, -) -> core::result::Result<(i32, i32), String> { - #[cfg(feature = "tracker-state-debug")] - debug!("Getting lock on tracker state for caculate tracking"); - if let Ok(mut ts) = tracker_state.lock() { - // if ts.last_detect + Duration::from_secs(2) < Instant::now() && !ts.identity_boxes.is_empty() { - // info!("Setting new target: {}", ts.identity_boxes[0].id); - // ts.tracking_id = ts.identity_boxes[0].id; - // } - - if let Some(target_box) = ts.identity_boxes.iter().find(|e| e.id == ts.tracking_id) { - let x_adjust = calc_x_adjust(target_box.x1, target_box.x2); - let y_adjust = calc_y_adjust(target_box.y1); - ts.last_detect = Instant::now(); - Ok((x_adjust, y_adjust)) - } else { - Err("Couldn't find target in results".to_string()) - } - } else { - Err("Couldn't lock tracker state".to_string()) - } -} - -fn calc_x_adjust(x1: f32, x2: f32) -> i32 { - let dist_from_center = ((x1 + x2) / 2.0) - 0.5; - let mut x_adjust = ((dist_from_center / 0.5 * 2.0) * 100.0) as i32; - if x_adjust < 15 && x_adjust > -15 { - x_adjust = 0; - } - min(max(x_adjust, -100), 100) -} - -fn calc_y_adjust(y1: f32) -> i32 { - // All values are normalized, then multiplied by 1000. 500 == 50% of the screen - let mut y_adjust = ((y1 - 0.1) * 250.0) as i32; - if y_adjust < 0 { - y_adjust -= 20; - } else if y_adjust < 30 { - y_adjust = 0; - } else { - y_adjust = (y_adjust as f32 * 0.75) as i32; - } - min(max(y_adjust, -100), 100) -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 4f9b623..e41e2f3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,37 +2,25 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use gtk::cairo::Context; -use gtk::gdk::Paintable; -use gtk::{glib, prelude::*, AspectFrame, Label, ListBox}; +use gtk::gdk::{Display, Paintable}; +use gtk::glib::clone; +use gtk::{gio, glib, prelude::*, AspectFrame, CssProvider, Label, ListBox}; use gtk::{Application, ApplicationWindow}; -use log::{error, info, debug}; -use serde::{Deserialize, Serialize}; +use log::{error, info}; use tokio::runtime::Handle; +use tokio::sync::RwLock; -use crate::config::load_config; +use crate::config::AppConfig; use crate::coordinator::{start_coordinator, ApplicationEvent, MoveEvent}; use crate::remote_sources::TrackerState; mod socket_panel; mod tracker_panel; +mod settings_modal; use socket_panel::SocketPanel; use tracker_panel::TrackerPanel; -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct AppState { - pub ip: String, - pub port: u32, -} - -impl Default for AppState { - fn default() -> Self { - AppState { - ip: "10.0.0.30".to_string(), - port: 8765, - } - } -} pub enum GuiUpdate { SocketState(bool), @@ -68,8 +56,38 @@ impl NormalizedBoxCoords { } } -pub fn build_ui(app: &Application, runtime: Handle) { - let initial_settings = load_config(); +pub fn on_activate(app: &Application) { + let provider = CssProvider::new(); + provider.load_from_string(include_str!("../../style.css")); + + gtk::style_context_add_provider_for_display( + &Display::default().expect("Could not connect to a display"), + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + + let menubar = { + let connections_menu = { + let settings = gio::MenuItem::new(Some("Edit IPs"), Some("app.connections")); + + let connections_menu = gio::Menu::new(); + connections_menu.append_item(&settings); + + connections_menu + }; + + let menubar = gio::Menu::new(); + menubar.append_submenu(Some("Connections"), &connections_menu); + + menubar + }; + + app.set_menubar(Some(&menubar)); + info!("Menu bar set up"); + +} + +pub fn build_ui(app: &Application, config: Arc>, runtime: Handle) { let main_box = gtk::Box::new(gtk::Orientation::Horizontal, 0); let left_box = ListBox::builder().width_request(200).build(); @@ -77,11 +95,26 @@ pub fn build_ui(app: &Application, runtime: Handle) { let window = ApplicationWindow::builder() .application(app) .title("VCC Camera Controller") + .show_menubar(true) .default_width(840) .default_height(480) .child(&main_box) .build(); + + let rt = runtime.clone(); + let config_modal_config = config.clone(); + let connections_activate = gio::ActionEntry::builder("connections") + .activate(clone!(@weak window => move |app: >k::Application, _, _| { + let connections_modal = settings_modal::ConnectionsModal::new(&app, &window, &rt, &config_modal_config); + + connections_modal.window.present(); + })) + .build(); + + app.add_action_entries([connections_activate]); + + // Main Event Channel let (to_mec, mec) = async_channel::unbounded::(); let (to_gui, gui_recv) = async_channel::bounded::(10); @@ -94,12 +127,15 @@ pub fn build_ui(app: &Application, runtime: Handle) { update_ids: false, })); + let coord_config = config.clone(); + runtime.spawn(start_coordinator( mec, to_mec.clone(), to_gui, runtime.clone(), tracker_state.clone(), + coord_config, )); // let conn_status_label = Label::new(Some(&"No Connection".to_string())); @@ -110,7 +146,7 @@ pub fn build_ui(app: &Application, runtime: Handle) { let tabpanel = gtk::Notebook::builder().enable_popup(true).build(); - let socket_panel = Arc::new(SocketPanel::new(&initial_settings)); + let socket_panel = Arc::new(SocketPanel::new()); socket_panel.connect_button_callback(to_mec.clone()); tabpanel.append_page( socket_panel.get_top_level(), @@ -146,17 +182,11 @@ pub fn build_ui(app: &Application, runtime: Handle) { .build(); main_box.append(&aspect); - let drawable = gtk::DrawingArea::builder() - .build(); + let drawable = gtk::DrawingArea::builder().build(); let drawable_ts = tracker_state.clone(); drawable.set_draw_func(move |_, ctx, width, height| { - draw_boxes( - width, - height, - &ctx, - &drawable_ts, - ); + draw_boxes(width, height, &ctx, &drawable_ts); }); overlay_box.set_child(Some(&webcam_picture)); @@ -165,36 +195,38 @@ pub fn build_ui(app: &Application, runtime: Handle) { let items = tracker_panel.items.clone(); let id_label = tracker_panel.current_id.clone(); - glib::timeout_add_seconds_local(1, + glib::timeout_add_seconds_local( + 1, glib::clone!(@strong items, @strong id_label => move || { - #[cfg(feature = "tracker-state-debug")] - debug!("Getting lock on tracker state for checking identity boxes"); + #[cfg(feature = "tracker-state-debug")] + debug!("Getting lock on tracker state for checking identity boxes"); - // don't update the stringlist until after letting go of the tracker state - // due to async interweaving causing a mutex deadlock - let mut ids: Option> = None; - let mut current_id: Option = None; - if let Ok(ts) = tracker_state.lock() { - current_id = Some(ts.tracking_id); - if ts.update_ids { - ids = Some(ts.identity_boxes - .iter() - .map(|t| t.id.to_string()) - .collect()); + // don't update the stringlist until after letting go of the tracker state + // due to async interweaving causing a mutex deadlock + let mut ids: Option> = None; + let mut current_id: Option = None; + if let Ok(ts) = tracker_state.lock() { + current_id = Some(ts.tracking_id); + if ts.update_ids { + ids = Some(ts.identity_boxes + .iter() + .map(|t| t.id.to_string()) + .collect()); + } } - } - if let Some(id) = current_id { - id_label.set_text(id.to_string().as_str()); - } + if let Some(id) = current_id { + id_label.set_text(id.to_string().as_str()); + } - if let Some (ids) = ids { - let old_len = items.n_items(); - items.splice(0, old_len, &ids.iter().map(|x| x.as_str()).collect::>()[0..]) - } + if let Some (ids) = ids { + let old_len = items.n_items(); + items.splice(0, old_len, &ids.iter().map(|x| x.as_str()).collect::>()[0..]) + } - glib::ControlFlow::Continue - })); + glib::ControlFlow::Continue + }), + ); glib::spawn_future_local( glib::clone!(@weak axis_label, @weak conn_status_label, @weak tabpanel, @strong socket_panel, @strong gui_recv, @weak drawable => async move { @@ -209,11 +241,9 @@ pub fn build_ui(app: &Application, runtime: Handle) { GuiUpdate::SocketState(v) => { let label = { if v { - socket_panel.set_sensitive(false); // tabpanel.set_show_tabs(true); "Currently Connected" } else { - socket_panel.set_sensitive(true); // tabpanel.set_page(0); // tabpanel.set_show_tabs(false); "Currently Disconnected" @@ -246,12 +276,7 @@ pub fn build_ui(app: &Application, runtime: Handle) { window.present(); } -fn draw_boxes( - width: i32, - height: i32, - ctx: &Context, - tracker_state: &Arc>, -) { +fn draw_boxes(width: i32, height: i32, ctx: &Context, tracker_state: &Arc>) { ctx.set_line_width(5.0); ctx.select_font_face( "Arial", diff --git a/src/ui/settings_modal.rs b/src/ui/settings_modal.rs new file mode 100644 index 0000000..5bf1cd2 --- /dev/null +++ b/src/ui/settings_modal.rs @@ -0,0 +1,157 @@ +use std::sync::Arc; + +use gtk::glib::{self, clone}; +use gtk::{prelude::{BoxExt, ButtonExt, EditableExt}, Application, ApplicationWindow, Box, Button, Entry, Label, Window}; +use tokio::runtime::Handle; +use tokio::sync::RwLock; +use log::{error, info}; + +use crate::config::{save_config, AppConfig}; + + +pub struct ConnectionsModal { + pub window: Window, + + pub top_level: Box, +} + +impl ConnectionsModal { + pub fn new(app: &Application, parent: &ApplicationWindow, rt: &Handle, app_config: &Arc>) -> Self { + + // Send help :( + let config_read = rt.block_on(async {app_config.read().await}); + + let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + + let window = Window::builder() + .application(app) + .title("Change Connection Information") + .modal(true) + .transient_for(parent) + .destroy_with_parent(true) + .resizable(false) + .child(&main_box) + .build(); + + let camera_label = Label::builder() + .label("Camera Connection") + .margin_end(5) + .margin_start(5) + .build(); + + let camera_ip_entry = Entry::builder() + .placeholder_text("IP Address") + .text(config_read.camera_ip.clone()) + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .can_focus(true) + .build(); + + let camera_port_entry = Entry::builder() + .placeholder_text("Port") + .text(config_read.camera_port.to_string()) + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .can_focus(true) + .build(); + + let tracker_label = Label::builder() + .label("Tracker Connection") + .margin_end(5) + .margin_start(5) + .build(); + + let tracker_ip_entry = Entry::builder() + .placeholder_text("IP Address") + .text(config_read.tracker_ip.clone()) + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .can_focus(true) + .build(); + + let tracker_port_entry = Entry::builder() + .placeholder_text("Port") + .text(config_read.tracker_port.to_string()) + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .can_focus(true) + .build(); + + let tracker_refresh_millis = Entry::builder() + .placeholder_text("Refresh timeout") + .text(config_read.tracker_refresh_rate_millis.to_string()) + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .can_focus(true) + .build(); + + let quit_button = Button::builder() + .label("Save") + // .action_name("window.close") + .margin_top(5) + .margin_end(5) + .margin_start(5) + .margin_bottom(5) + .build(); + + main_box.append(&camera_label); + main_box.append(&camera_ip_entry); + main_box.append(&camera_port_entry); + main_box.append(&tracker_label); + main_box.append(&tracker_ip_entry); + main_box.append(&tracker_port_entry); + main_box.append(&tracker_refresh_millis); + main_box.append(&quit_button); + + let new_ref = app_config.clone(); + quit_button + .connect_activate(clone!( + @strong rt, + @weak camera_ip_entry, @weak camera_port_entry, + @weak tracker_ip_entry, @weak tracker_port_entry, + @weak tracker_refresh_millis => move |_| { + + let new_camera_ip = camera_ip_entry.text().to_string(); + let new_camera_port = camera_port_entry.text().parse::().unwrap(); + + let new_tracker_ip = tracker_ip_entry.text().to_string(); + let new_tracker_port = tracker_port_entry.text().parse::().unwrap(); + let new_tracker_millis = tracker_refresh_millis.text().parse::().unwrap(); + + { + // maybe just send the police. + let mut write_lock = rt.block_on(async {new_ref.write().await}); + + write_lock.camera_ip = new_camera_ip; + write_lock.camera_port = new_camera_port; + + write_lock.tracker_ip = new_tracker_ip; + write_lock.tracker_port = new_tracker_port; + write_lock.tracker_refresh_rate_millis = new_tracker_millis; + // why does this feel like the borrow checker gave me a pass? + if let Err(e) = save_config(&write_lock.to_owned()) { + error!("Could not save config! {e}"); + } + // FBI!!! OPEN UP!!!! + } + + info!("Please nicholas, add a non-crashing parse"); + })); + + ConnectionsModal { + window, + + top_level: main_box, + } + } +} \ No newline at end of file diff --git a/src/ui/socket_panel.rs b/src/ui/socket_panel.rs index cbdab6e..516963c 100644 --- a/src/ui/socket_panel.rs +++ b/src/ui/socket_panel.rs @@ -1,25 +1,19 @@ use async_channel::Sender; use gtk::{ - glib, - prelude::{BoxExt, ButtonExt, EditableExt, WidgetExt}, - Box, Button, Entry, + glib, prelude::{BoxExt, ButtonExt}, Box, Button }; use log::error; -use crate::{config::save_config, coordinator::ApplicationEvent}; - -use super::AppState; +use crate::coordinator::ApplicationEvent; pub struct SocketPanel { top_level: Box, - ip_entry: Entry, - port_entry: Entry, connect_button: Button, } impl SocketPanel { - pub fn new(initial_settings: &crate::ui::AppState) -> SocketPanel { + pub fn new() -> SocketPanel { let content_box = Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(10) @@ -29,26 +23,12 @@ impl SocketPanel { .margin_bottom(12) .build(); - let ip_entry = Entry::builder() - .placeholder_text("IP Address") - .text(&initial_settings.ip) - .can_focus(true) - .build(); - let port_entry = Entry::builder() - .placeholder_text("Port") - .text(&initial_settings.port.to_string()) - .can_focus(true) - .build(); let button = Button::builder().margin_top(12).build(); - content_box.append(&ip_entry); - content_box.append(&port_entry); content_box.append(&button); SocketPanel { top_level: content_box, - ip_entry, - port_entry, connect_button: button, } } @@ -57,42 +37,19 @@ impl SocketPanel { &self.top_level } - pub fn set_sensitive(&self, is_sensitive: bool) { - self.ip_entry.set_sensitive(is_sensitive); - self.port_entry.set_sensitive(is_sensitive); - } pub fn button_label(&self, new_label: &str) { self.connect_button.set_label(new_label); } pub fn connect_button_callback(&self, to_mec: Sender) { - let ip_entry = &self.ip_entry; - let port_entry = &self.port_entry; - self.connect_button.connect_clicked(glib::clone!(@weak ip_entry, @weak port_entry, @strong to_mec => move |_button| { - let ip_text = ip_entry.text(); - let port_text = port_entry.text(); - - if ip_text.len() == 0 { - return; - } - if let Ok(val) = port_text.parse::() { - if let Err(error) = save_config(&AppState { - ip: ip_text.to_string(), - port: val - }) { - error!("{error}"); - }; - - match to_mec.try_send(ApplicationEvent::StartSocket( - format!("ws://{}:{}", ip_text, val), - )) { + self.connect_button.connect_clicked(glib::clone!(@strong to_mec => move |_button| { + match to_mec.try_send(ApplicationEvent::StartCameraSocket) { Ok(_) => {}, Err(async_channel::TrySendError::Closed(_)) => panic!("Coordinator MEC is closed. Unrecoverable error."), Err(e) => error!("There was an error sending to the MEC: {}", e), } - } })); } } diff --git a/src/ui/tracker_panel.rs b/src/ui/tracker_panel.rs index 334459f..65abf81 100644 --- a/src/ui/tracker_panel.rs +++ b/src/ui/tracker_panel.rs @@ -2,9 +2,13 @@ use std::sync::{Arc, Mutex}; use async_channel::Sender; use gtk::{ - glib::object::CastNone, prelude::{ - BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ListModelExt, ToggleButtonExt - }, Box, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory, SingleSelection, StringList, StringObject, ToggleButton, Widget + glib::object::CastNone, + prelude::{ + BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ListModelExt, + ToggleButtonExt, + }, + Box, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory, SingleSelection, + StringList, StringObject, ToggleButton, Widget, }; use log::{debug, error}; @@ -47,9 +51,7 @@ impl TrackerPanel { .build(); model.connect_selected_item_notify(move |x| { - let item = x - .selected_item() - .and_downcast::(); + let item = x.selected_item().and_downcast::(); if let Some(item) = item { if let Ok(id) = item.string().parse::() { @@ -108,13 +110,11 @@ impl TrackerPanel { } } - pub fn get_top_level(&self) -> &Box { &self.top_level } pub fn connect_button_callback(&self, to_mec: Sender) { - self.enable_disable.connect_clicked(move |button| { if let Err(e) = to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active())) diff --git a/style.css b/style.css index 7a11357..e3778ab 100644 --- a/style.css +++ b/style.css @@ -2,6 +2,10 @@ scrolledwindow > listview { font-size: 10pt; } +menuitem > * { + font-size: 16pt; +} + label.current-id { font-size: 12pt; color: black;