diff --git a/Cargo.lock b/Cargo.lock index 86f06a1..65c15fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,29 +414,6 @@ dependencies = [ "syn 3.0.4", ] -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib 0.18.5", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -672,31 +649,6 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.13.1", - "cairo-sys-rs", - "glib 0.18.5", - "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 0.18.1", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "calloop" version = "0.13.0" @@ -769,16 +721,6 @@ dependencies = [ "nom 7.1.3", ] -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon 0.12.16", -] - [[package]] name = "cfg-expr" version = "0.20.9" @@ -786,7 +728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917" dependencies = [ "smallvec", - "target-lexicon 0.13.5", + "target-lexicon", ] [[package]] @@ -1372,16 +1314,6 @@ dependencies = [ "vcpkg", ] -[[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.11" @@ -1441,32 +1373,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", -] - [[package]] name = "futures-core" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.34" @@ -1525,64 +1437,6 @@ 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 0.18.5", - "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 0.18.5", - "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 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - -[[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 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.2.2", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1637,51 +1491,6 @@ dependencies = [ "r-efi 6.0.0", ] -[[package]] -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 0.18.1", - "glib 0.18.5", - "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 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", - "winapi", -] - -[[package]] -name = "gio-sys" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28739f914c15b87a9856000bed9cbd5b3a8afbca5e982d9a299e1601422cb5" -dependencies = [ - "glib-sys 0.22.9", - "gobject-sys 0.22.9", - "libc", - "system-deps 9.0.0", - "windows-sys 0.61.2", -] - [[package]] name = "gl_generator" version = "0.14.0" @@ -1693,96 +1502,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.13.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys 0.18.1", - "glib-macros 0.18.5", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18b9b8d350db41f690ec1b87109f202782748bbd8ab2f2ede17cb40d29e2838c" -dependencies = [ - "bitflags 2.13.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys 0.22.9", - "glib-macros 0.22.9", - "glib-sys 0.22.9", - "gobject-sys 0.22.9", - "libc", - "memchr", - "smallvec", -] - -[[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.119", -] - -[[package]] -name = "glib-macros" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9597c68fa7bdf4154a3079642cd21c4dccac0b0bdd2897d82861523bf91e7b9" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 3.0.4", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "glib-sys" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b38907e67e40dec9b60f858bcada952598719cb512a13eb13d6cdcd16f8295" -dependencies = [ - "libc", - "system-deps 9.0.0", -] - [[package]] name = "glob" version = "0.3.4" @@ -1884,28 +1603,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys 0.18.1", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gobject-sys" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07595c9ba696bd9819cb6a8df39f08078f3dd679508fe052dd558492c2053834" -dependencies = [ - "glib-sys 0.22.9", - "libc", - "system-deps 9.0.0", -] - [[package]] name = "gpu-alloc" version = "0.6.2" @@ -1945,86 +1642,6 @@ dependencies = [ "bitflags 2.13.1", ] -[[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 0.18.5", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-layer-shell" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98" -dependencies = [ - "bitflags 2.13.1", - "gdk", - "glib 0.18.5", - "glib-sys 0.18.1", - "gtk", - "gtk-layer-shell-sys", - "libc", -] - -[[package]] -name = "gtk-layer-shell-sys" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eee067e022416d53a70de69d3d3929d8a6e687f3278b8934faa671750fa6eb" -dependencies = [ - "gdk-sys", - "glib-sys 0.18.1", - "gtk-sys", - "libc", - "system-deps 7.0.8", -] - -[[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 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pango-sys", - "system-deps 6.2.2", -] - -[[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.119", -] - [[package]] name = "half" version = "2.7.1" @@ -2051,12 +1668,6 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -[[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" @@ -2381,7 +1992,7 @@ dependencies = [ "libspa-sys", "nom 8.0.0", "rustix 1.1.4", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -2392,7 +2003,7 @@ checksum = "3b6e17bdaf63ed0d5e4144022624032b41fd9733112e8c74ac26fc9bf1291924" dependencies = [ "bindgen", "cc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -2734,7 +2345,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -3123,31 +2734,6 @@ 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 0.18.5", - "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 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "parking" version = "2.2.1" @@ -3248,7 +2834,7 @@ checksum = "9ce653f53e63e5b93853218092ee9a8906a5d082c92f3f1db26316955dd63ce0" dependencies = [ "bindgen", "libspa-sys", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -3332,57 +2918,13 @@ dependencies = [ "num-integer", ] -[[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]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.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", + "toml_edit", ] [[package]] @@ -3700,7 +3242,7 @@ dependencies = [ [[package]] name = "scythe" -version = "0.2.17" +version = "0.2.18" dependencies = [ "ashpd", "cpal", @@ -3710,11 +3252,7 @@ dependencies = [ "egui", "ffmpeg-next", "ffmpeg-sys-next", - "gdk", - "glib 0.22.9", "global-hotkey", - "gtk", - "gtk-layer-shell", "image", "libc", "memmap2", @@ -3789,15 +3327,6 @@ dependencies = [ "syn 3.0.4", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.1.1" @@ -4013,23 +3542,13 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", "syn 2.0.119", ] -[[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.119" @@ -4063,51 +3582,19 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr 0.15.8", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - [[package]] name = "system-deps" version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ - "cfg-expr 0.20.9", - "heck 0.5.0", + "cfg-expr", + "heck", "pkg-config", - "toml 1.1.4+spec-1.1.0", + "toml", "version-compare", ] -[[package]] -name = "system-deps" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0dae2cbca1f0ff93795713cfe0a4c02f760e3c0b8ae2ab4ec4045808ff429f" -dependencies = [ - "cfg-expr 0.20.9", - "heck 0.5.0", - "pkg-config", - "toml 1.1.4+spec-1.1.0", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "target-lexicon" version = "0.13.5" @@ -4253,18 +3740,6 @@ dependencies = [ "syn 3.0.4", ] -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - [[package]] name = "toml" version = "1.1.4+spec-1.1.0" @@ -4273,20 +3748,11 @@ checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", + "winnow", ] [[package]] @@ -4298,30 +3764,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -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 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.25.13+spec-1.1.0" @@ -4329,9 +3771,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", - "winnow 1.0.4", + "winnow", ] [[package]] @@ -4340,7 +3782,7 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.4", + "winnow", ] [[package]] @@ -5228,15 +4670,6 @@ 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 = "1.0.4" @@ -5427,7 +4860,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.4", + "winnow", "zbus_macros 5.19.0", "zbus_names 4.3.4", "zvariant 5.15.0", @@ -5463,7 +4896,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -5476,7 +4909,7 @@ version = "5.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.4", @@ -5503,7 +4936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "winnow 1.0.4", + "winnow", "zvariant 5.15.0", ] @@ -5652,7 +5085,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.4", + "winnow", "zcheapstr", "zvariant_derive 5.15.0", "zvariant_utils 4.2.0", @@ -5664,7 +5097,7 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -5677,7 +5110,7 @@ version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.4", @@ -5705,5 +5138,5 @@ dependencies = [ "quote", "serde", "syn 3.0.4", - "winnow 1.0.4", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 80ebb7a..6dd3e58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scythe" -version = "0.2.17" +version = "0.2.18" edition = "2024" [[bin]] @@ -15,10 +15,7 @@ path = "src/bin/scythe-ui.rs" crossbeam-channel = "0.5.16" ffmpeg-next = "9.0.0" ffmpeg-sys-next = "9.0.0" -gdk = "0.18.2" -glib = "0.22.9" global-hotkey = "0.8.0" -gtk = { version = "0.18.2", package = "gtk" } libc = "0.2.189" mp4 = "0.14.0" ringbuf = "0.5.1" @@ -34,11 +31,17 @@ image = { version = "0.25", default-features = false, features = ["png"] } [target.'cfg(target_os = "linux")'.dependencies] ashpd = { version = "0.13.13", features = ["screencast"] } -gtk-layer-shell = { version = "0.8.2", features = ["v0_5"] } pipewire = "0.10.1" xcb = { version = "1.7.1", features = ["shm", "xfixes"] } memmap2 = "0.9.11" +[profile.release] +opt-level = 3 +lto = "thin" +codegen-units = 4 +panic = "abort" +strip = true + [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = [ "Win32_Foundation", diff --git a/install.sh b/install.sh index 31f021e..c9449b4 100755 --- a/install.sh +++ b/install.sh @@ -26,6 +26,27 @@ ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec-ui" ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec" SYSTEMD_USER_DIR="$HOME/.config/systemd/user" +mkdir -p "$SYSTEMD_USER_DIR" +cat > "$SYSTEMD_USER_DIR/scythe-daemon.service" << EOF +[Unit] +Description=Scythe Screen Recorder Background Engine +After=graphical-session.target xdg-desktop-portal.service xdg-desktop-portal-hyprland.service +Wants=xdg-desktop-portal.service + +[Service] +Type=simple +ExecStart=$BIN_DIR/scythe-daemon +Restart=always +RestartSec=2 +Environment="XDG_RUNTIME_DIR=/run/user/1000" +Environment="XDG_CURRENT_DESKTOP=Hyprland" +Environment="XDG_SESSION_TYPE=wayland" + +[Install] +WantedBy=default.target +EOF +systemctl --user daemon-reload 2>/dev/null || true + if [ -f "$SYSTEMD_USER_DIR/vrec-daemon.service" ] || systemctl --user is-active --quiet vrec-daemon.service 2>/dev/null; then echo "Migrating legacy vrec-daemon systemd service to scythe-daemon.service..." systemctl --user stop vrec-daemon.service 2>/dev/null || true diff --git a/src/bin/scythe-daemon.rs b/src/bin/scythe-daemon.rs index 5262cab..aad3631 100644 --- a/src/bin/scythe-daemon.rs +++ b/src/bin/scythe-daemon.rs @@ -381,11 +381,17 @@ async fn main() -> Result<(), Box> { } } + let is_mock_cli = std::env::args().any(|a| a == "--mock"); let mut source: Box = match source_opt { Some(s) => s, None => { - eprintln!("Warning: Hardware capture unavailable; falling back to mock capture to preserve daemon IPC."); - Box::new(scythe::capture::mock::MockCapture::new()) + if is_mock_cli { + eprintln!("Warning: Mock capture mode active via --mock flag."); + Box::new(scythe::capture::mock::MockCapture::new()) + } else { + eprintln!("Error: Hardware capture failed to initialize on {} session. Exiting for restart.", session_type); + std::process::exit(1); + } } }; diff --git a/src/bin/scythe-ui.rs b/src/bin/scythe-ui.rs index 5317670..4c7dba9 100644 --- a/src/bin/scythe-ui.rs +++ b/src/bin/scythe-ui.rs @@ -120,8 +120,8 @@ fn check_and_toggle_overlay() -> bool { } } #[cfg(windows)] - if let Ok(pid) = content.trim().parse::() { - if pid != std::process::id() { + if let Ok(pid) = content.trim().parse::() + && pid != std::process::id() { unsafe { use windows::Win32::UI::WindowsAndMessaging::{ EnumWindows, GetWindowThreadProcessId, PostMessageW, WM_CLOSE, @@ -132,13 +132,15 @@ fn check_and_toggle_overlay() -> bool { }; unsafe extern "system" fn close_proc(hwnd: HWND, lparam: LPARAM) -> BOOL { - let target = lparam.0 as u32; - let mut pid = 0u32; - GetWindowThreadProcessId(hwnd, Some(&mut pid)); - if pid == target { - let _ = PostMessageW(hwnd, WM_CLOSE, None, None); + unsafe { + let target = lparam.0 as u32; + let mut pid = 0u32; + GetWindowThreadProcessId(hwnd, Some(&mut pid)); + if pid == target { + let _ = PostMessageW(hwnd, WM_CLOSE, None, None); + } + BOOL(1) } - BOOL(1) } let _ = EnumWindows(Some(close_proc), LPARAM(pid as isize)); @@ -154,7 +156,6 @@ fn check_and_toggle_overlay() -> bool { let _ = std::fs::remove_file(&pid_path); return true; } - } } } let _ = std::fs::remove_file(&pid_path); @@ -480,11 +481,11 @@ fn main() -> Result<(), Box> { return Ok(()); } "--gtk" => { - ensure_daemon_running(); + ensure_daemon_running_async(); ensure_hotkeys_running(); - #[cfg(target_os = "linux")] - scythe::overlay::show_menu_overlay(); - #[cfg(not(target_os = "linux"))] + if check_and_toggle_overlay() { + return Ok(()); + } scythe::overlay_egui::run_egui_overlay(); return Ok(()); } @@ -767,7 +768,7 @@ fn main() -> Result<(), Box> { use windows::Win32::UI::WindowsAndMessaging::{ MsgWaitForMultipleObjectsEx, QS_ALLINPUT, MWMO_INPUTAVAILABLE, }; - let _ = MsgWaitForMultipleObjectsEx(None, 200, QS_ALLINPUT.0, MWMO_INPUTAVAILABLE); + let _ = MsgWaitForMultipleObjectsEx(None, 200, QS_ALLINPUT, MWMO_INPUTAVAILABLE); } #[cfg(not(target_os = "windows"))] std::thread::sleep(std::time::Duration::from_millis(30)); diff --git a/src/capture/windows.rs b/src/capture/windows.rs index 1a37d5e..ca63a8a 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -55,20 +55,20 @@ impl WindowsCapture { 'adapter_loop: while let Ok(adapter) = factory.EnumAdapters1(a_idx) { if let Ok(desc1) = adapter.GetDesc1() { // Skip software rasterizers (e.g. Microsoft Basic Render Driver) when hardware GPU exists - if (desc1.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0) != 0 { + if (desc1.Flags & (DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32)) != 0 { a_idx += 1; continue; } } let mut o_idx = 0; while let Ok(output) = adapter.EnumOutputs(o_idx) { - if let Ok(output1) = output.cast::() { - if let Ok(desc) = output1.GetDesc() { + if let Ok(output1) = output.cast::() + && let Ok(desc) = output1.GetDesc() { let is_attached = desc.AttachedToDesktop.as_bool(); let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left).unsigned_abs(); let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top).unsigned_abs(); - if is_attached && width > 0 && height > 0 { - if let Ok(adapter_base) = adapter.cast::() { + if is_attached && width > 0 && height > 0 + && let Ok(adapter_base) = adapter.cast::() { let mut device: Option = None; let mut context: Option = None; let mut feature_level = D3D_FEATURE_LEVEL_11_0; @@ -90,8 +90,8 @@ impl WindowsCapture { Some(&mut context), ); - if create_res.is_ok() { - if let (Some(dev), Some(ctx)) = (device, context) { + if create_res.is_ok() + && let (Some(dev), Some(ctx)) = (device, context) { match output1.DuplicateOutput(&dev) { Ok(dup) => { successful_session = Some((dev, ctx, dup, width, height)); @@ -101,11 +101,8 @@ impl WindowsCapture { eprintln!("Adapter {} Output {} DuplicateOutput failed: {:?}", a_idx, o_idx, e); } } - } } - } } - } } o_idx += 1; } @@ -178,7 +175,7 @@ impl FrameSource for WindowsCapture { let _ = self.reinit_duplication(); } - let mut dup = match self.duplication.clone() { + let dup = match self.duplication.clone() { Some(d) => d, None => { return Ok(Frame::Raw { diff --git a/src/encoder.rs b/src/encoder.rs index 8ac84d5..cd97cb8 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,5 +1,6 @@ use ffmpeg_next::ffi::*; use std::ptr; +#[cfg(unix)] use std::collections::HashMap; use crate::capture::Frame; @@ -294,9 +295,11 @@ impl VaapiEncoder { self.has_frame = true; self.send_staged_frame(&mut packets, pts)?; } + #[allow(unused_variables)] Frame::DmaBuf { width: dma_width, height: dma_height, format, modifier, fd, stride, offset } => { #[allow(unused_variables)] let mmap_size = (*offset as usize) + (*stride as usize) * (*dma_height as usize); + #[allow(unused_mut)] let mut mapped_direct = false; #[cfg(unix)] @@ -465,6 +468,7 @@ impl Drop for VaapiEncoder { #[cfg(target_os = "windows")] pub struct WindowsHwEncoder { codec_ctx: *mut AVCodecContext, + #[allow(dead_code)] encoder_name: String, sws_ctx: *mut SwsContext, sw_frame: *mut AVFrame, @@ -697,46 +701,43 @@ impl WindowsHwEncoder { pub fn encode_frame(&mut self, frame: &Frame, pts: i64) -> Result, String> { let mut packets = Vec::new(); unsafe { - match frame { - Frame::Raw { width: _, height, stride, data } => { - if av_frame_make_writable(self.sw_frame) < 0 { - let _ = av_frame_get_buffer(self.sw_frame, 32); - } - - if !self.sws_ctx.is_null() { - let src_data = [data.as_ptr(), ptr::null(), ptr::null(), ptr::null()]; - let src_linesize = [*stride as i32, 0, 0, 0]; - sws_scale( - self.sws_ctx, - src_data.as_ptr(), - src_linesize.as_ptr(), - 0, - *height as i32, - (*self.sw_frame).data.as_mut_ptr(), - (*self.sw_frame).linesize.as_mut_ptr(), - ); - } - - (*self.sw_frame).pts = pts; - if self.force_idr { - (*self.sw_frame).pict_type = AVPictureType::AV_PICTURE_TYPE_I; - self.force_idr = false; - } else { - (*self.sw_frame).pict_type = AVPictureType::AV_PICTURE_TYPE_NONE; - } - - if avcodec_send_frame(self.codec_ctx, self.sw_frame) >= 0 { - let mut pkt = av_packet_alloc(); - while avcodec_receive_packet(self.codec_ctx, pkt) >= 0 { - let new_pkt = av_packet_alloc(); - av_packet_move_ref(new_pkt, pkt); - packets.push(crate::ring::Packet::new(new_pkt)); - } - av_packet_free(&mut pkt); - self.has_frame = true; - } + if let Frame::Raw { width: _, height, stride, data } = frame { + if av_frame_make_writable(self.sw_frame) < 0 { + let _ = av_frame_get_buffer(self.sw_frame, 32); + } + + if !self.sws_ctx.is_null() { + let src_data = [data.as_ptr(), ptr::null(), ptr::null(), ptr::null()]; + let src_linesize = [*stride as i32, 0, 0, 0]; + sws_scale( + self.sws_ctx, + src_data.as_ptr(), + src_linesize.as_ptr(), + 0, + *height as i32, + (*self.sw_frame).data.as_mut_ptr(), + (*self.sw_frame).linesize.as_mut_ptr(), + ); + } + + (*self.sw_frame).pts = pts; + if self.force_idr { + (*self.sw_frame).pict_type = AVPictureType::AV_PICTURE_TYPE_I; + self.force_idr = false; + } else { + (*self.sw_frame).pict_type = AVPictureType::AV_PICTURE_TYPE_NONE; + } + + if avcodec_send_frame(self.codec_ctx, self.sw_frame) >= 0 { + let mut pkt = av_packet_alloc(); + while avcodec_receive_packet(self.codec_ctx, pkt) >= 0 { + let new_pkt = av_packet_alloc(); + av_packet_move_ref(new_pkt, pkt); + packets.push(crate::ring::Packet::new(new_pkt)); + } + av_packet_free(&mut pkt); + self.has_frame = true; } - _ => {} } } Ok(packets) diff --git a/src/ipc.rs b/src/ipc.rs index a9d416a..0d2a3c9 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,5 +1,30 @@ use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ToastIcon { + Info, + Record, + Save, + Replay, + Cursor, + Screenshot, + Error, +} + +impl ToastIcon { + pub fn from_name(name: &str) -> Self { + match name.to_lowercase().as_str() { + "replay" | "save_replay" => ToastIcon::Replay, + "record" | "recording" | "start" => ToastIcon::Record, + "save" | "saved" | "stop" => ToastIcon::Save, + "cursor" => ToastIcon::Cursor, + "screenshot" => ToastIcon::Screenshot, + "error" => ToastIcon::Error, + _ => ToastIcon::Info, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum Command { SaveReplay, @@ -79,16 +104,14 @@ pub fn get_ipc_port_path() -> std::path::PathBuf { #[cfg(windows)] pub fn read_active_ipc_port() -> u16 { - if let Ok(content) = std::fs::read_to_string(get_ipc_port_path()) { - if let Ok(port) = content.trim().parse::() { + if let Ok(content) = std::fs::read_to_string(get_ipc_port_path()) + && let Ok(port) = content.trim().parse::() { return port; - } } let legacy = std::env::temp_dir().join("vrec-ipc.port"); - if let Ok(content) = std::fs::read_to_string(legacy) { - if let Ok(port) = content.trim().parse::() { + if let Ok(content) = std::fs::read_to_string(legacy) + && let Ok(port) = content.trim().parse::() { return port; - } } 42069 } @@ -147,14 +170,13 @@ pub fn send_command(cmd: Command) -> Result<(), Box() { - if let Ok(mut stream) = TcpStream::connect_timeout(&addr, Duration::from_millis(150)) { + if let Ok(addr) = format!("127.0.0.1:{}", port).parse::() + && let Ok(mut stream) = TcpStream::connect_timeout(&addr, Duration::from_millis(150)) { let _ = stream.set_write_timeout(Some(Duration::from_millis(600))); let _ = stream.set_read_timeout(Some(Duration::from_millis(600))); if stream.write_all(&len_buf).is_ok() && stream.write_all(&payload).is_ok() { return Ok(()); } - } } } last_err = Some("Could not connect to daemon on any TCP IPC port".into()); @@ -217,11 +239,10 @@ pub fn query_status() -> Result() { - if let Ok(stream) = TcpStream::connect_timeout(&addr, Duration::from_millis(150)) { + if let Ok(addr) = format!("127.0.0.1:{}", port).parse::() + && let Ok(stream) = TcpStream::connect_timeout(&addr, Duration::from_millis(150)) { conn = Some(stream); break; - } } } conn.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotConnected, "No active daemon port reachable")) diff --git a/src/lib.rs b/src/lib.rs index 52f1620..213d62c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ pub mod hotkey; pub mod ipc; pub mod overlay; pub mod config; -pub mod settings_ui; pub mod overlay_egui; pub mod hyprland_binds; pub mod updater; diff --git a/src/overlay.rs b/src/overlay.rs index 3dd1766..91a9be4 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -1,44 +1,6 @@ -use gtk::prelude::*; -use gtk::{ - Application, ApplicationWindow, Box, Button, ComboBoxText, CssProvider, - DrawingArea, Entry, Fixed, Label, LevelBar, Orientation, Revealer, RevealerTransitionType, Scale, - ScrolledWindow, SpinButton, Stack, StackTransitionType, StyleContext, Switch, -}; -#[cfg(target_os = "linux")] -use gtk_layer_shell::{Layer, LayerShell}; -use std::f64::consts::PI; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::{Duration, Instant}; -use crate::config::ScytheConfig; -use crate::ipc::{self, Command}; +pub use crate::ipc::ToastIcon; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ToastIcon { - Replay, - Record, - Save, - Cursor, - Info, - Error, - Screenshot, -} - -impl ToastIcon { - pub fn from_name(name: &str) -> Self { - match name.to_lowercase().as_str() { - "replay" | "save_replay" => ToastIcon::Replay, - "record" | "recording" | "start" => ToastIcon::Record, - "save" | "saved" | "stop" => ToastIcon::Save, - "cursor" => ToastIcon::Cursor, - "screenshot" => ToastIcon::Screenshot, - "error" => ToastIcon::Error, - _ => ToastIcon::Info, - } - } -} - -pub fn spawn_toast(title: &str, subtitle: &str, icon: ToastIcon) { +pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { let icon_str = match icon { ToastIcon::Replay => "replay", ToastIcon::Record => "record", @@ -48,1775 +10,26 @@ pub fn spawn_toast(title: &str, subtitle: &str, icon: ToastIcon) { ToastIcon::Error => "error", ToastIcon::Info => "info", }; - - let title_owned = title.to_string(); - let subtitle_owned = subtitle.to_string(); - let icon_owned = icon_str.to_string(); - - std::thread::spawn(move || { - let exe = if let Ok(mut p) = std::env::current_exe() { - p.pop(); - #[cfg(target_os = "windows")] - let ui = p.join("scythe-ui.exe"); - #[cfg(not(target_os = "windows"))] - let ui = p.join("scythe-ui"); - if ui.exists() { - ui - } else { - std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("scythe-ui")) - } - } else { - std::path::PathBuf::from("scythe-ui") - }; - let mut cmd = std::process::Command::new(&exe); - if let Some(parent) = exe.parent() { - cmd.current_dir(parent); - } - cmd.args(["--toast", &title_owned, &subtitle_owned, &icon_owned]) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()); - #[cfg(target_os = "windows")] - { - use std::os::windows::process::CommandExt; - cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW - } - let _ = cmd.spawn(); - }); -} - -pub fn show_notification_overlay() { - show_shadowplay_toast("INSTANT REPLAY", "Saved to Videos", ToastIcon::Replay); -} - -pub fn show_notification(message: &str) { - let lower = message.to_lowercase(); - let (title, subtitle, icon) = if lower.contains("replay saved") || lower.contains("replay") { - ("INSTANT REPLAY", "Saved to Videos", ToastIcon::Replay) - } else if lower.contains("recording started") { - ("RECORDING", "Recording started", ToastIcon::Record) - } else if lower.contains("recording saved") || lower.contains("stopped") { - ("RECORDING", "Recording saved", ToastIcon::Save) - } else if lower.contains("cursor") { - ("MOUSE CURSOR", message, ToastIcon::Cursor) - } else if lower.contains("error") { - ("SCYTHE", message, ToastIcon::Error) - } else { - ("SCYTHE", message, ToastIcon::Info) - }; - show_shadowplay_toast(title, subtitle, icon); + let _ = std::process::Command::new("scythe-ui") + .args(["--toast", title, subtitle, icon_str]) + .spawn(); } pub fn ensure_wayland_env() { #[cfg(target_os = "linux")] { - let runtime_dir = std::env::var("XDG_RUNTIME_DIR") - .unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() })); - unsafe { - if std::env::var("WAYLAND_DISPLAY").is_err() - && let Ok(entries) = std::fs::read_dir(&runtime_dir) { - for entry in entries.flatten() { - let name = entry.file_name().to_string_lossy().to_string(); - if name.starts_with("wayland-") && !name.ends_with(".lock") { - std::env::set_var("WAYLAND_DISPLAY", &name); - break; - } - } - } - if std::env::var("DISPLAY").is_err() && std::path::Path::new("/tmp/.X11-unix/X0").exists() { - std::env::set_var("DISPLAY", ":0"); - } - if std::env::var("DBUS_SESSION_BUS_ADDRESS").is_err() { - let bus_path = format!("{}/bus", runtime_dir); - if std::path::Path::new(&bus_path).exists() { - std::env::set_var("DBUS_SESSION_BUS_ADDRESS", format!("unix:path={}", bus_path)); - } - } - if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_err() { - let hypr_dir = std::path::Path::new(&runtime_dir).join("hypr"); - if let Ok(entries) = std::fs::read_dir(&hypr_dir) { - for entry in entries.flatten() { - if entry.path().is_dir() { - let sig = entry.file_name().to_string_lossy().to_string(); - if !sig.is_empty() { - std::env::set_var("HYPRLAND_INSTANCE_SIGNATURE", &sig); - break; - } + if std::env::var("WAYLAND_DISPLAY").is_err() + && let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") { + let candidates = ["wayland-1", "wayland-0", "wayland-2"]; + for c in &candidates { + let socket = std::path::Path::new(&runtime_dir).join(c); + if socket.exists() { + unsafe { + std::env::set_var("WAYLAND_DISPLAY", c); } + break; } } - } - if std::env::var("XDG_CURRENT_DESKTOP").is_err() && std::env::var("WAYLAND_DISPLAY").is_ok() { - std::env::set_var("XDG_CURRENT_DESKTOP", "Hyprland"); - } - if std::env::var("XDG_SESSION_TYPE").map(|s| s == "tty" || s.is_empty()).unwrap_or(true) { - if std::env::var("WAYLAND_DISPLAY").is_ok() { - std::env::set_var("XDG_SESSION_TYPE", "wayland"); - } else if std::env::var("DISPLAY").is_ok() { - std::env::set_var("XDG_SESSION_TYPE", "x11"); - } - } } } } - -pub fn show_shadowplay_toast(title: &str, subtitle: &str, icon: ToastIcon) { - #[cfg(target_os = "windows")] - { - spawn_toast(title, subtitle, icon); - return; - } - - #[cfg(not(target_os = "windows"))] - { - ensure_wayland_env(); - if std::env::var("WAYLAND_DISPLAY").is_err() && std::env::var("DISPLAY").is_err() { - crate::overlay_egui::run_egui_toast(title, subtitle, icon); - return; - } - if gtk::init().is_err() { - crate::overlay_egui::run_egui_toast(title, subtitle, icon); - return; - } - - let app_id = format!("com.scythe.toast.p{}", std::process::id()); - let app = Application::builder() - .application_id(&app_id) - .flags(gtk::gio::ApplicationFlags::NON_UNIQUE) - .build(); - - let title_text = title.to_string(); - let sub_text = subtitle.to_string(); - - let cfg = ScytheConfig::load(); - let accent_name = cfg.accent_color.to_lowercase(); - let (accent_hex, accent_rgb): (&str, (f64, f64, f64)) = match accent_name.as_str() { - "green" | "emerald" => ("#22c55e", (0.133, 0.773, 0.369)), - "lime" => ("#a3e635", (0.639, 0.902, 0.208)), - "yellow" | "solar" => ("#facc15", (0.980, 0.800, 0.082)), - "amber" | "orange" => ("#f59e0b", (0.961, 0.620, 0.043)), - "red" | "crimson" => ("#ef4444", (0.937, 0.267, 0.267)), - "pink" | "rose" => ("#f43f5e", (0.957, 0.247, 0.369)), - "purple" | "violet" => ("#a855f7", (0.659, 0.333, 0.969)), - "blue" | "sapphire" => ("#38bdf8", (0.220, 0.741, 0.973)), - _ => ("#38bdf8", (0.220, 0.741, 0.973)), - }; - - let (_active_accent_hex, active_accent) = if icon == ToastIcon::Record { - ("#ef4444", (0.937, 0.267, 0.267)) - } else { - (accent_hex, accent_rgb) - }; - - fn get_active_gdk_monitor(display: &gdk::Display) -> Option { - if let Ok(out) = std::process::Command::new("hyprctl").args(["monitors", "-j"]).output() - && let Ok(v) = serde_json::from_slice::>(&out.stdout) { - let focused = v.iter().find(|m| m["focused"].as_bool().unwrap_or(false)) - .or_else(|| v.first()); - if let Some(m) = focused { - let fx = m["x"].as_i64().unwrap_or(0) as i32; - let fy = m["y"].as_i64().unwrap_or(0) as i32; - for i in 0..display.n_monitors() { - if let Some(mon) = display.monitor(i) { - let geom = mon.geometry(); - if geom.x() == fx && geom.y() == fy { - return Some(mon); - } - } - } - } - } - display.primary_monitor().or_else(|| display.monitor(0)) - } - - app.connect_activate(move |app| { - let window = ApplicationWindow::builder() - .application(app) - .default_width(320) - .default_height(56) - .build(); - - #[cfg(target_os = "linux")] - let layer_shell_ok = gtk_layer_shell::is_supported(); - #[cfg(not(target_os = "linux"))] - let layer_shell_ok = false; - - let active_monitor = gdk::Display::default().and_then(|d| get_active_gdk_monitor(&d)); - - if layer_shell_ok { - window.init_layer_shell(); - window.set_layer(Layer::Overlay); - window.set_namespace("scythe-notification"); - #[cfg(target_os = "linux")] - if let Some(ref mon) = active_monitor { - gtk_layer_shell::LayerShell::set_monitor(&window, mon); - } - window.set_anchor(gtk_layer_shell::Edge::Top, true); - window.set_anchor(gtk_layer_shell::Edge::Right, true); - window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 16); - window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, 0); // flush on screen right edge - window.set_keyboard_interactivity(false); - } else { - window.set_decorated(false); - window.set_keep_above(true); - window.set_skip_taskbar_hint(true); - window.set_accept_focus(false); - if let Some(ref mon) = active_monitor { - let geom = mon.geometry(); - let x = geom.x() + geom.width() - 320; - let y = geom.y() + 16; - window.move_(x, y); - } - } - - if let Some(screen) = gdk::Screen::default() - && let Some(visual) = screen.rgba_visual() { - window.set_visual(Some(&visual)); - } - window.set_app_paintable(true); - window.connect_draw(|_, cr| { - cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); - cr.set_operator(gtk::cairo::Operator::Source); - let _ = cr.paint(); - cr.set_operator(gtk::cairo::Operator::Over); - gtk::glib::Propagation::Proceed - }); - - let css_provider = CssProvider::new(); - let css = r#" - window, window.background { - background-color: transparent; - background: transparent; - border: none; - box-shadow: none; - } - .toast-card { - background-color: rgba(10, 10, 10, 0.65); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 0px; - box-shadow: none; - } - "#; - let _ = css_provider.load_from_data(css.as_bytes()); - if let Some(screen) = gdk::Screen::default() { - StyleContext::add_provider_for_screen( - &screen, - &css_provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - } - window.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_USER); - - let start_time = Instant::now(); - - let hbox = Box::new(Orientation::Horizontal, 10); - hbox.style_context().add_class("toast-card"); - hbox.set_size_request(320, 56); - hbox.set_app_paintable(true); - - let start_time_draw = start_time; - hbox.connect_draw(move |widget, cr| { - let w = widget.allocated_width() as f64; - let h = widget.allocated_height() as f64; - let elapsed = start_time_draw.elapsed().as_secs_f64(); - let total_dur = 2.80_f64; - - // Dynamic fade transition - let fade_in = (elapsed / 0.22).min(1.0); - let fade_out = ((total_dur - elapsed) / 0.32).clamp(0.0, 1.0); - let alpha = fade_in.min(fade_out); - - cr.set_operator(gtk::cairo::Operator::Over); - - // 1. Entrance accent bloom (first 0.55s) - let bloom = if elapsed < 0.55 { (1.0 - elapsed / 0.55).powi(2) } else { 0.0 }; - if bloom > 0.01 { - cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.35 * bloom * alpha); - cr.set_line_width(2.0); - cr.rectangle(1.0, 1.0, w - 2.0, h - 2.0); - let _ = cr.stroke(); - } - - // 2. Sleek translucent neutral dark glass background (matching HUD cards) - cr.set_source_rgba(10.0 / 255.0, 10.0 / 255.0, 10.0 / 255.0, 0.68 * alpha); - cr.rectangle(0.0, 0.0, w, h); - let _ = cr.fill(); - - // 3. Top specular frost highlight - cr.set_source_rgba(1.0, 1.0, 1.0, 0.15 * alpha); - cr.set_line_width(1.0); - cr.move_to(1.0, 0.5); - cr.line_to(w - 1.0, 0.5); - let _ = cr.stroke(); - - // 4. Refined subtle accent stroke framing the card - cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.55 * alpha); - cr.set_line_width(1.0); - cr.rectangle(0.5, 0.5, w - 1.0, h - 1.0); - let _ = cr.stroke(); - - // 5. Animated countdown timer line on bottom edge (vibrant accent glow) - let progress = 1.0 - (elapsed / total_dur).clamp(0.0, 1.0); - let bar_h = 3.5; - let bar_w = (w * progress).max(0.0); - - // Track: glowing tinted accent baseline - cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.22 * alpha); - cr.rectangle(0.0, h - bar_h, w, bar_h); - let _ = cr.fill(); - - // Ambient vibrant glow above bar - let glow_h = 3.0; - cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 0.28 * alpha); - cr.rectangle(0.0, h - bar_h - glow_h, bar_w, glow_h); - let _ = cr.fill(); - - // Solid vibrant core bar - cr.set_source_rgba(active_accent.0, active_accent.1, active_accent.2, 1.0 * alpha); - cr.rectangle(0.0, h - bar_h, bar_w, bar_h); - let _ = cr.fill(); - - // Top neon specular edge highlight - let high_r = (active_accent.0 + 0.35).min(1.0); - let high_g = (active_accent.1 + 0.35).min(1.0); - let high_b = (active_accent.2 + 0.35).min(1.0); - cr.set_source_rgba(high_r, high_g, high_b, 0.90 * alpha); - cr.set_line_width(1.0); - cr.move_to(0.0, h - bar_h); - cr.line_to(bar_w, h - bar_h); - let _ = cr.stroke(); - - // Laser-sharp white leading tip indicator - if bar_w > 2.0 { - cr.set_source_rgba(1.0, 1.0, 1.0, 1.0 * alpha); - cr.set_line_width(2.0); - cr.move_to(bar_w, h - bar_h - 1.0); - cr.line_to(bar_w, h); - let _ = cr.stroke(); - } - - gtk::glib::Propagation::Proceed - }); - - // Left DrawingArea for vector icon - let icon_area = DrawingArea::new(); - icon_area.set_size_request(32, 32); - icon_area.set_margin_start(14); - icon_area.set_valign(gtk::Align::Center); - let icon_type = icon; - let start_time_icon = start_time; - icon_area.connect_draw(move |_, cr| { - let cx = 16.0; - let cy = 16.0; - let elapsed = start_time_icon.elapsed().as_secs_f64(); - match icon_type { - ToastIcon::Replay => { - let scale = if elapsed < 0.35 { 0.85 + 0.15 * (elapsed / 0.35) } else { 1.0 }; - cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2); - cr.set_line_width(1.8); - cr.set_line_cap(gtk::cairo::LineCap::Round); - cr.set_line_join(gtk::cairo::LineJoin::Round); - let ch_h = 7.0 * scale; - let ch_w = 4.4 * scale; - let gap = 4.2 * scale; - let start_x = cx - (ch_w + gap) * 0.5; - for i in 0..2 { - let tip_x = start_x + (i as f64) * gap; - let base_x = tip_x + ch_w; - cr.move_to(base_x, cy - ch_h); - cr.line_to(tip_x, cy); - cr.line_to(base_x, cy + ch_h); - let _ = cr.stroke(); - } - } - ToastIcon::Record => { - let pulse = ((elapsed * 5.0).sin() * 0.5 + 0.5) * 0.35 + 0.65; - let red = (0.937, 0.267, 0.267); - cr.set_source_rgb(red.0, red.1, red.2); - cr.set_line_width(1.6); - let half = 8.5; - let arm = 3.2; - cr.move_to(cx - half + arm, cy - half); cr.line_to(cx - half, cy - half); cr.line_to(cx - half, cy - half + arm); - cr.move_to(cx + half - arm, cy - half); cr.line_to(cx + half, cy - half); cr.line_to(cx + half, cy - half + arm); - cr.move_to(cx - half + arm, cy + half); cr.line_to(cx - half, cy + half); cr.line_to(cx - half, cy + half - arm); - cr.move_to(cx + half - arm, cy + half); cr.line_to(cx + half, cy + half); cr.line_to(cx + half, cy + half - arm); - let _ = cr.stroke(); - cr.arc(cx, cy, 3.5 * pulse, 0.0, PI * 2.0); - let _ = cr.fill(); - } - ToastIcon::Save => { - cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2); - cr.set_line_width(2.0); - cr.set_line_cap(gtk::cairo::LineCap::Round); - cr.set_line_join(gtk::cairo::LineJoin::Round); - cr.move_to(cx - 6.0, cy + 0.5); - cr.line_to(cx - 2.0, cy + 4.5); - cr.line_to(cx + 6.0, cy - 4.5); - let _ = cr.stroke(); - } - ToastIcon::Cursor => { - cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2); - cr.set_line_width(1.8); - cr.set_line_cap(gtk::cairo::LineCap::Round); - cr.set_line_join(gtk::cairo::LineJoin::Round); - cr.move_to(cx - 5.0, cy - 7.0); - cr.line_to(cx - 5.0, cy + 5.0); - cr.line_to(cx - 1.5, cy + 2.0); - cr.line_to(cx + 1.5, cy + 7.0); - cr.line_to(cx + 3.5, cy + 6.0); - cr.line_to(cx + 0.5, cy + 1.0); - cr.line_to(cx + 5.0, cy + 1.0); - cr.close_path(); - let _ = cr.stroke(); - } - ToastIcon::Error => { - cr.set_source_rgb(0.937, 0.267, 0.267); - cr.set_line_width(2.0); - cr.set_line_cap(gtk::cairo::LineCap::Round); - cr.move_to(cx - 5.0, cy - 5.0); - cr.line_to(cx + 5.0, cy + 5.0); - cr.move_to(cx + 5.0, cy - 5.0); - cr.line_to(cx - 5.0, cy + 5.0); - let _ = cr.stroke(); - } - ToastIcon::Screenshot => { - cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2); - cr.set_line_width(1.8); - cr.set_line_cap(gtk::cairo::LineCap::Round); - cr.set_line_join(gtk::cairo::LineJoin::Round); - cr.rectangle(cx - 7.0, cy - 4.5, 14.0, 10.0); - let _ = cr.stroke(); - cr.arc(cx, cy + 0.5, 2.5, 0.0, PI * 2.0); - let _ = cr.stroke(); - cr.rectangle(cx - 3.5, cy - 7.0, 4.0, 2.5); - let _ = cr.fill(); - } - ToastIcon::Info => { - cr.set_source_rgb(accent_rgb.0, accent_rgb.1, accent_rgb.2); - cr.arc(cx, cy, 4.5, 0.0, PI * 2.0); - let _ = cr.fill(); - } - } - gtk::glib::Propagation::Proceed - }); - - // Right text column - let vbox = Box::new(Orientation::Vertical, 2); - vbox.set_valign(gtk::Align::Center); - vbox.set_margin_end(16); - - let title_lbl = Label::new(None); - title_lbl.set_markup(&format!( - "{}", - gtk::glib::markup_escape_text(&title_text) - )); - title_lbl.set_halign(gtk::Align::Start); - - let sub_lbl = Label::new(None); - sub_lbl.set_markup(&format!( - "{}", - gtk::glib::markup_escape_text(&sub_text) - )); - sub_lbl.set_halign(gtk::Align::Start); - - vbox.pack_start(&title_lbl, false, false, 0); - vbox.pack_start(&sub_lbl, false, false, 0); - - hbox.pack_start(&icon_area, false, false, 0); - hbox.pack_start(&vbox, true, true, 0); - - let fixed = Fixed::new(); - fixed.set_size_request(320, 56); - fixed.put(&hbox, 320, 0); - window.add(&fixed); - window.show_all(); - - let window_clone = window.clone(); - let hbox_clone = hbox.clone(); - let fixed_clone = fixed.clone(); - - gtk::glib::timeout_add_local(Duration::from_millis(16), move || { - let elapsed = start_time.elapsed().as_secs_f32(); - if elapsed >= 2.80 { - window_clone.close(); - return gtk::glib::ControlFlow::Break; - } - - let slide_x = if elapsed < 0.38 { - let t = (elapsed / 0.38).min(1.0); - let ease = 1.0 - (1.0 - t).powi(4); - (1.0 - ease) * 320.0 - } else if elapsed < 2.45 { - 0.0 - } else { - let t = ((elapsed - 2.45) / 0.35).min(1.0); - let ease = t.powi(3); - ease * 320.0 - }; - - let target_x = slide_x.round() as i32; - fixed_clone.move_(&hbox_clone, target_x, 0); - window_clone.queue_draw(); - gtk::glib::ControlFlow::Continue - }); - }); - - app.run_with_args(&[] as &[&str]); - } -} - -// Vector Icon Drawing Helpers (Centered & Antialiased) -fn draw_replay_icon(cr: >k::cairo::Context, width: f64, height: f64, is_active: bool) { - let cx = width / 2.0; - let cy = height / 2.0; - let r = 24.0; - - if is_active { - cr.set_source_rgb(0.463, 0.725, 0.0); // #76b900 NVIDIA green - } else { - cr.set_source_rgb(0.60, 0.68, 0.78); // #94a3b8 - } - cr.set_line_width(3.8); - cr.arc(cx, cy, r, 0.25 * PI, 1.80 * PI); - let _ = cr.stroke(); - - // Arrowhead at start of arc - let a_x = cx + r * (0.25 * PI).cos(); - let a_y = cy + r * (0.25 * PI).sin(); - cr.move_to(a_x, a_y); - cr.line_to(a_x - 9.0, a_y + 1.0); - cr.line_to(a_x - 1.0, a_y - 9.0); - cr.close_path(); - let _ = cr.fill(); - - // Centered play triangle - let tri_r = 8.5; - let tri_cx = cx + 1.0; - let tri_cy = cy; - cr.move_to(tri_cx + tri_r, tri_cy); - cr.line_to(tri_cx - tri_r * 0.6, tri_cy - tri_r * 0.86); - cr.line_to(tri_cx - tri_r * 0.6, tri_cy + tri_r * 0.86); - cr.close_path(); - let _ = cr.fill(); -} - -fn draw_record_icon(cr: >k::cairo::Context, width: f64, height: f64, is_recording: bool) { - let cx = width / 2.0; - let cy = height / 2.0; - if is_recording { - // Glowing red recording indicator with halo - cr.set_source_rgba(0.937, 0.267, 0.267, 0.25); - cr.arc(cx, cy, 32.0, 0.0, PI * 2.0); - let _ = cr.fill(); - - cr.set_source_rgb(0.937, 0.267, 0.267); // #ef4444 - cr.set_line_width(3.5); - cr.arc(cx, cy, 26.0, 0.0, PI * 2.0); - let _ = cr.stroke(); - - cr.set_source_rgb(0.937, 0.267, 0.267); - cr.arc(cx, cy, 13.0, 0.0, PI * 2.0); - let _ = cr.fill(); - } else { - cr.set_source_rgb(0.60, 0.68, 0.78); // #94a3b8 - cr.set_line_width(3.2); - cr.arc(cx, cy, 25.0, 0.0, PI * 2.0); - let _ = cr.stroke(); - - cr.set_source_rgb(0.88, 0.91, 0.94); - cr.arc(cx, cy, 12.0, 0.0, PI * 2.0); - let _ = cr.fill(); - } -} - -fn draw_gear_icon(cr: >k::cairo::Context, width: f64, height: f64) { - let cx = width / 2.0; - let cy = height / 2.0; - let r = 24.0; - - cr.set_source_rgb(0.60, 0.68, 0.78); // #94a3b8 - cr.set_line_width(3.0); - cr.arc(cx, cy, r * 0.75, 0.0, PI * 2.0); - let _ = cr.stroke(); - - cr.arc(cx, cy, r * 0.32, 0.0, PI * 2.0); - let _ = cr.stroke(); - - for i in 0..8 { - let angle = i as f64 * (PI / 4.0); - let p_in_x = cx + angle.cos() * (r * 0.68); - let p_in_y = cy + angle.sin() * (r * 0.68); - let p_out_x = cx + angle.cos() * (r * 1.08); - let p_out_y = cy + angle.sin() * (r * 1.08); - cr.set_line_width(4.2); - cr.move_to(p_in_x, p_in_y); - cr.line_to(p_out_x, p_out_y); - let _ = cr.stroke(); - } -} - -pub fn show_menu_overlay() { - #[cfg(target_os = "windows")] - { - crate::overlay_egui::run_egui_overlay(); - return; - } - - #[cfg(not(target_os = "windows"))] - { - if std::env::var("WAYLAND_DISPLAY").is_err() && std::env::var("DISPLAY").is_err() { - eprintln!("Warning: No display found for GTK menu overlay. Falling back to cross-platform egui overlay..."); - crate::overlay_egui::run_egui_overlay(); - return; - } - if gtk::init().is_err() { - eprintln!("Note: GTK display init failed. Falling back to cross-platform egui overlay..."); - crate::overlay_egui::run_egui_overlay(); - return; - } - - let app = Application::builder() - .application_id("com.scythe.hud") - .flags(gtk::gio::ApplicationFlags::NON_UNIQUE) - .build(); - - app.connect_activate(|app| { - let config = ScytheConfig::load(); - - let window = ApplicationWindow::builder() - .application(app) - .default_width(740) - .default_height(280) - .build(); - - #[cfg(target_os = "linux")] - let layer_shell_ok = gtk_layer_shell::is_supported(); - #[cfg(not(target_os = "linux"))] - let layer_shell_ok = false; - - if layer_shell_ok { - window.init_layer_shell(); - window.set_layer(Layer::Overlay); - window.set_namespace("scythe-overlay"); - window.set_keyboard_interactivity(true); - // Shifted higher towards the top (50px margin) per user feedback - window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 50); - window.set_anchor(gtk_layer_shell::Edge::Top, true); - } else { - window.set_decorated(false); - window.set_keep_above(true); - window.set_skip_taskbar_hint(true); - window.set_position(gtk::WindowPosition::Center); - if let Some(display) = gdk::Display::default() - && let Some(mon) = display.primary_monitor().or_else(|| display.monitor(0)) { - let geom = mon.geometry(); - let x = geom.x() + (geom.width() - 760) / 2; - let y = geom.y() + 50; - window.move_(x, y); - } - } - - // RGBA transparent visual setup - if let Some(screen) = gdk::Screen::default() - && let Some(visual) = screen.rgba_visual() { - window.set_visual(Some(&visual)); - } - window.set_app_paintable(true); - window.connect_draw(|_, cr| { - cr.set_source_rgba(0.0, 0.0, 0.0, 0.0); - cr.set_operator(gtk::cairo::Operator::Source); - let _ = cr.paint(); - gtk::glib::Propagation::Proceed - }); - - let stack = Stack::new(); - stack.set_transition_type(StackTransitionType::Crossfade); - stack.set_transition_duration(160); - stack.set_homogeneous(false); - - // ========================================================================= - // PAGE 1: 3 FROSTED GLASS CARDS WITH CONTEXTUAL DROPDOWNS - // ========================================================================= - let hud_page = Box::new(Orientation::Vertical, 10); - hud_page.style_context().add_class("hud-wrapper"); - hud_page.set_size_request(740, -1); - - // Top Bar (Frosted glass header strip, no "X" button) - let header_box = Box::new(Orientation::Horizontal, 10); - header_box.style_context().add_class("header-bar"); - - let brand_badge = Label::new(Some("SCYTHE")); - brand_badge.style_context().add_class("scythe-badge"); - - let title_label = Label::new(Some("SHADOWPLAY OVERLAY")); - title_label.style_context().add_class("overlay-title"); - - let status_banner = Label::new(Some("")); - status_banner.style_context().add_class("status-banner"); - - let spacer = Box::new(Orientation::Horizontal, 0); - - let esc_hint = Label::new(Some("Esc to Close")); - esc_hint.style_context().add_class("esc-pill"); - - header_box.pack_start(&brand_badge, false, false, 0); - header_box.pack_start(&title_label, false, false, 4); - header_box.pack_start(&status_banner, false, false, 12); - header_box.pack_start(&spacer, true, true, 0); - header_box.pack_start(&esc_hint, false, false, 0); - - // Row of 3 Cards - let cards_box = Box::new(Orientation::Horizontal, 16); - cards_box.style_context().add_class("cards-container"); - cards_box.set_halign(gtk::Align::Center); - - // State Trackers for Vector Redraws - let replay_active_state = Arc::new(AtomicBool::new(config.replay_enabled)); - let record_active_state = Arc::new(AtomicBool::new(false)); - - // ------------------------------------------------------------------------- - // CARD 1: INSTANT REPLAY - // ------------------------------------------------------------------------- - let replay_col = Box::new(Orientation::Vertical, 0); - replay_col.set_size_request(225, -1); - - let replay_card_btn = Button::new(); - replay_card_btn.style_context().add_class("card-btn"); - replay_card_btn.set_size_request(225, 195); - - let replay_card_inner = Box::new(Orientation::Vertical, 6); - replay_card_inner.set_valign(gtk::Align::Center); - replay_card_inner.set_halign(gtk::Align::Center); - - let replay_title_lbl = Label::new(Some("INSTANT REPLAY")); - replay_title_lbl.style_context().add_class("card-title"); - - let replay_icon_area = DrawingArea::new(); - replay_icon_area.set_size_request(76, 76); - replay_icon_area.set_valign(gtk::Align::Center); - replay_icon_area.set_halign(gtk::Align::Center); - let r_state_clone = Arc::clone(&replay_active_state); - replay_icon_area.connect_draw(move |w, cr| { - let width = w.allocated_width() as f64; - let height = w.allocated_height() as f64; - draw_replay_icon(cr, width, height, r_state_clone.load(Ordering::Relaxed)); - gtk::glib::Propagation::Proceed - }); - - let replay_status_lbl = Label::new(Some(if config.replay_enabled { "On" } else { "Off" })); - replay_status_lbl.style_context().add_class("card-status"); - if config.replay_enabled { - replay_status_lbl.style_context().add_class("status-green"); - } - - let replay_sub_lbl = Label::new(Some(&format!("{}s Buffer", config.replay_duration_sec))); - replay_sub_lbl.style_context().add_class("card-sub"); - - replay_card_inner.pack_start(&replay_title_lbl, false, false, 0); - replay_card_inner.pack_start(&replay_icon_area, false, false, 6); - replay_card_inner.pack_start(&replay_status_lbl, false, false, 0); - replay_card_inner.pack_start(&replay_sub_lbl, false, false, 2); - replay_card_btn.add(&replay_card_inner); - - // Instant Replay Attached Dropdown Menu - let replay_revealer = Revealer::new(); - replay_revealer.set_transition_type(RevealerTransitionType::SlideDown); - replay_revealer.set_transition_duration(150); - - let replay_menu_box = Box::new(Orientation::Vertical, 3); - replay_menu_box.style_context().add_class("dropdown-menu"); - - let replay_toggle_item = Button::with_label(if config.replay_enabled { "Turn off" } else { "Turn on" }); - replay_toggle_item.style_context().add_class("dropdown-item"); - - let replay_save_item = Button::with_label("Save (Ctrl+Shift+R)"); - replay_save_item.style_context().add_class("dropdown-item"); - - let replay_settings_item = Button::with_label("Settings"); - replay_settings_item.style_context().add_class("dropdown-item"); - - replay_menu_box.pack_start(&replay_toggle_item, false, false, 0); - replay_menu_box.pack_start(&replay_save_item, false, false, 0); - replay_menu_box.pack_start(&replay_settings_item, false, false, 0); - replay_revealer.add(&replay_menu_box); - - replay_col.pack_start(&replay_card_btn, false, false, 0); - replay_col.pack_start(&replay_revealer, false, false, 0); - - // ------------------------------------------------------------------------- - // CARD 2: RECORD - // ------------------------------------------------------------------------- - let record_col = Box::new(Orientation::Vertical, 0); - record_col.set_size_request(225, -1); - - let record_card_btn = Button::new(); - record_card_btn.style_context().add_class("card-btn"); - record_card_btn.set_size_request(225, 195); - - let record_card_inner = Box::new(Orientation::Vertical, 6); - record_card_inner.set_valign(gtk::Align::Center); - record_card_inner.set_halign(gtk::Align::Center); - - let record_title_lbl = Label::new(Some("RECORD")); - record_title_lbl.style_context().add_class("card-title"); - - let record_icon_area = DrawingArea::new(); - record_icon_area.set_size_request(76, 76); - record_icon_area.set_valign(gtk::Align::Center); - record_icon_area.set_halign(gtk::Align::Center); - let rec_state_clone = Arc::clone(&record_active_state); - record_icon_area.connect_draw(move |w, cr| { - let width = w.allocated_width() as f64; - let height = w.allocated_height() as f64; - draw_record_icon(cr, width, height, rec_state_clone.load(Ordering::Relaxed)); - gtk::glib::Propagation::Proceed - }); - - let record_status_lbl = Label::new(Some("Not recording")); - record_status_lbl.style_context().add_class("card-status"); - - let record_sub_lbl = Label::new(Some(&format!("{} FPS • {} Mbps", config.fps, config.record_bitrate_kbps / 1000))); - record_sub_lbl.style_context().add_class("card-sub"); - - record_card_inner.pack_start(&record_title_lbl, false, false, 0); - record_card_inner.pack_start(&record_icon_area, false, false, 6); - record_card_inner.pack_start(&record_status_lbl, false, false, 0); - record_card_inner.pack_start(&record_sub_lbl, false, false, 2); - record_card_btn.add(&record_card_inner); - - // Record Attached Dropdown Menu - let record_revealer = Revealer::new(); - record_revealer.set_transition_type(RevealerTransitionType::SlideDown); - record_revealer.set_transition_duration(150); - - let record_menu_box = Box::new(Orientation::Vertical, 3); - record_menu_box.style_context().add_class("dropdown-menu"); - - let record_toggle_item = Button::with_label("Start (Ctrl+Shift+F9)"); - record_toggle_item.style_context().add_class("dropdown-item"); - - let record_settings_item = Button::with_label("Settings"); - record_settings_item.style_context().add_class("dropdown-item"); - - record_menu_box.pack_start(&record_toggle_item, false, false, 0); - record_menu_box.pack_start(&record_settings_item, false, false, 0); - record_revealer.add(&record_menu_box); - - record_col.pack_start(&record_card_btn, false, false, 0); - record_col.pack_start(&record_revealer, false, false, 0); - - // ------------------------------------------------------------------------- - // CARD 3: SETTINGS - // ------------------------------------------------------------------------- - let settings_col = Box::new(Orientation::Vertical, 0); - settings_col.set_size_request(225, -1); - - let settings_card_btn = Button::new(); - settings_card_btn.style_context().add_class("card-btn"); - settings_card_btn.set_size_request(225, 195); - - let settings_card_inner = Box::new(Orientation::Vertical, 6); - settings_card_inner.set_valign(gtk::Align::Center); - settings_card_inner.set_halign(gtk::Align::Center); - - let settings_title_lbl = Label::new(Some("SETTINGS")); - settings_title_lbl.style_context().add_class("card-title"); - - let settings_icon_area = DrawingArea::new(); - settings_icon_area.set_size_request(76, 76); - settings_icon_area.set_valign(gtk::Align::Center); - settings_icon_area.set_halign(gtk::Align::Center); - settings_icon_area.connect_draw(|w, cr| { - let width = w.allocated_width() as f64; - let height = w.allocated_height() as f64; - draw_gear_icon(cr, width, height); - gtk::glib::Propagation::Proceed - }); - - let settings_status_lbl = Label::new(Some("Preferences")); - settings_status_lbl.style_context().add_class("card-status"); - - let settings_sub_lbl = Label::new(Some("Audio, Quality & Hotkeys")); - settings_sub_lbl.style_context().add_class("card-sub"); - - settings_card_inner.pack_start(&settings_title_lbl, false, false, 0); - settings_card_inner.pack_start(&settings_icon_area, false, false, 6); - settings_card_inner.pack_start(&settings_status_lbl, false, false, 0); - settings_card_inner.pack_start(&settings_sub_lbl, false, false, 2); - settings_card_btn.add(&settings_card_inner); - - settings_col.pack_start(&settings_card_btn, false, false, 0); - - // Pack 3 columns - cards_box.pack_start(&replay_col, false, false, 0); - cards_box.pack_start(&record_col, false, false, 0); - cards_box.pack_start(&settings_col, false, false, 0); - - hud_page.pack_start(&header_box, false, false, 0); - hud_page.pack_start(&cards_box, true, true, 0); - - // ========================================================================= - // PAGE 2: REVAMPED CLEAN FROSTED GLASS SETTINGS PANEL - // ========================================================================= - let settings_page = Box::new(Orientation::Vertical, 12); - settings_page.style_context().add_class("settings-panel"); - settings_page.set_size_request(760, -1); - - // Header with Back Button (No "X" button) - let settings_header = Box::new(Orientation::Horizontal, 12); - let back_btn = Button::with_label("< Back to Overlay"); - back_btn.style_context().add_class("back-btn"); - - let settings_page_title = Label::new(Some("RECORDER SETTINGS")); - settings_page_title.style_context().add_class("settings-title"); - - let settings_spacer = Box::new(Orientation::Horizontal, 0); - - let settings_esc_hint = Label::new(Some("Esc to Close")); - settings_esc_hint.style_context().add_class("esc-pill"); - - settings_header.pack_start(&back_btn, false, false, 0); - settings_header.pack_start(&settings_page_title, false, false, 10); - settings_header.pack_start(&settings_spacer, true, true, 0); - settings_header.pack_start(&settings_esc_hint, false, false, 0); - - // Scrollable Body - let scroll = ScrolledWindow::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); - scroll.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); - scroll.set_min_content_height(440); - scroll.set_max_content_height(480); - - let settings_body = Box::new(Orientation::Vertical, 10); - settings_body.set_margin_start(4); - settings_body.set_margin_end(4); - - // ------------------------------------------------------------------------- - // SECTION 1: DISPLAY & CAPTURE (With Mouse Cursor Switch!) - // ------------------------------------------------------------------------- - let sec1_card = Box::new(Orientation::Vertical, 8); - sec1_card.style_context().add_class("settings-section-card"); - - let sec1_hdr = Label::new(Some("DISPLAY & CAPTURE")); - sec1_hdr.style_context().add_class("section-header"); - sec1_hdr.set_halign(gtk::Align::Start); - sec1_card.pack_start(&sec1_hdr, false, false, 0); - - // 1.1 Mouse Cursor Toggle Row - let cursor_row = Box::new(Orientation::Horizontal, 12); - let cursor_text_box = Box::new(Orientation::Vertical, 2); - let cursor_title = Label::new(Some("Record Mouse Cursor")); - cursor_title.style_context().add_class("setting-row-title"); - cursor_title.set_halign(gtk::Align::Start); - let cursor_desc = Label::new(Some("Capture the mouse pointer in gameplay recordings and instant replays")); - cursor_desc.style_context().add_class("sub-info-label"); - cursor_desc.set_halign(gtk::Align::Start); - cursor_text_box.pack_start(&cursor_title, false, false, 0); - cursor_text_box.pack_start(&cursor_desc, false, false, 0); - - let cursor_switch = Switch::new(); - cursor_switch.set_active(config.show_cursor); - cursor_switch.set_valign(gtk::Align::Center); - - let cursor_sp = Box::new(Orientation::Horizontal, 0); - cursor_row.pack_start(&cursor_text_box, false, false, 0); - cursor_row.pack_start(&cursor_sp, true, true, 0); - cursor_row.pack_start(&cursor_switch, false, false, 0); - sec1_card.pack_start(&cursor_row, false, false, 4); - - let sec1_grid = gtk::Grid::new(); - sec1_grid.set_column_spacing(18); - sec1_grid.set_row_spacing(8); - - // 1.2 Target Framerate (FPS) - let fps_lbl = Label::new(Some("Target Framerate:")); - fps_lbl.set_halign(gtk::Align::Start); - let fps_box = Box::new(Orientation::Horizontal, 6); - let fps_spin = SpinButton::with_range(15.0, 240.0, 1.0); - fps_spin.set_value(config.fps as f64); - fps_box.pack_start(&fps_spin, false, false, 0); - for &fps_val in &[30, 60, 120, 144] { - let p_btn = Button::with_label(&format!("{} FPS", fps_val)); - p_btn.style_context().add_class("preset-btn"); - let s_clone = fps_spin.clone(); - p_btn.connect_clicked(move |_| s_clone.set_value(fps_val as f64)); - fps_box.pack_start(&p_btn, false, false, 0); - } - - // 1.3 Video Bitrate (Mbps) - let bit_lbl = Label::new(Some("Video Bitrate:")); - bit_lbl.set_halign(gtk::Align::Start); - let bit_box = Box::new(Orientation::Horizontal, 6); - let bit_spin = SpinButton::with_range(2.0, 100.0, 1.0); - bit_spin.set_value((config.record_bitrate_kbps / 1000) as f64); - bit_box.pack_start(&bit_spin, false, false, 0); - for &bit_val in &[10, 20, 30, 50] { - let p_btn = Button::with_label(&format!("{} Mbps", bit_val)); - p_btn.style_context().add_class("preset-btn"); - let s_clone = bit_spin.clone(); - p_btn.connect_clicked(move |_| s_clone.set_value(bit_val as f64)); - bit_box.pack_start(&p_btn, false, false, 0); - } - - // 1.4 Video Codec - let codec_lbl = Label::new(Some("Encoder Codec:")); - codec_lbl.set_halign(gtk::Align::Start); - let codec_combo = ComboBoxText::new(); - codec_combo.append(Some("h264"), "H.264 / AVC (Fast & Universal)"); - codec_combo.append(Some("hevc"), "HEVC / H.265 (High Efficiency)"); - codec_combo.append(Some("av1"), "AV1 (Next-Generation Quality)"); - codec_combo.set_active_id(Some(&config.video_codec)); - - sec1_grid.attach(&fps_lbl, 0, 0, 1, 1); - sec1_grid.attach(&fps_box, 1, 0, 1, 1); - sec1_grid.attach(&bit_lbl, 0, 1, 1, 1); - sec1_grid.attach(&bit_box, 1, 1, 1, 1); - sec1_grid.attach(&codec_lbl, 0, 2, 1, 1); - sec1_grid.attach(&codec_combo, 1, 2, 1, 1); - sec1_card.pack_start(&sec1_grid, false, false, 2); - settings_body.pack_start(&sec1_card, false, false, 0); - - // ------------------------------------------------------------------------- - // SECTION 2: INSTANT REPLAY BUFFER - // ------------------------------------------------------------------------- - let sec2_card = Box::new(Orientation::Vertical, 8); - sec2_card.style_context().add_class("settings-section-card"); - - let sec2_hdr = Label::new(Some("INSTANT REPLAY BUFFER")); - sec2_hdr.style_context().add_class("section-header"); - sec2_hdr.set_halign(gtk::Align::Start); - sec2_card.pack_start(&sec2_hdr, false, false, 0); - - let sec2_grid = gtk::Grid::new(); - sec2_grid.set_column_spacing(18); - sec2_grid.set_row_spacing(8); - - let dur_lbl = Label::new(Some("Buffer Length:")); - dur_lbl.set_halign(gtk::Align::Start); - let dur_box = Box::new(Orientation::Horizontal, 6); - let dur_spin = SpinButton::with_range(5.0, 1800.0, 5.0); - dur_spin.set_value(config.replay_duration_sec as f64); - dur_box.pack_start(&dur_spin, false, false, 0); - for &(dur_val, dur_txt) in &[(15, "15s"), (30, "30s"), (60, "60s"), (120, "2m"), (300, "5m")] { - let p_btn = Button::with_label(dur_txt); - p_btn.style_context().add_class("preset-btn"); - let s_clone = dur_spin.clone(); - p_btn.connect_clicked(move |_| s_clone.set_value(dur_val as f64)); - dur_box.pack_start(&p_btn, false, false, 0); - } - - sec2_grid.attach(&dur_lbl, 0, 0, 1, 1); - sec2_grid.attach(&dur_box, 1, 0, 1, 1); - sec2_card.pack_start(&sec2_grid, false, false, 2); - settings_body.pack_start(&sec2_card, false, false, 0); - - // ------------------------------------------------------------------------- - // SECTION 3: AUDIO ROUTING & LEVELS - // ------------------------------------------------------------------------- - let sec3_card = Box::new(Orientation::Vertical, 8); - sec3_card.style_context().add_class("settings-section-card"); - - let sec3_hdr = Label::new(Some("AUDIO ROUTING & SOUND")); - sec3_hdr.style_context().add_class("section-header"); - sec3_hdr.set_halign(gtk::Align::Start); - sec3_card.pack_start(&sec3_hdr, false, false, 0); - - let sec3_grid = gtk::Grid::new(); - sec3_grid.set_column_spacing(18); - sec3_grid.set_row_spacing(8); - - let audio_mode_lbl = Label::new(Some("Audio Mode:")); - audio_mode_lbl.set_halign(gtk::Align::Start); - let audio_mode_combo = ComboBoxText::new(); - audio_mode_combo.append(Some("system"), "System Sounds Only (Game / Desktop)"); - audio_mode_combo.append(Some("mic"), "Microphone Only"); - audio_mode_combo.append(Some("both"), "Both Combined (System Sounds + Microphone)"); - audio_mode_combo.append(Some("muted"), "Muted (No Audio)"); - audio_mode_combo.set_active_id(Some(&config.audio_mode)); - - let audio_dev_lbl = Label::new(Some("Recording Device:")); - audio_dev_lbl.set_halign(gtk::Align::Start); - let audio_dev_combo = ComboBoxText::new(); - audio_dev_combo.append(Some("default"), "Default Recording Device"); - for dev in crate::capture::audio::list_input_devices() { - audio_dev_combo.append(Some(&dev), &dev); - } - for app in crate::capture::audio::list_application_audio() { - audio_dev_combo.append(Some(&format!("app:{}", app)), &format!("App: {}", app)); - } - audio_dev_combo.set_active_id(Some(&config.audio_device)); - - let sys_vol_lbl = Label::new(Some("System Volume:")); - sys_vol_lbl.set_halign(gtk::Align::Start); - let sys_vol_scale = Scale::with_range(Orientation::Horizontal, 0.0, 150.0, 5.0); - sys_vol_scale.set_value((config.system_volume * 100.0).round() as f64); - sys_vol_scale.set_size_request(240, -1); - - let sys_level_bar = LevelBar::new(); - sys_level_bar.set_min_value(0.0); - sys_level_bar.set_max_value(1.0); - sys_level_bar.set_size_request(240, 6); - - let mic_vol_lbl = Label::new(Some("Mic Volume:")); - mic_vol_lbl.set_halign(gtk::Align::Start); - let mic_vol_scale = Scale::with_range(Orientation::Horizontal, 0.0, 150.0, 5.0); - mic_vol_scale.set_value((config.mic_volume * 100.0).round() as f64); - mic_vol_scale.set_size_request(240, -1); - - let mic_level_bar = LevelBar::new(); - mic_level_bar.set_min_value(0.0); - mic_level_bar.set_max_value(1.0); - mic_level_bar.set_size_request(240, 6); - - sec3_grid.attach(&audio_mode_lbl, 0, 0, 1, 1); - sec3_grid.attach(&audio_mode_combo, 1, 0, 1, 1); - sec3_grid.attach(&audio_dev_lbl, 0, 1, 1, 1); - sec3_grid.attach(&audio_dev_combo, 1, 1, 1, 1); - sec3_grid.attach(&sys_vol_lbl, 0, 2, 1, 1); - sec3_grid.attach(&sys_vol_scale, 1, 2, 1, 1); - sec3_grid.attach(&sys_level_bar, 1, 3, 1, 1); - sec3_grid.attach(&mic_vol_lbl, 0, 4, 1, 1); - sec3_grid.attach(&mic_vol_scale, 1, 4, 1, 1); - sec3_grid.attach(&mic_level_bar, 1, 5, 1, 1); - sec3_card.pack_start(&sec3_grid, false, false, 2); - settings_body.pack_start(&sec3_card, false, false, 0); - - // ------------------------------------------------------------------------- - // SECTION 4: STORAGE & SHORTCUTS - // ------------------------------------------------------------------------- - let sec4_card = Box::new(Orientation::Vertical, 8); - sec4_card.style_context().add_class("settings-section-card"); - - let sec4_hdr = Label::new(Some("STORAGE & SHORTCUTS")); - sec4_hdr.style_context().add_class("section-header"); - sec4_hdr.set_halign(gtk::Align::Start); - sec4_card.pack_start(&sec4_hdr, false, false, 0); - - let sec4_grid = gtk::Grid::new(); - sec4_grid.set_column_spacing(18); - sec4_grid.set_row_spacing(8); - - let dir_lbl = Label::new(Some("Save Folder:")); - dir_lbl.set_halign(gtk::Align::Start); - let dir_box = Box::new(Orientation::Horizontal, 8); - let dir_entry = Entry::new(); - dir_entry.set_text(&config.output_directory); - dir_entry.set_size_request(280, -1); - let dir_browse_btn = Button::with_label("Browse..."); - dir_browse_btn.style_context().add_class("preset-btn"); - - let dir_entry_clone = dir_entry.clone(); - dir_browse_btn.connect_clicked(move |_| { - let cur_path = dir_entry_clone.text().to_string(); - let (tx, rx) = std::sync::mpsc::channel::(); - let de = dir_entry_clone.clone(); - gtk::glib::timeout_add_local(Duration::from_millis(100), move || { - if let Ok(sel) = rx.try_recv() { - de.set_text(&sel); - gtk::glib::ControlFlow::Break - } else { - gtk::glib::ControlFlow::Continue - } - }); - std::thread::spawn(move || { - if let Ok(out) = std::process::Command::new("kdialog") - .args(["--getexistingdirectory", &cur_path]) - .output() - { - let sel = String::from_utf8_lossy(&out.stdout).trim().to_string(); - if !sel.is_empty() { - let _ = tx.send(sel); - } - } - }); - }); - - dir_box.pack_start(&dir_entry, true, true, 0); - dir_box.pack_start(&dir_browse_btn, false, false, 0); - - let save_hk_lbl = Label::new(Some("Save Replay Hotkey:")); - save_hk_lbl.set_halign(gtk::Align::Start); - let save_hk_entry = Entry::new(); - save_hk_entry.set_text(&config.save_hotkey); - - let rec_hk_lbl = Label::new(Some("Toggle Record Hotkey:")); - rec_hk_lbl.set_halign(gtk::Align::Start); - let rec_hk_entry = Entry::new(); - rec_hk_entry.set_text(&config.record_hotkey); - - let menu_hk_lbl = Label::new(Some("Overlay Menu Hotkey:")); - menu_hk_lbl.set_halign(gtk::Align::Start); - let menu_hk_entry = Entry::new(); - menu_hk_entry.set_text(&config.menu_hotkey); - - let cur_hk_lbl = Label::new(Some("Toggle Cursor Hotkey:")); - cur_hk_lbl.set_halign(gtk::Align::Start); - let cur_hk_entry = Entry::new(); - cur_hk_entry.set_text(&config.cursor_hotkey); - - let auto_lbl = Label::new(Some("Autostart on Login:")); - auto_lbl.set_halign(gtk::Align::Start); - let auto_switch = Switch::new(); - auto_switch.set_active(config.autostart); - - sec4_grid.attach(&dir_lbl, 0, 0, 1, 1); - sec4_grid.attach(&dir_box, 1, 0, 1, 1); - sec4_grid.attach(&save_hk_lbl, 0, 1, 1, 1); - sec4_grid.attach(&save_hk_entry, 1, 1, 1, 1); - sec4_grid.attach(&rec_hk_lbl, 0, 2, 1, 1); - sec4_grid.attach(&rec_hk_entry, 1, 2, 1, 1); - sec4_grid.attach(&menu_hk_lbl, 0, 3, 1, 1); - sec4_grid.attach(&menu_hk_entry, 1, 3, 1, 1); - sec4_grid.attach(&cur_hk_lbl, 0, 4, 1, 1); - sec4_grid.attach(&cur_hk_entry, 1, 4, 1, 1); - sec4_grid.attach(&auto_lbl, 0, 5, 1, 1); - sec4_grid.attach(&auto_switch, 1, 5, 1, 1); - sec4_card.pack_start(&sec4_grid, false, false, 2); - settings_body.pack_start(&sec4_card, false, false, 0); - - scroll.add(&settings_body); - - // Apply & Save Settings Button - let apply_btn = Button::with_label("Apply & Save Settings"); - apply_btn.style_context().add_class("apply-save-btn"); - apply_btn.set_size_request(-1, 44); - - settings_page.pack_start(&settings_header, false, false, 0); - settings_page.pack_start(&scroll, true, true, 4); - settings_page.pack_start(&apply_btn, false, false, 4); - - // Add Pages to Stack - stack.add_named(&hud_page, "hud"); - stack.add_named(&settings_page, "settings"); - window.add(&stack); - - // ========================================================================= - // CSS STYLING (Translucent Frosted Glass Theme) - // ========================================================================= - let css_provider = CssProvider::new(); - let css = r#" - window, window.background, .background { - background-color: transparent !important; - background: transparent !important; - border: none !important; - box-shadow: none !important; - } - .hud-wrapper { - background-color: transparent; - padding: 0px; - } - /* Floating Frosted Glass Top Bar */ - .header-bar { - background-color: rgba(18, 26, 38, 0.72); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 12px; - padding: 8px 18px; - box-shadow: 0px 12px 32px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.16); - } - .scythe-badge, - .vrec-badge { - background-color: #76b900; - color: #0b1204; - font-size: 11px; - font-weight: 900; - border-radius: 4px; - padding: 2px 7px; - letter-spacing: 0.5px; - } - .overlay-title { - color: #f1f5f9; - font-size: 12.5px; - font-weight: 800; - letter-spacing: 1.2px; - } - .status-banner { - color: #76b900; - font-size: 11.5px; - font-weight: 700; - } - .esc-pill { - background-color: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.14); - color: #94a3b8; - font-size: 10.5px; - font-weight: 700; - border-radius: 6px; - padding: 3px 8px; - } - /* Frosted Glass Action Cards */ - .card-btn { - background-color: rgba(20, 28, 42, 0.68); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 16px; - padding: 18px; - box-shadow: 0px 16px 40px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.18); - } - .card-btn:hover { - background-color: rgba(26, 38, 56, 0.82); - border-color: #76b900; - box-shadow: 0 20px 48px rgba(0, 0, 0, 0.65), 0 0 20px rgba(118, 185, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.25); - } - .card-btn-active { - background-color: rgba(26, 38, 56, 0.90); - border-color: #76b900; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - box-shadow: 0 0 20px rgba(118, 185, 0, 0.35); - } - .card-title { - color: #ffffff; - font-size: 11.5px; - font-weight: 800; - letter-spacing: 0.8px; - } - .card-status { - color: #94a3b8; - font-size: 11.5px; - font-weight: 700; - } - .status-green { - color: #76b900 !important; - } - .status-red { - color: #ef4444 !important; - } - .card-sub { - color: #64748b; - font-size: 10.5px; - font-weight: 500; - } - /* Attached Frosted Dropdown Menus */ - .dropdown-menu { - background-color: rgba(16, 22, 34, 0.88); - border: 1px solid rgba(255, 255, 255, 0.15); - border-top: none; - border-bottom-left-radius: 14px; - border-bottom-right-radius: 14px; - padding: 8px; - box-shadow: 0px 20px 48px rgba(0, 0, 0, 0.75); - } - .dropdown-item { - background-color: transparent; - color: #e2e8f0; - font-size: 12.5px; - font-weight: 700; - border-radius: 8px; - border: 1px solid transparent; - padding: 9px 14px; - } - .dropdown-item:hover { - background-color: rgba(118, 185, 0, 0.15); - border-color: rgba(118, 185, 0, 0.5); - color: #76b900; - } - /* Frosted Settings Panel */ - .settings-panel { - background-color: #0f1620; - border: 1px solid rgba(255, 255, 255, 0.16); - border-radius: 18px; - padding: 20px 26px; - box-shadow: 0px 24px 64px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.2); - } - .settings-section-card { - background-color: #18202e; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 12px; - padding: 14px 18px; - } - .settings-title { - color: #ffffff; - font-size: 13.5px; - font-weight: 800; - letter-spacing: 1px; - } - .setting-row-title { - color: #ffffff; - font-size: 13px; - font-weight: 700; - } - .back-btn { - background-color: rgba(118, 185, 0, 0.12); - color: #76b900; - font-size: 12px; - font-weight: 800; - border: 1px solid rgba(118, 185, 0, 0.45); - border-radius: 8px; - padding: 6px 14px; - } - .back-btn:hover { - background-color: rgba(118, 185, 0, 0.22); - color: #8ce000; - } - .section-header { - color: #76b900; - font-size: 11px; - font-weight: 800; - letter-spacing: 1px; - } - .preset-btn { - background-color: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 6px; - color: #cbd5e1; - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - } - .preset-btn:hover { - background-color: rgba(118, 185, 0, 0.22); - border-color: #76b900; - color: #ffffff; - } - .apply-save-btn { - background-color: #76b900; - color: #0b1204; - font-size: 13px; - font-weight: 800; - border-radius: 8px; - border: none; - padding: 10px 16px; - box-shadow: 0 4px 16px rgba(118, 185, 0, 0.35); - } - .apply-save-btn:hover { - background-color: #8ce000; - } - switch { - border-radius: 14px; - background-color: #1e2838; - border: 1px solid rgba(255, 255, 255, 0.2); - } - switch:checked { - background-color: #76b900; - border-color: #8ce000; - } - switch slider { - background-color: #ffffff; - border-radius: 50%; - min-width: 18px; - min-height: 18px; - } - entry, spinbutton, combobox button { - background-color: rgba(18, 24, 36, 0.7); - color: #f1f5f9; - border: 1px solid rgba(255, 255, 255, 0.14); - border-radius: 6px; - padding: 5px 8px; - font-size: 12px; - font-weight: 600; - } - entry:focus, spinbutton:focus, combobox button:focus { - border-color: #76b900; - } - scale highlight { - background-color: #76b900; - border-radius: 3px; - } - scale trough { - background-color: #1c2432; - border-radius: 3px; - } - scale slider { - background-color: #ffffff; - border-radius: 50%; - min-width: 14px; - min-height: 14px; - } - .sub-info-label { - color: #94a3b8; - font-size: 10.5px; - font-weight: 500; - } - "#; - let _ = css_provider.load_from_data(css.as_bytes()); - if let Some(screen) = gdk::Screen::default() { - StyleContext::add_provider_for_screen( - &screen, - &css_provider, - gtk::STYLE_PROVIDER_PRIORITY_USER, - ); - } - window.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_USER); - - // ========================================================================= - // EVENT HANDLERS & NAVIGATION (Esc key closes cleanly) - // ========================================================================= - let w_esc = window.clone(); - window.connect_key_press_event(move |_, key| { - if key.keyval() == gdk::keys::constants::Escape { - w_esc.close(); - std::process::exit(0); - } else { - gtk::glib::Propagation::Proceed - } - }); - - // ------------------------------------------------------------------------- - // CARD INTERACTION: TOGGLE ATTACHED DROPDOWNS - // ------------------------------------------------------------------------- - let rev_r_click = replay_revealer.clone(); - let rev_rec_close = record_revealer.clone(); - let card_r_btn = replay_card_btn.clone(); - let card_rec_btn = record_card_btn.clone(); - replay_card_btn.connect_clicked(move |_| { - let next_rev = !rev_r_click.reveals_child(); - rev_r_click.set_reveal_child(next_rev); - rev_rec_close.set_reveal_child(false); - card_rec_btn.style_context().remove_class("card-btn-active"); - if next_rev { - card_r_btn.style_context().add_class("card-btn-active"); - } else { - card_r_btn.style_context().remove_class("card-btn-active"); - } - }); - - let rev_rec_click = record_revealer.clone(); - let rev_r_close = replay_revealer.clone(); - let card_rec_btn2 = record_card_btn.clone(); - let card_r_btn2 = replay_card_btn.clone(); - record_card_btn.connect_clicked(move |_| { - let next_rev = !rev_rec_click.reveals_child(); - rev_rec_click.set_reveal_child(next_rev); - rev_r_close.set_reveal_child(false); - card_r_btn2.style_context().remove_class("card-btn-active"); - if next_rev { - card_rec_btn2.style_context().add_class("card-btn-active"); - } else { - card_rec_btn2.style_context().remove_class("card-btn-active"); - } - }); - - let stack_to_settings = stack.clone(); - let w_to_settings = window.clone(); - settings_card_btn.connect_clicked(move |_| { - stack_to_settings.set_visible_child_name("settings"); - w_to_settings.resize(760, 560); - }); - - // Back to HUD from Settings - let stack_to_hud = stack.clone(); - let w_to_hud = window.clone(); - back_btn.connect_clicked(move |_| { - stack_to_hud.set_visible_child_name("hud"); - w_to_hud.resize(740, 280); - }); - - // Replay Menu Items - let r_toggle_lbl = replay_toggle_item.clone(); - let r_status_lbl_sync = replay_status_lbl.clone(); - let r_state_sync = Arc::clone(&replay_active_state); - let r_icon_sync = replay_icon_area.clone(); - let rev_r_hide = replay_revealer.clone(); - let card_r_deactivate = replay_card_btn.clone(); - replay_toggle_item.connect_clicked(move |_| { - let mut cfg = ScytheConfig::load(); - cfg.replay_enabled = !cfg.replay_enabled; - let _ = cfg.save(); - ScytheConfig::notify_daemon_reload(); - - r_state_sync.store(cfg.replay_enabled, Ordering::Relaxed); - r_icon_sync.queue_draw(); - - if cfg.replay_enabled { - r_toggle_lbl.set_label("Turn off"); - r_status_lbl_sync.set_label("On"); - r_status_lbl_sync.style_context().add_class("status-green"); - } else { - r_toggle_lbl.set_label("Turn on"); - r_status_lbl_sync.set_label("Off"); - r_status_lbl_sync.style_context().remove_class("status-green"); - } - rev_r_hide.set_reveal_child(false); - card_r_deactivate.style_context().remove_class("card-btn-active"); - }); - - let banner_save = status_banner.clone(); - let rev_r_hide2 = replay_revealer.clone(); - let card_r_deactivate2 = replay_card_btn.clone(); - replay_save_item.connect_clicked(move |_| { - let _ = ipc::send_command(Command::SaveReplay); - banner_save.set_text("Replay saved successfully!"); - let b_clear = banner_save.clone(); - gtk::glib::timeout_add_local(Duration::from_millis(3000), move || { - b_clear.set_text(""); - gtk::glib::ControlFlow::Break - }); - rev_r_hide2.set_reveal_child(false); - card_r_deactivate2.style_context().remove_class("card-btn-active"); - }); - - let stack_r_settings = stack.clone(); - let w_r_settings = window.clone(); - let rev_r_hide3 = replay_revealer.clone(); - let card_r_deactivate3 = replay_card_btn.clone(); - replay_settings_item.connect_clicked(move |_| { - rev_r_hide3.set_reveal_child(false); - card_r_deactivate3.style_context().remove_class("card-btn-active"); - stack_r_settings.set_visible_child_name("settings"); - w_r_settings.resize(760, 560); - }); - - // Record Menu Items - let rev_rec_hide = record_revealer.clone(); - let card_rec_deactivate = record_card_btn.clone(); - record_toggle_item.connect_clicked(move |_| { - let _ = ipc::send_command(Command::ToggleRecording); - rev_rec_hide.set_reveal_child(false); - card_rec_deactivate.style_context().remove_class("card-btn-active"); - }); - - let stack_rec_settings = stack.clone(); - let w_rec_settings = window.clone(); - let rev_rec_hide2 = record_revealer.clone(); - let card_rec_deactivate2 = record_card_btn.clone(); - record_settings_item.connect_clicked(move |_| { - rev_rec_hide2.set_reveal_child(false); - card_rec_deactivate2.style_context().remove_class("card-btn-active"); - stack_rec_settings.set_visible_child_name("settings"); - w_rec_settings.resize(760, 560); - }); - - // Direct Mouse Cursor Switch Toggle - let banner_cur = status_banner.clone(); - cursor_switch.connect_state_set(move |_, active| { - let mut cfg = ScytheConfig::load(); - if cfg.show_cursor != active { - cfg.show_cursor = active; - let _ = cfg.save(); - let _ = ipc::send_command(Command::ToggleCursor); - banner_cur.set_text(if active { "Cursor: Visible in recording" } else { "Cursor: Hidden from recording" }); - let b_clear = banner_cur.clone(); - gtk::glib::timeout_add_local(Duration::from_millis(3000), move || { - b_clear.set_text(""); - gtk::glib::ControlFlow::Break - }); - } - gtk::glib::Propagation::Proceed - }); - - // Apply & Save Settings - let stack_after_save = stack.clone(); - let w_after_save = window.clone(); - let banner_after_save = status_banner.clone(); - let r_sub_lbl_sync = replay_sub_lbl.clone(); - let rec_sub_lbl_sync = record_sub_lbl.clone(); - let cur_sw_sync = cursor_switch.clone(); - - apply_btn.connect_clicked(move |_| { - let mut cfg = ScytheConfig::load(); - cfg.show_cursor = cur_sw_sync.is_active(); - cfg.fps = fps_spin.value() as u32; - let bit_mbps = bit_spin.value() as u32; - cfg.record_bitrate_kbps = bit_mbps * 1000; - cfg.replay_bitrate_kbps = bit_mbps * 1000; - if let Some(c) = codec_combo.active_id() { - cfg.video_codec = c.to_string(); - } - cfg.replay_duration_sec = dur_spin.value() as u32; - if let Some(m) = audio_mode_combo.active_id() { - cfg.audio_mode = m.to_string(); - } - if let Some(dev) = audio_dev_combo.active_id() { - cfg.audio_device = dev.to_string(); - } - cfg.system_volume = (sys_vol_scale.value() / 100.0) as f32; - cfg.mic_volume = (mic_vol_scale.value() / 100.0) as f32; - - let dir_val = dir_entry.text().to_string(); - if !dir_val.trim().is_empty() { - cfg.output_directory = dir_val; - } - cfg.save_hotkey = save_hk_entry.text().to_string(); - cfg.record_hotkey = rec_hk_entry.text().to_string(); - cfg.menu_hotkey = menu_hk_entry.text().to_string(); - cfg.cursor_hotkey = cur_hk_entry.text().to_string(); - cfg.autostart = auto_switch.is_active(); - - let _ = cfg.save(); - ScytheConfig::notify_daemon_reload(); - - r_sub_lbl_sync.set_text(&format!("{}s Buffer", cfg.replay_duration_sec)); - rec_sub_lbl_sync.set_text(&format!("{} FPS • {} Mbps", cfg.fps, cfg.record_bitrate_kbps / 1000)); - - banner_after_save.set_text("Settings saved!"); - let b_clear = banner_after_save.clone(); - gtk::glib::timeout_add_local(Duration::from_millis(3000), move || { - b_clear.set_text(""); - gtk::glib::ControlFlow::Break - }); - - stack_after_save.set_visible_child_name("hud"); - w_after_save.resize(740, 280); - }); - - // ========================================================================= - // LIVE STATUS POLLER (Syncs Daemon States, Vectors & Timers Every 300ms) - // ========================================================================= - let rec_status_sync = record_status_lbl.clone(); - let rec_toggle_item_sync = record_toggle_item.clone(); - let rec_state_sync2 = Arc::clone(&record_active_state); - let rec_icon_sync = record_icon_area.clone(); - - let r_status_sync2 = replay_status_lbl.clone(); - let r_toggle_item_sync2 = replay_toggle_item.clone(); - let r_state_sync2 = Arc::clone(&replay_active_state); - let r_icon_sync2 = replay_icon_area.clone(); - let sys_level_sync = sys_level_bar.clone(); - let mic_level_sync = mic_level_bar.clone(); - - let is_running = Arc::new(AtomicBool::new(true)); - let is_running_clone = Arc::clone(&is_running); - - gtk::glib::timeout_add_local(Duration::from_millis(100), move || { - if !is_running_clone.load(Ordering::Relaxed) { - return gtk::glib::ControlFlow::Break; - } - - if let Ok(st) = ipc::query_status() { - // 1. Recording status sync - let prev_rec = rec_state_sync2.swap(st.is_recording, Ordering::Relaxed); - if st.is_recording { - let mins = st.recording_duration_sec / 60; - let secs = st.recording_duration_sec % 60; - rec_status_sync.set_label(&format!("Recording {:02}:{:02}", mins, secs)); - rec_status_sync.style_context().add_class("status-red"); - rec_toggle_item_sync.set_label("Stop (Ctrl+Shift+F9)"); - } else { - rec_status_sync.set_label("Not recording"); - rec_status_sync.style_context().remove_class("status-red"); - rec_toggle_item_sync.set_label("Start (Ctrl+Shift+F9)"); - } - if prev_rec != st.is_recording { - rec_icon_sync.queue_draw(); - } - - // 2. Replay status sync - let prev_replay = r_state_sync2.swap(st.is_replay_active, Ordering::Relaxed); - if st.is_replay_active { - r_status_sync2.set_label("On"); - r_status_sync2.style_context().add_class("status-green"); - r_toggle_item_sync2.set_label("Turn off"); - } else { - r_status_sync2.set_label("Off"); - r_status_sync2.style_context().remove_class("status-green"); - r_toggle_item_sync2.set_label("Turn on"); - } - if prev_replay != st.is_replay_active { - r_icon_sync2.queue_draw(); - } - - // 3. Audio VU meter levels sync - sys_level_sync.set_value(st.system_level_peak as f64); - mic_level_sync.set_value(st.mic_level_peak as f64); - } - gtk::glib::ControlFlow::Continue - }); - - window.connect_destroy(move |_| { - is_running.store(false, Ordering::Relaxed); - }); - - window.show_all(); - }); - - app.run_with_args(&[] as &[&str]); - } -} diff --git a/src/overlay_egui.rs b/src/overlay_egui.rs index 2a579a8..3ed09fa 100644 --- a/src/overlay_egui.rs +++ b/src/overlay_egui.rs @@ -484,7 +484,7 @@ fn pick_folder(current_dir: &str, tx: Sender, is_active: Arc let _ = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; let mut picked = None; - if let Ok(dialog) = (unsafe { CoCreateInstance::<_, IFileOpenDialog>(&FileOpenDialog, None, CLSCTX_INPROC_SERVER) }) { + if let Ok(dialog) = unsafe { CoCreateInstance::<_, IFileOpenDialog>(&FileOpenDialog, None, CLSCTX_INPROC_SERVER) } { unsafe { let _ = dialog.SetTitle(windows::core::w!("Select Recordings Directory")); if dialog.SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM).is_ok() @@ -2115,7 +2115,7 @@ impl ScytheOverlayApp { ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { ui.add_space(4.0); ui.label( - egui::RichText::new("Scythe v0.2.17") + egui::RichText::new(format!("Scythe v{}", crate::updater::CURRENT_VERSION)) .size(10.0) .color(Color32::from_rgb(100, 108, 120)), ); @@ -3022,7 +3022,7 @@ impl ScytheOverlayApp { render_section_card(ui, "Software Updates", accent, |ui| { ui.horizontal(|ui| { ui.label(egui::RichText::new("Scythe Version:").size(12.0).color(Color32::WHITE)); - ui.label(egui::RichText::new("v0.2.17 (Latest)").size(12.0).color(accent).strong()); + ui.label(egui::RichText::new(format!("v{} (Latest)", crate::updater::CURRENT_VERSION)).size(12.0).color(accent).strong()); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if squared_button_sized(ui, "Check for Updates", false, accent, Some(egui::vec2(130.0, 26.0))) { @@ -3934,7 +3934,7 @@ impl eframe::App for ScytheOverlayApp { } #[cfg(target_os = "windows")] -pub fn apply_windows_transparency(title: &str) { +pub fn apply_windows_transparency(_title: &str) { unsafe { use windows::Win32::UI::WindowsAndMessaging::{ EnumWindows, GetWindowThreadProcessId, @@ -4202,13 +4202,15 @@ pub fn run_egui_toast(title: &str, subtitle: &str, icon: crate::overlay::ToastIc }; unsafe extern "system" fn close_toast(hwnd: HWND, lparam: LPARAM) -> BOOL { - let target = lparam.0 as u32; - let mut pid = 0u32; - GetWindowThreadProcessId(hwnd, Some(&mut pid)); - if pid == target { - let _ = PostMessageW(hwnd, WM_CLOSE, None, None); + unsafe { + let target = lparam.0 as u32; + let mut pid = 0u32; + GetWindowThreadProcessId(hwnd, Some(&mut pid)); + if pid == target { + let _ = PostMessageW(hwnd, WM_CLOSE, None, None); + } + BOOL(1) } - BOOL(1) } let _ = EnumWindows(Some(close_toast), LPARAM(prev_pid as isize)); diff --git a/src/settings_ui.rs b/src/settings_ui.rs deleted file mode 100644 index 8b4048d..0000000 --- a/src/settings_ui.rs +++ /dev/null @@ -1,187 +0,0 @@ -use gtk::prelude::*; -use gtk::{Window, WindowType, Box, Orientation, Label, Entry, Button, Switch, ComboBoxText}; -use crate::config::ScytheConfig; - -pub fn open_replay_settings(config_in: &ScytheConfig) { - let window = Window::new(WindowType::Toplevel); - window.set_title("Replay Settings"); - window.set_default_size(320, 200); - - let vbox = Box::new(Orientation::Vertical, 10); - vbox.set_margin(15); - - let duration_label = Label::new(Some("Replay Duration (seconds):")); - duration_label.set_halign(gtk::Align::Start); - let duration_entry = Entry::new(); - duration_entry.set_text(&config_in.replay_duration_sec.to_string()); - - let bitrate_label = Label::new(Some("Bitrate (kbps):")); - bitrate_label.set_halign(gtk::Align::Start); - let bitrate_entry = Entry::new(); - bitrate_entry.set_text(&config_in.replay_bitrate_kbps.to_string()); - - let save_btn = Button::with_label("Save"); - - vbox.pack_start(&duration_label, false, false, 0); - vbox.pack_start(&duration_entry, false, false, 0); - vbox.pack_start(&bitrate_label, false, false, 0); - vbox.pack_start(&bitrate_entry, false, false, 0); - vbox.pack_start(&save_btn, false, false, 0); - - window.add(&vbox); - - let window_clone = window.clone(); - save_btn.connect_clicked(move |_| { - let mut cfg = ScytheConfig::load(); - if let Ok(val) = duration_entry.text().parse() { - cfg.replay_duration_sec = val; - } - if let Ok(val) = bitrate_entry.text().parse() { - cfg.replay_bitrate_kbps = val; - } - let _ = cfg.save(); - ScytheConfig::notify_daemon_reload(); - window_clone.close(); - }); - - let window_esc = window.clone(); - window.connect_key_press_event(move |_, key| { - if key.keyval() == gdk::keys::constants::Escape { - window_esc.close(); - gtk::glib::Propagation::Stop - } else { - gtk::glib::Propagation::Proceed - } - }); - - window.show_all(); -} - -pub fn open_record_settings(config_in: &ScytheConfig) { - let window = Window::new(WindowType::Toplevel); - window.set_title("Record Settings"); - window.set_default_size(320, 150); - - let vbox = Box::new(Orientation::Vertical, 10); - vbox.set_margin(15); - - let bitrate_label = Label::new(Some("Recording Bitrate (kbps):")); - bitrate_label.set_halign(gtk::Align::Start); - let bitrate_entry = Entry::new(); - bitrate_entry.set_text(&config_in.record_bitrate_kbps.to_string()); - - let save_btn = Button::with_label("Save"); - - vbox.pack_start(&bitrate_label, false, false, 0); - vbox.pack_start(&bitrate_entry, false, false, 0); - vbox.pack_start(&save_btn, false, false, 0); - - window.add(&vbox); - - let window_clone = window.clone(); - save_btn.connect_clicked(move |_| { - let mut cfg = ScytheConfig::load(); - if let Ok(val) = bitrate_entry.text().parse() { - cfg.record_bitrate_kbps = val; - } - let _ = cfg.save(); - ScytheConfig::notify_daemon_reload(); - window_clone.close(); - }); - - let window_esc = window.clone(); - window.connect_key_press_event(move |_, key| { - if key.keyval() == gdk::keys::constants::Escape { - window_esc.close(); - gtk::glib::Propagation::Stop - } else { - gtk::glib::Propagation::Proceed - } - }); - - window.show_all(); -} - -pub fn open_general_settings(config_in: &ScytheConfig) { - let window = Window::new(WindowType::Toplevel); - window.set_title("General Settings"); - window.set_default_size(380, 320); - - let vbox = Box::new(Orientation::Vertical, 10); - vbox.set_margin(15); - - // Autostart - let hbox_auto = Box::new(Orientation::Horizontal, 10); - let auto_label = Label::new(Some("Autostart with system:")); - let auto_switch = Switch::new(); - auto_switch.set_active(config_in.autostart); - hbox_auto.pack_start(&auto_label, false, false, 0); - hbox_auto.pack_start(&auto_switch, false, false, 0); - - // Theme - let theme_label = Label::new(Some("UI Theme:")); - theme_label.set_halign(gtk::Align::Start); - let theme_combo = ComboBoxText::new(); - theme_combo.append_text("dark"); - theme_combo.append_text("light"); - theme_combo.set_active(Some(if config_in.ui_color_theme == "dark" { 0 } else { 1 })); - - // Language - let lang_label = Label::new(Some("Language Code (e.g. en, fr):")); - lang_label.set_halign(gtk::Align::Start); - let lang_entry = Entry::new(); - lang_entry.set_text(&config_in.language); - - // Hotkeys - let menu_hk_label = Label::new(Some("Menu Hotkey:")); - menu_hk_label.set_halign(gtk::Align::Start); - let menu_hk_entry = Entry::new(); - menu_hk_entry.set_text(&config_in.menu_hotkey); - - let save_hk_label = Label::new(Some("Save Replay Hotkey:")); - save_hk_label.set_halign(gtk::Align::Start); - let save_hk_entry = Entry::new(); - save_hk_entry.set_text(&config_in.save_hotkey); - - let save_btn = Button::with_label("Save General Settings"); - - vbox.pack_start(&hbox_auto, false, false, 0); - vbox.pack_start(&theme_label, false, false, 0); - vbox.pack_start(&theme_combo, false, false, 0); - vbox.pack_start(&lang_label, false, false, 0); - vbox.pack_start(&lang_entry, false, false, 0); - vbox.pack_start(&menu_hk_label, false, false, 0); - vbox.pack_start(&menu_hk_entry, false, false, 0); - vbox.pack_start(&save_hk_label, false, false, 0); - vbox.pack_start(&save_hk_entry, false, false, 0); - vbox.pack_start(&save_btn, false, false, 0); - - window.add(&vbox); - - let window_clone = window.clone(); - save_btn.connect_clicked(move |_| { - let mut cfg = ScytheConfig::load(); - cfg.autostart = auto_switch.is_active(); - if let Some(txt) = theme_combo.active_text() { - cfg.ui_color_theme = txt.to_string(); - } - cfg.language = lang_entry.text().to_string(); - cfg.menu_hotkey = menu_hk_entry.text().to_string(); - cfg.save_hotkey = save_hk_entry.text().to_string(); - let _ = cfg.save(); - ScytheConfig::notify_daemon_reload(); - window_clone.close(); - }); - - let window_esc = window.clone(); - window.connect_key_press_event(move |_, key| { - if key.keyval() == gdk::keys::constants::Escape { - window_esc.close(); - gtk::glib::Propagation::Stop - } else { - gtk::glib::Propagation::Proceed - } - }); - - window.show_all(); -} diff --git a/src/updater.rs b/src/updater.rs index 21dfd71..6466b12 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -2,6 +2,8 @@ use std::sync::{Arc, Mutex}; use serde::Deserialize; pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const FORGEJO_HOST: &str = "https://git.oiupoyt.space"; +pub const REPO_OWNER_NAME: &str = "oiupoyt/scythe"; pub const GITHUB_REPO: &str = "oiupoyt/scythe"; #[derive(Deserialize, Debug, Clone, PartialEq, Eq, Default)] @@ -24,7 +26,7 @@ pub struct ReleaseInfo { } #[derive(Deserialize, Debug)] -struct GitHubRelease { +struct ForgejoRelease { tag_name: String, name: Option, html_url: String, @@ -54,41 +56,43 @@ pub fn is_newer(latest: &str, current: &str) -> bool { } pub fn check_for_updates() -> Option { - let url = format!("https://api.github.com/repos/{}/releases/latest", GITHUB_REPO); + let urls = [ + format!("{}/api/v1/repos/{}/releases/latest", FORGEJO_HOST, REPO_OWNER_NAME), + format!("https://api.github.com/repos/{}/releases/latest", REPO_OWNER_NAME), + ]; - // Run curl with a 6-second timeout to avoid blocking - let mut cmd = std::process::Command::new("curl"); - cmd.args([ - "-s", - "-H", "User-Agent: scythe-updater", - "-H", "Accept: application/vnd.github.v3+json", - "--max-time", "6", - &url, - ]); - #[cfg(target_os = "windows")] - { - use std::os::windows::process::CommandExt; - cmd.creation_flags(0x08000000); + for url in urls { + let mut cmd = std::process::Command::new("curl"); + cmd.args([ + "-s", + "-H", "User-Agent: scythe-updater", + "-H", "Accept: application/json", + "--max-time", "6", + &url, + ]); + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x08000000); + } + if let Ok(output) = cmd.output() + && output.status.success() + && let Ok(release) = serde_json::from_slice::(&output.stdout) { + let clean_ver = release.tag_name.trim().trim_start_matches('v').to_string(); + let has_update = is_newer(&clean_ver, CURRENT_VERSION); + + return Some(ReleaseInfo { + tag_name: release.tag_name, + version: clean_ver, + name: release.name.unwrap_or_else(|| format!("v{}", CURRENT_VERSION)), + html_url: release.html_url, + release_notes: release.body.unwrap_or_default(), + has_update, + assets: release.assets, + }); + } } - let output = cmd.output().ok()?; - - if !output.status.success() { - return None; - } - - let release: GitHubRelease = serde_json::from_slice(&output.stdout).ok()?; - let clean_ver = release.tag_name.trim().trim_start_matches('v').to_string(); - let has_update = is_newer(&clean_ver, CURRENT_VERSION); - - Some(ReleaseInfo { - tag_name: release.tag_name, - version: clean_ver, - name: release.name.unwrap_or_else(|| format!("Scythe v{}", CURRENT_VERSION)), - html_url: release.html_url, - release_notes: release.body.unwrap_or_default(), - has_update, - assets: release.assets, - }) + None } #[derive(Debug, Clone, PartialEq, Eq, Default)]