From 05ab8a8bf4b2ebff8ef2715aa1dcb81c3d0ba924 Mon Sep 17 00:00:00 2001 From: AZ Computer Guru Date: Mon, 29 Dec 2025 18:56:18 -0700 Subject: [PATCH] Unify agent and viewer into single guruconnect binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed package from guruconnect-agent to guruconnect - Added CLI subcommands: agent, view, install, uninstall, launch - Moved viewer code into agent/src/viewer module - Added install module with: - UAC elevation attempt with user-install fallback - Protocol handler registration (guruconnect://) - System-wide install to Program Files or user install to LocalAppData - Single binary now handles both receiving and initiating connections - Protocol URL format: guruconnect://view/SESSION_ID?token=API_KEY Usage: guruconnect agent - Run as background agent guruconnect view - View a remote session guruconnect install - Install and register protocol guruconnect launch - Handle guruconnect:// URL 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Cargo.lock | 914 ++++++++++++++++-- Cargo.toml | 1 - agent/Cargo.toml | 28 +- agent/src/install.rs | 364 +++++++ agent/src/main.rs | 392 +++++--- {viewer/src => agent/src/viewer}/input.rs | 2 +- viewer/src/main.rs => agent/src/viewer/mod.rs | 74 +- {viewer/src => agent/src/viewer}/render.rs | 5 +- {viewer/src => agent/src/viewer}/transport.rs | 0 viewer/Cargo.toml | 66 -- viewer/build.rs | 9 - viewer/src/proto.rs | 4 - 12 files changed, 1463 insertions(+), 396 deletions(-) create mode 100644 agent/src/install.rs rename {viewer/src => agent/src/viewer}/input.rs (99%) rename viewer/src/main.rs => agent/src/viewer/mod.rs (67%) rename {viewer/src => agent/src/viewer}/render.rs (99%) rename {viewer/src => agent/src/viewer}/transport.rs (100%) delete mode 100644 viewer/Cargo.toml delete mode 100644 viewer/build.rs delete mode 100644 viewer/src/proto.rs diff --git a/Cargo.lock b/Cargo.lock index 45ffb2c..f127ccb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,30 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] @@ -286,7 +309,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -343,6 +366,15 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -366,7 +398,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -375,12 +407,43 @@ 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 = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.10.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "calloop" version = "0.13.0" @@ -425,6 +488,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -479,10 +552,10 @@ version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -612,6 +685,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -687,6 +769,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -711,7 +814,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -720,7 +823,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.9", ] [[package]] @@ -834,6 +937,25 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "find-msvc-tools" version = "0.1.5" @@ -900,7 +1022,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -976,7 +1098,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1008,6 +1130,64 @@ dependencies = [ "slab", ] +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1054,29 +1234,181 @@ dependencies = [ ] [[package]] -name = "guruconnect-agent" +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "guruconnect" version = "0.1.0" dependencies = [ "anyhow", "bytes", "chrono", + "clap", "futures-util", "hostname", + "image", + "muda", "prost", "prost-build", "prost-types", + "raw-window-handle", "ring", "serde", "serde_json", + "softbuffer", "thiserror 1.0.69", "tokio", "tokio-tungstenite", - "toml", + "toml 0.8.2", "tracing", "tracing-subscriber", + "tray-icon", + "url", + "urlencoding", "uuid", "windows", "windows-service", + "winit", + "winres", "zstd", ] @@ -1101,7 +1433,7 @@ dependencies = [ "sqlx", "thiserror 1.0.69", "tokio", - "toml", + "toml 0.8.2", "tower", "tower-http", "tracing", @@ -1109,33 +1441,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "guruconnect-viewer" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "clap", - "futures-util", - "prost", - "prost-build", - "prost-types", - "raw-window-handle", - "serde", - "serde_json", - "softbuffer", - "thiserror 1.0.69", - "tokio", - "tokio-tungstenite", - "tracing", - "tracing-subscriber", - "url", - "uuid", - "windows", - "winit", - "zstd", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -1162,6 +1467,12 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1432,6 +1743,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.0", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -1520,6 +1844,17 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1529,12 +1864,46 @@ dependencies = [ "spin", ] +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + [[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.9" @@ -1572,6 +1941,25 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1651,6 +2039,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1688,6 +2085,36 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "once_cell", + "png 0.17.16", + "thiserror 1.0.69", + "windows-sys 0.59.0", +] + [[package]] name = "multimap" version = "0.10.1" @@ -1828,10 +2255,10 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1866,7 +2293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", @@ -1875,6 +2302,18 @@ dependencies = [ "objc2-quartz-core 0.2.2", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -1882,7 +2321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -1894,7 +2333,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -1906,7 +2345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -1941,7 +2380,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -1953,7 +2392,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", "objc2-foundation 0.2.2", @@ -1972,7 +2411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2 0.5.2", @@ -1985,6 +2424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", "objc2 0.6.3", "objc2-core-foundation", ] @@ -2006,9 +2446,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -2019,7 +2459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2031,7 +2471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -2066,7 +2506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", @@ -2086,7 +2526,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2098,7 +2538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.10.0", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -2139,7 +2579,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -2160,6 +2600,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.49" @@ -2178,6 +2624,31 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.2.1" @@ -2270,7 +2741,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -2312,6 +2783,32 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.10.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -2357,7 +2854,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.111", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] @@ -2369,6 +2886,30 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -2394,7 +2935,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck", + "heck 0.5.0", "itertools", "log", "multimap", @@ -2404,7 +2945,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn", + "syn 2.0.111", "tempfile", ] @@ -2418,7 +2959,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -2430,6 +2971,15 @@ dependencies = [ "prost", ] +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + [[package]] name = "quick-xml" version = "0.37.5" @@ -2517,6 +3067,17 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + [[package]] name = "regex" version = "1.12.2" @@ -2580,6 +3141,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -2684,6 +3254,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2711,7 +3287,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3002,7 +3578,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.111", ] [[package]] @@ -3013,7 +3589,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -3025,7 +3601,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.111", "tokio", "url", ] @@ -3174,6 +3750,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.111" @@ -3199,9 +3785,28 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.23.0" @@ -3241,7 +3846,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3252,7 +3857,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3328,7 +3933,7 @@ checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" dependencies = [ "as-raw-xcb-connection", "ctor-lite", - "libloading", + "libloading 0.8.9", "pkg-config", "tracing", ] @@ -3383,7 +3988,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3436,21 +4041,30 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] @@ -3466,16 +4080,26 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime 0.6.11", - "toml_write", - "winnow", + "toml_datetime 0.6.3", + "winnow 0.5.40", ] [[package]] @@ -3487,7 +4111,7 @@ dependencies = [ "indexmap", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -3496,15 +4120,9 @@ version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "winnow", + "winnow 0.7.14", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tower" version = "0.5.2" @@ -3580,7 +4198,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -3622,6 +4240,27 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd75f5002e2513eaa19b2365f533090cc3e93abd38788452d9ea85cff7b48a" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "thiserror 2.0.17", + "windows-sys 0.59.0", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -3710,6 +4349,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -3752,6 +4397,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -3834,7 +4485,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -3992,6 +4643,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -4001,6 +4668,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.58.0" @@ -4045,7 +4718,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4056,7 +4729,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4067,7 +4740,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4078,7 +4751,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4162,6 +4835,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -4433,7 +5115,7 @@ dependencies = [ "android-activity", "atomic-waker", "bitflags 2.10.0", - "block2", + "block2 0.5.1", "bytemuck", "calloop", "cfg_aliases", @@ -4447,7 +5129,7 @@ dependencies = [ "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -4475,6 +5157,15 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.14" @@ -4484,6 +5175,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -4496,6 +5196,16 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -4516,7 +5226,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", + "libloading 0.8.9", "once_cell", "rustix 1.1.2", "x11rb-protocol", @@ -4572,7 +5282,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", "synstructure", ] @@ -4593,7 +5303,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -4613,7 +5323,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", "synstructure", ] @@ -4653,7 +5363,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d68553a..9a5462e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "agent", "server", - "viewer", ] [workspace.package] diff --git a/agent/Cargo.toml b/agent/Cargo.toml index ca24f6b..ed8cbf3 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,11 +1,14 @@ [package] -name = "guruconnect-agent" +name = "guruconnect" version = "0.1.0" edition = "2021" authors = ["AZ Computer Guru"] -description = "GuruConnect Remote Desktop Agent" +description = "GuruConnect Remote Desktop - Agent and Viewer" [dependencies] +# CLI +clap = { version = "4", features = ["derive"] } + # Async runtime tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] } @@ -13,6 +16,11 @@ tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", tokio-tungstenite = { version = "0.24", features = ["native-tls"] } futures-util = "0.3" +# Windowing (for viewer) +winit = { version = "0.30", features = ["rwh_06"] } +softbuffer = "0.4" +raw-window-handle = "0.6" + # Compression zstd = "0.13" @@ -58,8 +66,11 @@ muda = "0.15" # Menu for tray icon # Image handling for tray icon image = { version = "0.25", default-features = false, features = ["png"] } +# URL parsing +url = "2" + [target.'cfg(windows)'.dependencies] -# Windows APIs for screen capture and input +# Windows APIs for screen capture, input, and shell operations windows = { version = "0.58", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", @@ -69,10 +80,12 @@ windows = { version = "0.58", features = [ "Win32_Graphics_Direct3D11", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", + "Win32_UI_Shell", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_System_Registry", "Win32_System_Console", + "Win32_System_Environment", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Pipes", @@ -88,16 +101,9 @@ prost-build = "0.13" winres = "0.1" [[bin]] -name = "guruconnect-agent" +name = "guruconnect" path = "src/main.rs" [[bin]] name = "guruconnect-sas-service" path = "src/bin/sas_service.rs" - -[profile.release] -lto = true -codegen-units = 1 -opt-level = "z" -strip = true -panic = "abort" diff --git a/agent/src/install.rs b/agent/src/install.rs new file mode 100644 index 0000000..daf1a91 --- /dev/null +++ b/agent/src/install.rs @@ -0,0 +1,364 @@ +//! Installation and protocol handler registration +//! +//! Handles: +//! - Self-installation to Program Files (with UAC) or LocalAppData (fallback) +//! - Protocol handler registration (guruconnect://) +//! - UAC elevation with graceful fallback + +use anyhow::{anyhow, Result}; +use tracing::{info, warn, error}; + +#[cfg(windows)] +use windows::{ + core::PCWSTR, + Win32::Foundation::HANDLE, + Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY}, + Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}, + Win32::System::Registry::{ + RegCreateKeyExW, RegSetValueExW, RegCloseKey, HKEY, HKEY_CLASSES_ROOT, + HKEY_CURRENT_USER, KEY_WRITE, REG_SZ, REG_OPTION_NON_VOLATILE, + }, + Win32::UI::Shell::ShellExecuteW, + Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL, +}; + +#[cfg(windows)] +use std::ffi::OsStr; +#[cfg(windows)] +use std::os::windows::ffi::OsStrExt; + +/// Install locations +pub const SYSTEM_INSTALL_PATH: &str = r"C:\Program Files\GuruConnect"; +pub const USER_INSTALL_PATH: &str = r"GuruConnect"; // Relative to %LOCALAPPDATA% + +/// Check if running with elevated privileges +#[cfg(windows)] +pub fn is_elevated() -> bool { + unsafe { + let mut token_handle = HANDLE::default(); + if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() { + return false; + } + + let mut elevation = TOKEN_ELEVATION::default(); + let mut size = std::mem::size_of::() as u32; + + let result = GetTokenInformation( + token_handle, + TokenElevation, + Some(&mut elevation as *mut _ as *mut _), + size, + &mut size, + ); + + let _ = windows::Win32::Foundation::CloseHandle(token_handle); + + result.is_ok() && elevation.TokenIsElevated != 0 + } +} + +#[cfg(not(windows))] +pub fn is_elevated() -> bool { + unsafe { libc::geteuid() == 0 } +} + +/// Get the install path based on elevation status +pub fn get_install_path(elevated: bool) -> std::path::PathBuf { + if elevated { + std::path::PathBuf::from(SYSTEM_INSTALL_PATH) + } else { + let local_app_data = std::env::var("LOCALAPPDATA") + .unwrap_or_else(|_| { + let home = std::env::var("USERPROFILE").unwrap_or_else(|_| ".".to_string()); + format!(r"{}\AppData\Local", home) + }); + std::path::PathBuf::from(local_app_data).join(USER_INSTALL_PATH) + } +} + +/// Get the executable path +pub fn get_exe_path(install_path: &std::path::Path) -> std::path::PathBuf { + install_path.join("guruconnect.exe") +} + +/// Attempt to elevate and re-run with install command +#[cfg(windows)] +pub fn try_elevate_and_install() -> Result { + let exe_path = std::env::current_exe()?; + let exe_path_wide: Vec = OsStr::new(exe_path.as_os_str()) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + let verb: Vec = OsStr::new("runas") + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + let params: Vec = OsStr::new("install --elevated") + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + unsafe { + let result = ShellExecuteW( + None, + PCWSTR(verb.as_ptr()), + PCWSTR(exe_path_wide.as_ptr()), + PCWSTR(params.as_ptr()), + PCWSTR::null(), + SW_SHOWNORMAL, + ); + + // ShellExecuteW returns > 32 on success + if result.0 as usize > 32 { + info!("UAC elevation requested"); + Ok(true) + } else { + warn!("UAC elevation denied or failed"); + Ok(false) + } + } +} + +#[cfg(not(windows))] +pub fn try_elevate_and_install() -> Result { + Ok(false) +} + +/// Register the guruconnect:// protocol handler +#[cfg(windows)] +pub fn register_protocol_handler(elevated: bool) -> Result<()> { + let install_path = get_install_path(elevated); + let exe_path = get_exe_path(&install_path); + let exe_path_str = exe_path.to_string_lossy(); + + // Command to execute: "C:\...\guruconnect.exe" "launch" "%1" + let command = format!("\"{}\" launch \"%1\"", exe_path_str); + + // Choose registry root based on elevation + let root_key = if elevated { + HKEY_CLASSES_ROOT + } else { + // User-level registration under Software\Classes + HKEY_CURRENT_USER + }; + + let base_path = if elevated { + "guruconnect" + } else { + r"Software\Classes\guruconnect" + }; + + unsafe { + // Create guruconnect key + let mut protocol_key = HKEY::default(); + let key_path = to_wide(base_path); + let result = RegCreateKeyExW( + root_key, + PCWSTR(key_path.as_ptr()), + 0, + PCWSTR::null(), + REG_OPTION_NON_VOLATILE, + KEY_WRITE, + None, + &mut protocol_key, + None, + ); + if result.is_err() { + return Err(anyhow!("Failed to create protocol key: {:?}", result)); + } + + // Set default value (protocol description) + let description = to_wide("GuruConnect Protocol"); + let result = RegSetValueExW( + protocol_key, + PCWSTR::null(), + 0, + REG_SZ, + Some(&description_to_bytes(&description)), + ); + if result.is_err() { + let _ = RegCloseKey(protocol_key); + return Err(anyhow!("Failed to set protocol description: {:?}", result)); + } + + // Set URL Protocol (empty string indicates this is a protocol handler) + let url_protocol = to_wide("URL Protocol"); + let empty = to_wide(""); + let result = RegSetValueExW( + protocol_key, + PCWSTR(url_protocol.as_ptr()), + 0, + REG_SZ, + Some(&description_to_bytes(&empty)), + ); + if result.is_err() { + let _ = RegCloseKey(protocol_key); + return Err(anyhow!("Failed to set URL Protocol: {:?}", result)); + } + + let _ = RegCloseKey(protocol_key); + + // Create shell\open\command key + let command_path = if elevated { + r"guruconnect\shell\open\command" + } else { + r"Software\Classes\guruconnect\shell\open\command" + }; + let command_key_path = to_wide(command_path); + let mut command_key = HKEY::default(); + let result = RegCreateKeyExW( + root_key, + PCWSTR(command_key_path.as_ptr()), + 0, + PCWSTR::null(), + REG_OPTION_NON_VOLATILE, + KEY_WRITE, + None, + &mut command_key, + None, + ); + if result.is_err() { + return Err(anyhow!("Failed to create command key: {:?}", result)); + } + + // Set the command + let command_wide = to_wide(&command); + let result = RegSetValueExW( + command_key, + PCWSTR::null(), + 0, + REG_SZ, + Some(&description_to_bytes(&command_wide)), + ); + if result.is_err() { + let _ = RegCloseKey(command_key); + return Err(anyhow!("Failed to set command: {:?}", result)); + } + + let _ = RegCloseKey(command_key); + } + + info!("Protocol handler registered: guruconnect://"); + Ok(()) +} + +#[cfg(not(windows))] +pub fn register_protocol_handler(_elevated: bool) -> Result<()> { + warn!("Protocol handler registration not supported on this platform"); + Ok(()) +} + +/// Install the application +pub fn install(force_user_install: bool) -> Result<()> { + let elevated = is_elevated(); + + // If not elevated and not forcing user install, try to elevate + if !elevated && !force_user_install { + info!("Attempting UAC elevation for system-wide install..."); + match try_elevate_and_install() { + Ok(true) => { + // Elevation was requested, exit this instance + // The elevated instance will continue the install + info!("Elevated process started, exiting current instance"); + std::process::exit(0); + } + Ok(false) => { + info!("UAC denied, falling back to user install"); + } + Err(e) => { + warn!("Elevation failed: {}, falling back to user install", e); + } + } + } + + let install_path = get_install_path(elevated); + let exe_path = get_exe_path(&install_path); + + info!("Installing to: {}", install_path.display()); + + // Create install directory + std::fs::create_dir_all(&install_path)?; + + // Copy ourselves to install location + let current_exe = std::env::current_exe()?; + if current_exe != exe_path { + std::fs::copy(¤t_exe, &exe_path)?; + info!("Copied executable to: {}", exe_path.display()); + } + + // Register protocol handler + register_protocol_handler(elevated)?; + + info!("Installation complete!"); + if elevated { + info!("Installed system-wide to: {}", install_path.display()); + } else { + info!("Installed for current user to: {}", install_path.display()); + } + + Ok(()) +} + +/// Parse a guruconnect:// URL and extract session parameters +pub fn parse_protocol_url(url: &str) -> Result<(String, String, Option)> { + // Expected formats: + // guruconnect://view/SESSION_ID + // guruconnect://view/SESSION_ID?token=API_KEY + // guruconnect://connect/SESSION_ID?server=wss://...&token=API_KEY + + let url = url::Url::parse(url) + .map_err(|e| anyhow!("Invalid URL: {}", e))?; + + if url.scheme() != "guruconnect" { + return Err(anyhow!("Invalid scheme: expected guruconnect://")); + } + + let path = url.path().trim_start_matches('/'); + let parts: Vec<&str> = path.split('/').collect(); + + if parts.is_empty() { + return Err(anyhow!("Missing action in URL")); + } + + let action = parts[0]; + let session_id = parts.get(1).map(|s| s.to_string()) + .ok_or_else(|| anyhow!("Missing session ID"))?; + + // Extract query parameters + let mut server = None; + let mut token = None; + + for (key, value) in url.query_pairs() { + match key.as_ref() { + "server" => server = Some(value.to_string()), + "token" | "api_key" => token = Some(value.to_string()), + _ => {} + } + } + + // Default server if not specified + let server = server.unwrap_or_else(|| "wss://connect.azcomputerguru.com/ws/viewer".to_string()); + + match action { + "view" | "connect" => Ok((server, session_id, token)), + _ => Err(anyhow!("Unknown action: {}", action)), + } +} + +// Helper functions for Windows registry operations +#[cfg(windows)] +fn to_wide(s: &str) -> Vec { + OsStr::new(s) + .encode_wide() + .chain(std::iter::once(0)) + .collect() +} + +#[cfg(windows)] +fn description_to_bytes(wide: &[u16]) -> Vec { + wide.iter() + .flat_map(|w| w.to_le_bytes()) + .collect() +} diff --git a/agent/src/main.rs b/agent/src/main.rs index 4e9ae8a..980f445 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,12 +1,13 @@ -//! GuruConnect Agent - Remote Desktop Agent for Windows +//! GuruConnect - Remote Desktop Agent and Viewer //! -//! Provides screen capture, input injection, and remote control capabilities. +//! Single binary for both agent (receiving connections) and viewer (initiating connections). //! //! Usage: -//! guruconnect-agent.exe [support_code] -//! -//! If a support code is provided, the agent will connect using that code -//! for a one-time support session. +//! guruconnect agent - Run as background agent +//! guruconnect view - View a remote session +//! guruconnect install - Install and register protocol handler +//! guruconnect launch - Handle guruconnect:// URL +//! guruconnect [support_code] - Legacy: run agent with support code // Hide console window by default on Windows (release builds) #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] @@ -16,42 +17,227 @@ mod chat; mod config; mod encoder; mod input; +mod install; mod sas_client; mod session; mod startup; mod transport; mod tray; +mod viewer; pub mod proto { include!(concat!(env!("OUT_DIR"), "/guruconnect.rs")); } use anyhow::Result; +use clap::{Parser, Subcommand}; use tracing::{info, error, warn, Level}; use tracing_subscriber::FmtSubscriber; #[cfg(windows)] -use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION}; +use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_OK, MB_ICONINFORMATION, MB_ICONERROR}; #[cfg(windows)] use windows::core::PCWSTR; #[cfg(windows)] -use windows::Win32::Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY}; +use windows::Win32::System::Console::{AllocConsole, GetConsoleWindow}; #[cfg(windows)] -use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; -#[cfg(windows)] -use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow}; -#[cfg(windows)] -use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW, SW_HIDE}; +use windows::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_SHOW}; -/// Extract a 6-digit support code from the executable's filename. -/// Looks for patterns like "GuruConnect-123456.exe" or "123456.exe" +/// GuruConnect Remote Desktop +#[derive(Parser)] +#[command(name = "guruconnect")] +#[command(version, about = "Remote desktop agent and viewer")] +struct Cli { + #[command(subcommand)] + command: Option, + + /// Support code for legacy mode (runs agent with code) + #[arg(value_name = "SUPPORT_CODE")] + support_code: Option, + + /// Enable verbose logging + #[arg(short, long, global = true)] + verbose: bool, +} + +#[derive(Subcommand)] +enum Commands { + /// Run as background agent (receive remote connections) + Agent { + /// Support code for one-time session + #[arg(short, long)] + code: Option, + }, + + /// View a remote session (connect to an agent) + View { + /// Session ID to connect to + session_id: String, + + /// Server URL + #[arg(short, long, default_value = "wss://connect.azcomputerguru.com/ws/viewer")] + server: String, + + /// API key for authentication + #[arg(short, long, default_value = "")] + api_key: String, + }, + + /// Install GuruConnect and register protocol handler + Install { + /// Skip UAC elevation, install for current user only + #[arg(long)] + user_only: bool, + + /// Called internally when running elevated + #[arg(long, hide = true)] + elevated: bool, + }, + + /// Uninstall GuruConnect + Uninstall, + + /// Handle a guruconnect:// protocol URL + Launch { + /// The guruconnect:// URL to handle + url: String, + }, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + // Initialize logging + let level = if cli.verbose { Level::DEBUG } else { Level::INFO }; + FmtSubscriber::builder() + .with_max_level(level) + .with_target(true) + .with_thread_ids(true) + .init(); + + info!("GuruConnect v{}", env!("CARGO_PKG_VERSION")); + + match cli.command { + Some(Commands::Agent { code }) => { + run_agent_mode(code) + } + Some(Commands::View { session_id, server, api_key }) => { + run_viewer_mode(&server, &session_id, &api_key) + } + Some(Commands::Install { user_only, elevated }) => { + run_install(user_only || elevated) + } + Some(Commands::Uninstall) => { + run_uninstall() + } + Some(Commands::Launch { url }) => { + run_launch(&url) + } + None => { + // Legacy mode: if a support code was provided, run as agent + if let Some(code) = cli.support_code { + run_agent_mode(Some(code)) + } else { + // No args: check if we should auto-detect mode + // For now, default to agent mode + run_agent_mode(None) + } + } + } +} + +/// Run in agent mode (receive remote connections) +fn run_agent_mode(support_code: Option) -> Result<()> { + info!("Running in agent mode"); + + // Check elevation status + if install::is_elevated() { + info!("Running with elevated (administrator) privileges"); + } else { + info!("Running with standard user privileges"); + } + + // Also check for support code in filename (legacy compatibility) + let code = support_code.or_else(extract_code_from_filename); + if let Some(ref c) = code { + info!("Support code: {}", c); + } + + // Load configuration + let mut config = config::Config::load()?; + config.support_code = code; + info!("Server: {}", config.server_url); + + // Run the agent + let rt = tokio::runtime::Runtime::new()?; + rt.block_on(run_agent(config)) +} + +/// Run in viewer mode (connect to remote session) +fn run_viewer_mode(server: &str, session_id: &str, api_key: &str) -> Result<()> { + info!("Running in viewer mode"); + info!("Connecting to session: {}", session_id); + + let rt = tokio::runtime::Runtime::new()?; + rt.block_on(viewer::run(server, session_id, api_key)) +} + +/// Handle guruconnect:// URL launch +fn run_launch(url: &str) -> Result<()> { + info!("Handling protocol URL: {}", url); + + match install::parse_protocol_url(url) { + Ok((server, session_id, token)) => { + let api_key = token.unwrap_or_default(); + run_viewer_mode(&server, &session_id, &api_key) + } + Err(e) => { + error!("Failed to parse URL: {}", e); + show_error_box("GuruConnect", &format!("Invalid URL: {}", e)); + Err(e) + } + } +} + +/// Install GuruConnect +fn run_install(force_user_install: bool) -> Result<()> { + info!("Installing GuruConnect..."); + + match install::install(force_user_install) { + Ok(()) => { + show_message_box("GuruConnect", "Installation complete!\n\nYou can now use guruconnect:// links."); + Ok(()) + } + Err(e) => { + error!("Installation failed: {}", e); + show_error_box("GuruConnect", &format!("Installation failed: {}", e)); + Err(e) + } + } +} + +/// Uninstall GuruConnect +fn run_uninstall() -> Result<()> { + info!("Uninstalling GuruConnect..."); + + // Remove from startup + if let Err(e) = startup::remove_from_startup() { + warn!("Failed to remove from startup: {}", e); + } + + // TODO: Remove registry keys for protocol handler + // TODO: Remove install directory + + show_message_box("GuruConnect", "Uninstall complete."); + Ok(()) +} + +/// Extract a 6-digit support code from the executable's filename fn extract_code_from_filename() -> Option { - // Get the path to the current executable let exe_path = std::env::current_exe().ok()?; let filename = exe_path.file_stem()?.to_str()?; // Look for a 6-digit number in the filename - // Try common patterns: "Name-123456", "Name_123456", "123456" for part in filename.split(|c| c == '-' || c == '_' || c == '.') { let trimmed = part.trim(); if trimmed.len() == 6 && trimmed.chars().all(|c| c.is_ascii_digit()) { @@ -59,7 +245,7 @@ fn extract_code_from_filename() -> Option { } } - // Also check if the last 6 characters are digits (e.g., "GuruConnect123456") + // Check if the last 6 characters are digits if filename.len() >= 6 { let last_six = &filename[filename.len() - 6..]; if last_six.chars().all(|c| c.is_ascii_digit()) { @@ -70,45 +256,12 @@ fn extract_code_from_filename() -> Option { None } -/// Check if the process is running with elevated privileges (Windows only) -#[cfg(windows)] -fn is_elevated() -> bool { - unsafe { - let mut token_handle = windows::Win32::Foundation::HANDLE::default(); - if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle).is_err() { - return false; - } - - let mut elevation = TOKEN_ELEVATION::default(); - let mut size = std::mem::size_of::() as u32; - - let result = GetTokenInformation( - token_handle, - TokenElevation, - Some(&mut elevation as *mut _ as *mut _), - size, - &mut size, - ); - - let _ = windows::Win32::Foundation::CloseHandle(token_handle); - - result.is_ok() && elevation.TokenIsElevated != 0 - } -} - -#[cfg(not(windows))] -fn is_elevated() -> bool { - // On non-Windows, check if running as root - unsafe { libc::geteuid() == 0 } -} - -/// Show a message box to the user (Windows only) +/// Show a message box (Windows only) #[cfg(windows)] fn show_message_box(title: &str, message: &str) { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; - // Convert strings to wide strings for Windows API let title_wide: Vec = OsStr::new(title) .encode_wide() .chain(std::iter::once(0)) @@ -129,74 +282,59 @@ fn show_message_box(title: &str, message: &str) { } #[cfg(not(windows))] -fn show_message_box(_title: &str, _message: &str) { - // No-op on non-Windows platforms +fn show_message_box(_title: &str, message: &str) { + println!("{}", message); } -/// Show the debug console window (Windows only) +/// Show an error message box (Windows only) #[cfg(windows)] +fn show_error_box(title: &str, message: &str) { + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + + let title_wide: Vec = OsStr::new(title) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let message_wide: Vec = OsStr::new(message) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + unsafe { + MessageBoxW( + None, + PCWSTR(message_wide.as_ptr()), + PCWSTR(title_wide.as_ptr()), + MB_OK | MB_ICONERROR, + ); + } +} + +#[cfg(not(windows))] +fn show_error_box(_title: &str, message: &str) { + eprintln!("ERROR: {}", message); +} + +/// Show debug console window (Windows only) +#[cfg(windows)] +#[allow(dead_code)] fn show_debug_console() { unsafe { - // Check if we already have a console let hwnd = GetConsoleWindow(); if hwnd.0 == std::ptr::null_mut() { - // No console, allocate one let _ = AllocConsole(); - info!("Debug console window opened"); } else { - // Console exists, make sure it's visible let _ = ShowWindow(hwnd, SW_SHOW); - info!("Debug console window shown"); } } } #[cfg(not(windows))] -fn show_debug_console() { - // No-op on non-Windows platforms -} +#[allow(dead_code)] +fn show_debug_console() {} -#[tokio::main] -async fn main() -> Result<()> { - // Initialize logging - let subscriber = FmtSubscriber::builder() - .with_max_level(Level::INFO) - .with_target(true) - .with_thread_ids(true) - .init(); - - info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION")); - - // Check and log elevation status - if is_elevated() { - info!("Running with elevated (administrator) privileges"); - } else { - info!("Running with standard user privileges"); - } - - // Extract support code from executable filename - // e.g., GuruConnect-123456.exe -> 123456 - let support_code = extract_code_from_filename(); - if let Some(ref code) = support_code { - info!("Support code from filename: {}", code); - } - - // Load configuration - let mut config = config::Config::load()?; - config.support_code = support_code; - info!("Loaded configuration for server: {}", config.server_url); - - // Run the agent - if let Err(e) = run_agent(config).await { - error!("Agent error: {}", e); - return Err(e); - } - - Ok(()) -} - -/// Clean up before exiting (remove from startup, etc.) -/// Called when user explicitly ends session or support session completes +/// Clean up before exiting fn cleanup_on_exit() { info!("Cleaning up before exit"); if let Err(e) = startup::remove_from_startup() { @@ -204,44 +342,37 @@ fn cleanup_on_exit() { } } +/// Run the agent main loop async fn run_agent(config: config::Config) -> Result<()> { - // Create session manager with elevation status - let elevated = is_elevated(); + let elevated = install::is_elevated(); let mut session = session::SessionManager::new(config.clone(), elevated); let is_support_session = config.support_code.is_some(); let hostname = config.hostname(); - // Add to startup so we reconnect after reboot - // Persistent agents (no support code) should ALWAYS be in startup - // Support sessions only need startup temporarily while active + // Add to startup if let Err(e) = startup::add_to_startup() { - warn!("Failed to add to startup: {}. Agent won't persist through reboot.", e); + warn!("Failed to add to startup: {}", e); } // Create tray icon - // Only support sessions can be ended by user - persistent agents are admin-managed let tray = match tray::TrayController::new(&hostname, config.support_code.as_deref(), is_support_session) { Ok(t) => { info!("Tray icon created"); Some(t) } Err(e) => { - warn!("Failed to create tray icon: {}. Continuing without tray.", e); + warn!("Failed to create tray icon: {}", e); None } }; // Create chat controller let chat_ctrl = chat::ChatController::new(); - if chat_ctrl.is_some() { - info!("Chat controller created"); - } // Connect to server and run main loop loop { info!("Connecting to server..."); - // Check if user requested exit via tray before connecting (support sessions only) if is_support_session { if let Some(ref t) = tray { if t.exit_requested() { @@ -256,44 +387,32 @@ async fn run_agent(config: config::Config) -> Result<()> { Ok(_) => { info!("Connected to server"); - // Update tray status if let Some(ref t) = tray { t.update_status("Status: Connected"); } - // Run session until disconnect, passing tray and chat for event processing if let Err(e) = session.run_with_tray(tray.as_ref(), chat_ctrl.as_ref()).await { let error_msg = e.to_string(); - // Check if this is a user-initiated exit if error_msg.contains("USER_EXIT") { info!("Session ended by user"); cleanup_on_exit(); return Ok(()); } - // Check if this is a cancellation if error_msg.contains("SESSION_CANCELLED") { info!("Session was cancelled by technician"); cleanup_on_exit(); - show_message_box( - "Support Session Ended", - "The support session was cancelled by the technician.\n\nThis window will close automatically.", - ); - // Exit cleanly without reconnecting + show_message_box("Support Session Ended", "The support session was cancelled."); return Ok(()); } - // Check if this is an admin disconnect (uninstall) if error_msg.contains("ADMIN_DISCONNECT") { - info!("Session was disconnected by administrator - uninstalling"); + info!("Session disconnected by administrator - uninstalling"); if let Err(e) = startup::uninstall() { warn!("Uninstall failed: {}", e); } - show_message_box( - "Remote Session Ended", - "The remote support session has been ended by the administrator.\n\nThe agent will be removed from this computer.", - ); + show_message_box("Remote Session Ended", "The session was ended by the administrator."); return Ok(()); } @@ -303,15 +422,10 @@ async fn run_agent(config: config::Config) -> Result<()> { Err(e) => { let error_msg = e.to_string(); - // Check if connection was rejected due to cancelled code if error_msg.contains("cancelled") { - info!("Support code was cancelled before connection"); + info!("Support code was cancelled"); cleanup_on_exit(); - show_message_box( - "Support Session Cancelled", - "This support session has been cancelled.\n\nPlease contact your technician for a new support code.", - ); - // Exit cleanly without reconnecting + show_message_box("Support Session Cancelled", "This support session has been cancelled."); return Ok(()); } @@ -319,14 +433,12 @@ async fn run_agent(config: config::Config) -> Result<()> { } } - // For support sessions, don't reconnect if something goes wrong if is_support_session { info!("Support session ended, not reconnecting"); cleanup_on_exit(); return Ok(()); } - // Wait before reconnecting (persistent agents only - support sessions already exited above) info!("Reconnecting in 5 seconds..."); tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; } diff --git a/viewer/src/input.rs b/agent/src/viewer/input.rs similarity index 99% rename from viewer/src/input.rs rename to agent/src/viewer/input.rs index c080d29..553c951 100644 --- a/viewer/src/input.rs +++ b/agent/src/viewer/input.rs @@ -1,6 +1,6 @@ //! Low-level keyboard hook for capturing all keys including Win key -use crate::InputEvent; +use super::InputEvent; #[cfg(windows)] use crate::proto; use anyhow::Result; diff --git a/viewer/src/main.rs b/agent/src/viewer/mod.rs similarity index 67% rename from viewer/src/main.rs rename to agent/src/viewer/mod.rs index de236d8..44315e1 100644 --- a/viewer/src/main.rs +++ b/agent/src/viewer/mod.rs @@ -1,42 +1,17 @@ -//! GuruConnect Native Viewer +//! Viewer module - Native remote desktop viewer with full keyboard capture //! -//! Native remote desktop viewer with full keyboard capture including Win key. +//! This module provides the viewer functionality for connecting to remote +//! GuruConnect sessions with low-level keyboard hooks for Win key capture. -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -mod proto; -mod transport; -mod render; mod input; +mod render; +mod transport; +use crate::proto; use anyhow::Result; -use clap::Parser; use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; -use tracing::{info, error, warn, Level}; -use tracing_subscriber::FmtSubscriber; - -/// GuruConnect Native Viewer -#[derive(Parser, Debug)] -#[command(name = "guruconnect-viewer")] -#[command(about = "Native remote desktop viewer with full keyboard capture")] -struct Args { - /// Server URL (e.g., wss://connect.azcomputerguru.com/ws/viewer) - #[arg(short, long, default_value = "wss://connect.azcomputerguru.com/ws/viewer")] - server: String, - - /// Session ID to connect to - #[arg(short = 'i', long)] - session_id: String, - - /// API key for authentication - #[arg(short, long, default_value = "dev-key")] - api_key: String, - - /// Enable verbose logging - #[arg(short, long)] - verbose: bool, -} +use tracing::{info, error, warn}; #[derive(Debug, Clone)] pub enum ViewerEvent { @@ -54,43 +29,21 @@ pub enum InputEvent { SpecialKey(proto::SpecialKeyEvent), } -fn main() -> Result<()> { - let args = Args::parse(); - - // Initialize logging - let level = if args.verbose { Level::DEBUG } else { Level::INFO }; - FmtSubscriber::builder() - .with_max_level(level) - .with_target(false) - .init(); - +/// Run the viewer to connect to a remote session +pub async fn run(server_url: &str, session_id: &str, api_key: &str) -> Result<()> { info!("GuruConnect Viewer starting"); - info!("Server: {}", args.server); - info!("Session: {}", args.session_id); + info!("Server: {}", server_url); + info!("Session: {}", session_id); // Create channels for communication between components let (viewer_tx, viewer_rx) = mpsc::channel::(100); let (input_tx, input_rx) = mpsc::channel::(100); - // Run the viewer - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { - run_viewer(args, viewer_tx, viewer_rx, input_tx, input_rx).await - }) -} - -async fn run_viewer( - args: Args, - viewer_tx: mpsc::Sender, - viewer_rx: mpsc::Receiver, - input_tx: mpsc::Sender, - mut input_rx: mpsc::Receiver, -) -> Result<()> { // Connect to server - let ws_url = format!("{}?session_id={}", args.server, args.session_id); + let ws_url = format!("{}?session_id={}", server_url, session_id); info!("Connecting to {}", ws_url); - let (ws_sender, mut ws_receiver) = transport::connect(&ws_url, &args.api_key).await?; + let (ws_sender, mut ws_receiver) = transport::connect(&ws_url, api_key).await?; let ws_sender = Arc::new(Mutex::new(ws_sender)); info!("Connected to server"); @@ -100,6 +53,7 @@ async fn run_viewer( let ws_sender_input = ws_sender.clone(); // Spawn task to forward input events to server + let mut input_rx = input_rx; let input_task = tokio::spawn(async move { while let Some(event) = input_rx.recv().await { let msg = match event { diff --git a/viewer/src/render.rs b/agent/src/viewer/render.rs similarity index 99% rename from viewer/src/render.rs rename to agent/src/viewer/render.rs index 28a8a56..eee9a63 100644 --- a/viewer/src/render.rs +++ b/agent/src/viewer/render.rs @@ -1,8 +1,9 @@ //! Window rendering and frame display -use crate::{ViewerEvent, InputEvent, proto}; +use super::{ViewerEvent, InputEvent}; +use crate::proto; #[cfg(windows)] -use crate::input; +use super::input; use anyhow::Result; use std::num::NonZeroU32; use std::sync::Arc; diff --git a/viewer/src/transport.rs b/agent/src/viewer/transport.rs similarity index 100% rename from viewer/src/transport.rs rename to agent/src/viewer/transport.rs diff --git a/viewer/Cargo.toml b/viewer/Cargo.toml deleted file mode 100644 index 14d163a..0000000 --- a/viewer/Cargo.toml +++ /dev/null @@ -1,66 +0,0 @@ -[package] -name = "guruconnect-viewer" -version = "0.1.0" -edition = "2021" -authors = ["AZ Computer Guru"] -description = "GuruConnect Native Remote Desktop Viewer" - -[dependencies] -# Async runtime -tokio = { version = "1", features = ["full", "sync", "time", "rt-multi-thread", "macros"] } - -# WebSocket -tokio-tungstenite = { version = "0.24", features = ["native-tls"] } -futures-util = "0.3" -url = "2" - -# Windowing -winit = { version = "0.30", features = ["rwh_06"] } -softbuffer = "0.4" -raw-window-handle = "0.6" - -# Compression -zstd = "0.13" - -# Protocol (protobuf) -prost = "0.13" -prost-types = "0.13" -bytes = "1" - -# Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Error handling -anyhow = "1" -thiserror = "1" - -# UUID -uuid = { version = "1", features = ["v4", "serde"] } - -# CLI args -clap = { version = "4", features = ["derive"] } - -[target.'cfg(windows)'.dependencies] -# Windows APIs for low-level keyboard hooks -windows = { version = "0.58", features = [ - "Win32_Foundation", - "Win32_UI_WindowsAndMessaging", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_System_LibraryLoader", - "Win32_System_Threading", -]} - -[build-dependencies] -prost-build = "0.13" - -[profile.release] -lto = true -codegen-units = 1 -opt-level = "z" -strip = true -panic = "abort" diff --git a/viewer/build.rs b/viewer/build.rs deleted file mode 100644 index 5580f9e..0000000 --- a/viewer/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::io::Result; - -fn main() -> Result<()> { - println!("cargo:rerun-if-changed=../proto/guruconnect.proto"); - - prost_build::compile_protos(&["../proto/guruconnect.proto"], &["../proto/"])?; - - Ok(()) -} diff --git a/viewer/src/proto.rs b/viewer/src/proto.rs deleted file mode 100644 index 2310784..0000000 --- a/viewer/src/proto.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Protocol buffer definitions - -// Include generated protobuf code -include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));