From e82467559fb01ac319592d0e2983fbd192c944e1 Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 19:42:43 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20feat(desktop):=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=B3=BB=E7=BB=9F=E6=89=98=E7=9B=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 684 ++++++++++++++++-- Cargo.toml | 2 + crates/desktop/Cargo.toml | 2 + crates/desktop/src/bridges/mod.rs | 2 + crates/desktop/src/bridges/settings.rs | 29 + crates/desktop/src/bridges/tray.rs | 189 +++++ crates/desktop/src/bridges/window_control.rs | 25 +- crates/desktop/ui/blocks/bridges.slint | 1 + .../ui/i18n/en_US/LC_MESSAGES/wsrx-desktop.po | 5 - .../ui/i18n/zh_CN/LC_MESSAGES/wsrx-desktop.po | 5 - .../ui/i18n/zh_TW/LC_MESSAGES/wsrx-desktop.po | 5 - crates/desktop/ui/pages/settings.slint | 8 +- 12 files changed, 855 insertions(+), 102 deletions(-) create mode 100644 crates/desktop/src/bridges/tray.rs diff --git a/Cargo.lock b/Cargo.lock index ee6275c..933806d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,7 +293,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -429,7 +429,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -464,7 +464,30 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", ] [[package]] @@ -519,7 +542,7 @@ dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -657,7 +680,7 @@ checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -693,7 +716,7 @@ dependencies = [ "regex", "rustc-hash 2.1.2", "shlex 1.3.0", - "syn", + "syn 2.0.118", ] [[package]] @@ -823,7 +846,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -844,6 +867,31 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "calloop" version = "0.13.0" @@ -916,6 +964,16 @@ 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", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -970,7 +1028,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.9", ] [[package]] @@ -1001,10 +1059,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1089,7 +1147,7 @@ checksum = "9b5dc851b7ea80cc4f69aa28632385ffd3e0f88014d53db3f2e5a781896ca783" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1299,7 +1357,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.118", ] [[package]] @@ -1310,7 +1368,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1349,7 +1407,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1359,7 +1417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.118", ] [[package]] @@ -1381,7 +1439,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.118", "unicode-xid", ] @@ -1393,7 +1451,7 @@ checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1415,6 +1473,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -1451,7 +1518,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1460,7 +1527,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading", + "libloading 0.8.9", ] [[package]] @@ -1566,7 +1633,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1586,7 +1653,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1803,7 +1870,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1896,7 +1963,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1950,6 +2017,64 @@ dependencies = [ "libc", ] +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2026,7 +2151,7 @@ checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2059,6 +2184,38 @@ dependencies = [ "weezl", ] +[[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", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + [[package]] name = "git-version" version = "0.3.9" @@ -2076,7 +2233,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2090,6 +2247,53 @@ 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.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glob" version = "0.3.3" @@ -2121,7 +2325,7 @@ dependencies = [ "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", - "libloading", + "libloading 0.8.9", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", @@ -2174,12 +2378,75 @@ 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", + "libc", + "system-deps", +] + [[package]] name = "grid" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "h2" version = "0.4.15" @@ -2248,6 +2515,12 @@ dependencies = [ "foldhash", ] +[[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" @@ -2596,7 +2869,7 @@ checksum = "4e66e7fc31239ceba98646825614df8148abc30f8326296c4d762008cc22ddff" dependencies = [ "quote", "serde_json", - "syn", + "syn 2.0.118", ] [[package]] @@ -2968,7 +3241,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3064,7 +3337,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.118", ] [[package]] @@ -3092,7 +3365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3157,6 +3430,30 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + [[package]] name = "libc" version = "0.2.186" @@ -3173,6 +3470,16 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.9" @@ -3211,6 +3518,25 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -3460,7 +3786,9 @@ checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" dependencies = [ "crossbeam-channel", "dpi", + "gtk", "keyboard-types", + "libxdo", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", @@ -3533,7 +3861,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.118", ] [[package]] @@ -3630,7 +3958,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3679,10 +4007,10 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4167,6 +4495,31 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking" version = "2.2.1" @@ -4284,7 +4637,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4319,7 +4672,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4441,7 +4794,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.118", +] + +[[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]] @@ -4450,7 +4823,31 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.12+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", ] [[package]] @@ -4478,7 +4875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5194,7 +5591,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5229,7 +5626,16 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", ] [[package]] @@ -5350,7 +5756,7 @@ dependencies = [ "bindgen", "cc", "flate2", - "heck", + "heck 0.5.0", "pkg-config", "regex", "serde_json", @@ -5416,7 +5822,7 @@ dependencies = [ "fontique", "i-slint-compiler", "spin_on", - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -5543,10 +5949,10 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5642,10 +6048,10 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5691,6 +6097,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" +[[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.118" @@ -5719,7 +6135,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5769,6 +6185,19 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + [[package]] name = "taffy" version = "0.10.1" @@ -5792,6 +6221,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "temp-dir" version = "0.1.16" @@ -5843,7 +6278,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5854,7 +6289,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5969,7 +6404,7 @@ checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" dependencies = [ "as-raw-xcb-connection", "ctor", - "libloading", + "libloading 0.8.9", "pkg-config", "tracing", ] @@ -6025,7 +6460,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -6076,6 +6511,18 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + [[package]] name = "toml" version = "1.1.2+spec-1.1.0" @@ -6084,11 +6531,20 @@ checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", ] [[package]] @@ -6100,6 +6556,30 @@ 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.12+spec-1.1.0" @@ -6107,10 +6587,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 1.0.3", ] [[package]] @@ -6119,7 +6599,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.3", ] [[package]] @@ -6208,7 +6688,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -6263,6 +6743,27 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tray-icon" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png", + "thiserror 2.0.18", + "windows-sys 0.61.2", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6499,6 +7000,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -6525,7 +7032,7 @@ checksum = "482a37f9777480673d3a99c80ac2edc69f92382230440946855fa27ebc47cba6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -6604,7 +7111,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -6887,7 +7394,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -6898,7 +7405,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -7238,6 +7745,15 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "1.0.3" @@ -7316,6 +7832,7 @@ dependencies = [ "directories", "git-version", "i-slint-backend-winit", + "image", "local-ip-address", "once_cell", "open", @@ -7338,11 +7855,22 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "tray-icon", "winit", "winres", "wsrx", ] +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "x11-clipboard" version = "0.9.3" @@ -7373,7 +7901,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", + "libloading 0.8.9", "once_cell", "rustix 1.1.4", "x11rb-protocol", @@ -7485,7 +8013,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -7518,7 +8046,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 1.0.3", "zbus_macros", "zbus_names", "zvariant", @@ -7542,7 +8070,7 @@ checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "zbus-lockstep", "zbus_xml", "zvariant", @@ -7554,10 +8082,10 @@ version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "zbus_names", "zvariant", "zvariant_utils", @@ -7570,7 +8098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow", + "winnow 1.0.3", "zvariant", ] @@ -7609,7 +8137,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -7629,7 +8157,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -7671,7 +8199,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -7713,7 +8241,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow", + "winnow 1.0.3", "zvariant_derive", "zvariant_utils", ] @@ -7724,10 +8252,10 @@ version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "zvariant_utils", ] @@ -7740,6 +8268,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", - "winnow", + "syn 2.0.118", + "winnow 1.0.3", ] diff --git a/Cargo.toml b/Cargo.toml index f7878f5..d7c24cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,8 @@ slint = { version = "1.15", default-features = false, features = [ sys-locale = "0.3" sysinfo = "0.38" toml = "1.0" +tray-icon = "0.24" +image = { version = "0.25", default-features = false, features = ["png"] } winit = "0.30" # build dependencies diff --git a/crates/desktop/Cargo.toml b/crates/desktop/Cargo.toml index 84a39a2..ca64d6b 100644 --- a/crates/desktop/Cargo.toml +++ b/crates/desktop/Cargo.toml @@ -19,10 +19,12 @@ slint = { workspace = true } winit = { workspace = true } directories = { workspace = true } +image = { workspace = true } local-ip-address = { workspace = true } open = { workspace = true } sys-locale = { workspace = true } sysinfo = { workspace = true } +tray-icon = { workspace = true } async-compat = { workspace = true } bitflags = { workspace = true } diff --git a/crates/desktop/src/bridges/mod.rs b/crates/desktop/src/bridges/mod.rs index 4c0bf13..abdab99 100644 --- a/crates/desktop/src/bridges/mod.rs +++ b/crates/desktop/src/bridges/mod.rs @@ -1,5 +1,6 @@ pub mod settings; pub mod system_info; +pub mod tray; pub mod ui_state; pub mod window_control; @@ -9,4 +10,5 @@ pub fn setup(window: &MainWindow) { window_control::setup(window); system_info::setup(window); ui_state::setup(window); + tray::setup(window); } diff --git a/crates/desktop/src/bridges/settings.rs b/crates/desktop/src/bridges/settings.rs index ac9241a..7fae3c2 100644 --- a/crates/desktop/src/bridges/settings.rs +++ b/crates/desktop/src/bridges/settings.rs @@ -90,6 +90,10 @@ pub fn load_config(window: &MainWindow) { bridge.set_language(config.language.into()); bridge.set_running_in_tray(config.running_in_tray); + if config.running_in_tray { + crate::bridges::tray::ensure_created(window); + } + let window_clone = window.as_weak(); bridge.on_change_language(move |lang| { let window = window_clone.upgrade().unwrap(); @@ -97,6 +101,31 @@ pub fn load_config(window: &MainWindow) { bridge.set_language(lang.clone()); slint::select_bundled_translation(lang.as_str()).ok(); + + // Refresh the tray menu labels to match the new language. + if crate::bridges::tray::is_created() { + crate::bridges::tray::destroy(); + crate::bridges::tray::ensure_created(&window); + } + }); + + let window_clone = window.as_weak(); + bridge.on_toggle_running_in_tray(move || { + let window = match window_clone.upgrade() { + Some(w) => w, + None => return, + }; + let bridge = window.global::(); + let new_val = !bridge.get_running_in_tray(); + bridge.set_running_in_tray(new_val); + + if new_val { + crate::bridges::tray::ensure_created(&window); + } else { + crate::bridges::tray::destroy(); + } + + save_config(&window); }); } diff --git a/crates/desktop/src/bridges/tray.rs b/crates/desktop/src/bridges/tray.rs new file mode 100644 index 0000000..d948cf3 --- /dev/null +++ b/crates/desktop/src/bridges/tray.rs @@ -0,0 +1,189 @@ +use std::cell::RefCell; + +use i_slint_backend_winit::WinitWindowAccessor; +use slint::ComponentHandle; +use tray_icon::{ + Icon, MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent, + menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem}, +}; +use tracing::{debug, error}; + +use crate::ui::{MainWindow, SettingsBridge}; + +/// Holds the live tray icon together with the menu item ids we need to +/// match incoming [`MenuEvent`]s against. The icon is not `Send` (it has +/// thread affinity on Windows / macOS), so we keep it in a `thread_local` +/// that is only ever touched from the UI thread. +struct TrayState { + _icon: TrayIcon, + show_item_id: tray_icon::menu::MenuId, + quit_item_id: tray_icon::menu::MenuId, +} + +thread_local! { + static TRAY: RefCell> = const { RefCell::new(None) }; +} + +/// Decode the bundled application logo into the RGBA buffer expected by +/// [`tray_icon::Icon::from_rgba`]. Returns `None` on failure — the tray +/// will still be created, just without a custom icon. +fn load_icon() -> Option { + let bytes: &[u8] = include_bytes!("../../ui/assets/logo.png"); + match image::load_from_memory(bytes) { + Ok(img) => { + let rgba = img.to_rgba8(); + let (width, height) = rgba.dimensions(); + match Icon::from_rgba(rgba.into_raw(), width, height) { + Ok(icon) => Some(icon), + Err(e) => { + error!("Failed to build tray icon from RGBA data: {e}"); + None + } + } + } + Err(e) => { + error!("Failed to decode embedded logo.png for tray icon: {e}"); + None + } + } +} + +/// Localised labels for the tray context menu, picked from the active UI +/// language so the tray matches the rest of the interface. +fn localized_labels(language: &str) -> (&'static str, &'static str) { + match language { + "zh_CN" => ("显示窗口", "退出"), + "zh_TW" => ("顯示視窗", "退出"), + _ => ("Show", "Quit"), + } +} + +/// Register the global tray / menu event handlers. Must be called once +/// during startup, before any tray icon is created. +pub fn setup(window: &MainWindow) { + let window_weak = window.as_weak(); + MenuEvent::set_event_handler(Some(move |event: MenuEvent| { + let id = event.id; + let window_weak = window_weak.clone(); + let _ = slint::invoke_from_event_loop(move || { + handle_menu_event(&window_weak, &id); + }); + })); + + let window_weak = window.as_weak(); + TrayIconEvent::set_event_handler(Some(move |event: TrayIconEvent| { + if let TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } = event + { + let window_weak = window_weak.clone(); + let _ = slint::invoke_from_event_loop(move || { + show_window(&window_weak); + }); + } + })); +} + +fn handle_menu_event(window_weak: &slint::Weak, id: &tray_icon::menu::MenuId) { + // 0 = show, 1 = quit + let action = TRAY.with(|t| { + t.borrow().as_ref().and_then(|state| { + if id == &state.show_item_id { + Some(0u8) + } else if id == &state.quit_item_id { + Some(1u8) + } else { + None + } + }) + }); + + match action { + Some(0) => show_window(window_weak), + Some(1) => crate::launcher::shutdown(window_weak), + _ => {} + } +} + +fn show_window(window_weak: &slint::Weak) { + if let Some(window) = window_weak.upgrade() { + let _ = window.show(); + window.window().with_winit_window(|winit_window| { + winit_window.set_minimized(false); + winit_window.set_focus(); + }); + } +} + +/// Create the tray icon if it does not already exist. Safe to call +/// repeatedly — subsequent calls are no-ops. +pub fn ensure_created(window: &MainWindow) { + let already_exists = TRAY.with(|t| t.borrow().is_some()); + if already_exists { + return; + } + + let language = window.global::().get_language().to_string(); + let (show_label, quit_label) = localized_labels(&language); + + let show_item = MenuItem::new(show_label, true, None); + let quit_item = MenuItem::new(quit_label, true, None); + let show_item_id = show_item.id().clone(); + let quit_item_id = quit_item.id().clone(); + + let menu = Menu::new(); + if let Err(e) = menu.append(&show_item) { + error!("Failed to append show item to tray menu: {e}"); + } + if let Err(e) = menu.append(&PredefinedMenuItem::separator()) { + error!("Failed to append separator to tray menu: {e}"); + } + if let Err(e) = menu.append(&quit_item) { + error!("Failed to append quit item to tray menu: {e}"); + } + + let mut builder = TrayIconBuilder::new() + .with_tooltip("WebSocket Reflector X") + .with_menu(Box::new(menu)) + // Left-click should bring the window back instead of opening the menu; + // the menu is still reachable via right-click. + .show_menu_on_left_click(false); + + if let Some(icon) = load_icon() { + builder = builder.with_icon(icon); + } + + match builder.build() { + Ok(tray) => { + TRAY.with(|t| { + *t.borrow_mut() = Some(TrayState { + _icon: tray, + show_item_id, + quit_item_id, + }); + }); + debug!("System tray icon created"); + } + Err(e) => { + error!("Failed to create system tray icon: {e}"); + } + } +} + +/// Remove the tray icon if it exists. Dropping the [`TrayIcon`] removes it +/// from the system tray. +pub fn destroy() { + TRAY.with(|t| { + if t.borrow().is_some() { + debug!("Destroying system tray icon"); + } + *t.borrow_mut() = None; + }); +} + +/// Whether a tray icon is currently active. +pub fn is_created() -> bool { + TRAY.with(|t| t.borrow().is_some()) +} diff --git a/crates/desktop/src/bridges/window_control.rs b/crates/desktop/src/bridges/window_control.rs index 8d167e7..701ba99 100644 --- a/crates/desktop/src/bridges/window_control.rs +++ b/crates/desktop/src/bridges/window_control.rs @@ -6,7 +6,7 @@ use winit::window::ResizeDirection; use crate::{ launcher, - ui::{MainWindow, WindowControlBridge}, + ui::{MainWindow, SettingsBridge, WindowControlBridge}, }; pub fn setup(window: &MainWindow) { @@ -36,7 +36,15 @@ pub fn setup(window: &MainWindow) { EventResult::Propagate } winit::event::WindowEvent::CloseRequested => { - launcher::shutdown(&window_weak); + if let Some(window) = window_weak.upgrade() { + let settings = window.global::(); + if settings.get_running_in_tray() { + let _ = window.hide(); + crate::bridges::settings::save_config(&window); + } else { + launcher::shutdown(&window_weak); + } + } EventResult::PreventDefault } _ => EventResult::Propagate, @@ -65,8 +73,17 @@ pub fn setup(window: &MainWindow) { let window_clone_pin = window.as_weak(); window_control_bridge.on_close(move || { - // TODO: system tray implementation - launcher::shutdown(&window_clone_pin); + let window = match window_clone_pin.upgrade() { + Some(w) => w, + None => return, + }; + let settings = window.global::(); + if settings.get_running_in_tray() { + let _ = window.hide(); + crate::bridges::settings::save_config(&window); + } else { + launcher::shutdown(&window_clone_pin); + } }); let window_clone_pin = window.as_weak(); diff --git a/crates/desktop/ui/blocks/bridges.slint b/crates/desktop/ui/blocks/bridges.slint index 54d0349..999be57 100644 --- a/crates/desktop/ui/blocks/bridges.slint +++ b/crates/desktop/ui/blocks/bridges.slint @@ -62,4 +62,5 @@ export global SettingsBridge { in-out property api-port: 0; in-out property online: false; callback change-language(lang: string); + callback toggle-running-in-tray(); } diff --git a/crates/desktop/ui/i18n/en_US/LC_MESSAGES/wsrx-desktop.po b/crates/desktop/ui/i18n/en_US/LC_MESSAGES/wsrx-desktop.po index 9562a5a..01d794a 100644 --- a/crates/desktop/ui/i18n/en_US/LC_MESSAGES/wsrx-desktop.po +++ b/crates/desktop/ui/i18n/en_US/LC_MESSAGES/wsrx-desktop.po @@ -137,11 +137,6 @@ msgctxt "SettingsPage" msgid "Running in system tray when closed" msgstr "Running in system tray when closed" -#: crates/desktop/ui/pages/settings.slint:95 -msgctxt "SettingsPage" -msgid " (not implemented yet) " -msgstr " (not implemented yet) " - #: crates/desktop/ui/pages/settings.slint:106 msgctxt "SettingsPage" msgid "Enabled" diff --git a/crates/desktop/ui/i18n/zh_CN/LC_MESSAGES/wsrx-desktop.po b/crates/desktop/ui/i18n/zh_CN/LC_MESSAGES/wsrx-desktop.po index d5d20db..735add4 100644 --- a/crates/desktop/ui/i18n/zh_CN/LC_MESSAGES/wsrx-desktop.po +++ b/crates/desktop/ui/i18n/zh_CN/LC_MESSAGES/wsrx-desktop.po @@ -140,11 +140,6 @@ msgctxt "SettingsPage" msgid "Running in system tray when closed" msgstr "关闭窗口后继续在系统托盘中运行" -#: crates/desktop/ui/pages/settings.slint:95 -msgctxt "SettingsPage" -msgid " (not implemented yet) " -msgstr " (暂未实现) " - #: crates/desktop/ui/pages/settings.slint:106 msgctxt "SettingsPage" msgid "Enabled" diff --git a/crates/desktop/ui/i18n/zh_TW/LC_MESSAGES/wsrx-desktop.po b/crates/desktop/ui/i18n/zh_TW/LC_MESSAGES/wsrx-desktop.po index 968ed2f..d943358 100644 --- a/crates/desktop/ui/i18n/zh_TW/LC_MESSAGES/wsrx-desktop.po +++ b/crates/desktop/ui/i18n/zh_TW/LC_MESSAGES/wsrx-desktop.po @@ -140,11 +140,6 @@ msgctxt "SettingsPage" msgid "Running in system tray when closed" msgstr "關閉窗口後繼續在系統托盤中運行" -#: crates/desktop/ui/pages/settings.slint:95 -msgctxt "SettingsPage" -msgid " (not implemented yet) " -msgstr " (暫未實現) " - #: crates/desktop/ui/pages/settings.slint:106 msgctxt "SettingsPage" msgid "Enabled" diff --git a/crates/desktop/ui/pages/settings.slint b/crates/desktop/ui/pages/settings.slint index 800dbf3..d849570 100644 --- a/crates/desktop/ui/pages/settings.slint +++ b/crates/desktop/ui/pages/settings.slint @@ -92,23 +92,21 @@ export component SettingsPage inherits ScrollView { padding-left: Styles.sizes.p-lg; Text { - text: @tr("Running in system tray when closed") + @tr(" (not implemented yet) "); + text: @tr("Running in system tray when closed"); color: Styles.palette.window-fg; vertical-alignment: center; horizontal-stretch: 1; } Button { - // property toggled: SettingsBridge.running-in-tray; - property toggled: false; + property toggled: SettingsBridge.running-in-tray; icon: toggled ? @image-url("../assets/toggle-right.svg") : @image-url("../assets/toggle-left.svg"); icon-color: toggled ? Styles.palette.info-bg : Styles.palette.window-fg; text: toggled ? @tr("Enabled") : @tr("Disabled"); flat: true; - disabled: true; clicked => { - SettingsBridge.running-in-tray = !toggled; + SettingsBridge.toggle-running-in-tray(); } } } From 04a5c8e7481ec4c240ca4dbd946bedf9e7aceb5a Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 19:53:52 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=90=9Bfix(tray):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=89=98=E7=9B=98=E7=AA=97=E5=8F=A3=E8=81=9A=E7=84=A6=E5=92=8C?= =?UTF-8?q?=E5=B7=A6=E9=94=AE=E7=82=B9=E5=87=BB=E8=8F=9C=E5=8D=95=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/desktop/src/bridges/tray.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/desktop/src/bridges/tray.rs b/crates/desktop/src/bridges/tray.rs index d948cf3..0dc4b06 100644 --- a/crates/desktop/src/bridges/tray.rs +++ b/crates/desktop/src/bridges/tray.rs @@ -112,7 +112,7 @@ fn show_window(window_weak: &slint::Weak) { let _ = window.show(); window.window().with_winit_window(|winit_window| { winit_window.set_minimized(false); - winit_window.set_focus(); + let _ = winit_window.focus_window(); }); } } @@ -149,7 +149,7 @@ pub fn ensure_created(window: &MainWindow) { .with_menu(Box::new(menu)) // Left-click should bring the window back instead of opening the menu; // the menu is still reachable via right-click. - .show_menu_on_left_click(false); + .with_menu_on_left_click(false); if let Some(icon) = load_icon() { builder = builder.with_icon(icon); From bb7ac5d8b46368b03563ce602361e87769ce209a Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 20:56:44 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9Bfix(tray,=20window):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=AA=97=E5=8F=A3=E6=9C=80=E5=B0=8F=E5=8C=96?= =?UTF-8?q?=E5=88=B0=E6=89=98=E7=9B=98=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将托盘相关的窗口隐藏逻辑抽离为hide_to_tray函数,统一两处关闭窗口的处理流程,修复部分平台下hide()被误判为关闭导致程序退出的问题,同时优化窗口恢复显示的逻辑 --- crates/desktop/src/bridges/tray.rs | 5 ++- crates/desktop/src/bridges/window_control.rs | 39 +++++++++++++++----- crates/desktop/src/daemon/mod.rs | 2 + 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/crates/desktop/src/bridges/tray.rs b/crates/desktop/src/bridges/tray.rs index 0dc4b06..2fb2389 100644 --- a/crates/desktop/src/bridges/tray.rs +++ b/crates/desktop/src/bridges/tray.rs @@ -109,10 +109,11 @@ fn handle_menu_event(window_weak: &slint::Weak, id: &tray_icon::menu fn show_window(window_weak: &slint::Weak) { if let Some(window) = window_weak.upgrade() { - let _ = window.show(); window.window().with_winit_window(|winit_window| { + // Restore the window that was hidden via winit::Window::set_visible(false). + winit_window.set_visible(true); winit_window.set_minimized(false); - let _ = winit_window.focus_window(); + winit_window.focus_window(); }); } } diff --git a/crates/desktop/src/bridges/window_control.rs b/crates/desktop/src/bridges/window_control.rs index 701ba99..c608aa7 100644 --- a/crates/desktop/src/bridges/window_control.rs +++ b/crates/desktop/src/bridges/window_control.rs @@ -9,6 +9,33 @@ use crate::{ ui::{MainWindow, SettingsBridge, WindowControlBridge}, }; +/// Hide the window into the system tray when the user has enabled that +/// behaviour. Returns `true` when the window was hidden (the close event +/// has been consumed), `false` when the application should proceed to shut +/// down. +fn hide_to_tray(window: &MainWindow) -> bool { + let settings = window.global::(); + if !settings.get_running_in_tray() { + return false; + } + + // Make sure the tray icon exists before hiding the window, otherwise + // the user would have no way to bring the application back. + crate::bridges::tray::ensure_created(window); + if !crate::bridges::tray::is_created() { + return false; + } + + window.window().with_winit_window(|winit_window| { + // Use the winit API directly instead of Slint's `hide()`. On some + // backends `Window::hide()` is treated as a close, which would make + // `ui.run()` return and terminate the process. + winit_window.set_visible(false); + }); + crate::bridges::settings::save_config(window); + true +} + pub fn setup(window: &MainWindow) { let mut resize_map = HashMap::new(); resize_map.insert("r".to_string(), ResizeDirection::East); @@ -37,11 +64,7 @@ pub fn setup(window: &MainWindow) { } winit::event::WindowEvent::CloseRequested => { if let Some(window) = window_weak.upgrade() { - let settings = window.global::(); - if settings.get_running_in_tray() { - let _ = window.hide(); - crate::bridges::settings::save_config(&window); - } else { + if !hide_to_tray(&window) { launcher::shutdown(&window_weak); } } @@ -77,11 +100,7 @@ pub fn setup(window: &MainWindow) { Some(w) => w, None => return, }; - let settings = window.global::(); - if settings.get_running_in_tray() { - let _ = window.hide(); - crate::bridges::settings::save_config(&window); - } else { + if !hide_to_tray(&window) { launcher::shutdown(&window_clone_pin); } }); diff --git a/crates/desktop/src/daemon/mod.rs b/crates/desktop/src/daemon/mod.rs index f14f72b..19dfbe7 100644 --- a/crates/desktop/src/daemon/mod.rs +++ b/crates/desktop/src/daemon/mod.rs @@ -137,6 +137,7 @@ pub fn setup(ui: &MainWindow) { scope_host.as_str(), ) .await; + save_scopes(&handle_cloned); })) { Ok(_) => {} Err(e) => { @@ -154,6 +155,7 @@ pub fn setup(ui: &MainWindow) { match slint::spawn_local(async_compat::Compat::new(async move { ui_controller::on_scope_del(&state_cloned, handle_cloned.clone(), scope_host.as_str()) .await; + save_scopes(&handle_cloned); })) { Ok(_) => {} Err(e) => { From d96fa2c6fd4a6237fb0ff46641917e131354381f Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 21:18:19 +0800 Subject: [PATCH 4/8] =?UTF-8?q?:bug:fix(desktop=20api=20controller):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=B9=E7=AA=97=E7=AA=97=E5=8F=A3=E4=B8=8D?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整弹窗窗口的显示逻辑,先设置窗口可见再取消最小化状态,确保弹窗可以正确弹出 --- crates/desktop/src/daemon/api_controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/desktop/src/daemon/api_controller.rs b/crates/desktop/src/daemon/api_controller.rs index 3a5a6c8..20b7151 100644 --- a/crates/desktop/src/daemon/api_controller.rs +++ b/crates/desktop/src/daemon/api_controller.rs @@ -468,8 +468,8 @@ async fn popup_window( ) -> Result { slint::invoke_from_event_loop(move || { let ui_handle = state.ui.upgrade().unwrap(); - ui_handle.show().ok(); ui_handle.window().with_winit_window(|winit_window| { + winit_window.set_visible(true); winit_window.set_minimized(false); }); }) From 52c0ec8e7191c910cbc11c2ebca0cdb0d57ccc7c Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 21:24:09 +0800 Subject: [PATCH 5/8] =?UTF-8?q?:sparkles:build(Windows):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0Windows=E5=B9=B3=E5=8F=B0=E4=B8=80=E9=94=AE=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployments/build-windows.ps1 | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 deployments/build-windows.ps1 diff --git a/deployments/build-windows.ps1 b/deployments/build-windows.ps1 new file mode 100644 index 0000000..f1eda26 --- /dev/null +++ b/deployments/build-windows.ps1 @@ -0,0 +1,79 @@ +#!/usr/bin/env pwsh +$ErrorActionPreference = "Stop" + +$AppName = "WebSocketReflectorX" +$AppRoot = "dist" +$DistDir = $AppName +$PortableName = "$AppName-portable-windows-msvc-x86_64.zip" +$InstallerName = "$AppName-installer-windows-msvc-x86_64.exe" + +function Test-Command($cmd) { + return [bool](Get-Command $cmd -ErrorAction SilentlyContinue) +} + +# Validate working directory +if ((Split-Path -Leaf (Get-Location)) -ne $AppName) { + Write-Error "This script must be run from the $AppName directory." + exit 1 +} + +# Validate toolchain dependencies +if (-not (Test-Command cargo)) { + Write-Error "cargo not found. Please install Rust and add it to PATH." + exit 1 +} + +if (-not (Test-Command 7z)) { + Write-Error "7z not found. Please install 7-Zip and add it to PATH." + exit 1 +} + +if (-not (Test-Command makensis)) { + Write-Error "makensis not found. Please install NSIS and add it to PATH." + exit 1 +} + +# Clean previous build artifacts +$pathsToClean = @($AppRoot, $DistDir, "windows/$DistDir", $PortableName, $InstallerName) +foreach ($p in $pathsToClean) { + if (Test-Path $p) { + Remove-Item -Recurse -Force $p + } +} + +# Build release binaries +Write-Host "---- Building release binaries" +cargo build --release --bins + +# Prepare distribution files +Write-Host "---- Preparing distribution files" +New-Item -ItemType Directory -Force -Path $AppRoot | Out-Null +Copy-Item "target/release/wsrx.exe" "$AppRoot/wsrx.exe" +Copy-Item "target/release/wsrx-desktop.exe" "$AppRoot/wsrx-desktop.exe" + +# Optional: include Visual C++ Redistributable installer so setup.nsi can run it. +# Download from: https://aka.ms/vs/17/release/vc_redist.x64.exe +if (Test-Path "windows/vc_redist.x64.exe") { + Copy-Item "windows/vc_redist.x64.exe" "$AppRoot/vc_redist.x64.exe" +} else { + Write-Warning "windows/vc_redist.x64.exe not found. The NSIS installer will fail to install the VC++ redistributable. You can download it from https://aka.ms/vs/17/release/vc_redist.x64.exe" +} + +Rename-Item $AppRoot $DistDir + +# Create portable package +Write-Host "---- Creating portable package" +7z a $PortableName $DistDir + +# Create installer +Write-Host "---- Creating installer" +Move-Item $DistDir "windows/$DistDir" +Copy-Item "windows/$AppName.ico" "windows/$DistDir/$AppName.ico" +makensis "windows/setup.nsi" + +# Move installer to project root +Get-ChildItem "windows/*.exe" | Move-Item -Destination "./$InstallerName" + +Write-Host "---- Done" +Write-Host "Portable package: $PortableName" +Write-Host "Installer: $InstallerName" From 3330f5677058928197e2e8c56184bcca5b9f5626 Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 21:33:00 +0800 Subject: [PATCH 6/8] =?UTF-8?q?:sparkles:feat:=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E5=AE=9E=E4=BE=8B=E7=9A=84=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E5=AD=98=E5=82=A8=E4=B8=8E=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增实例配置保存与加载功能,支持程序退出后保存当前代理实例列表,重启时自动恢复之前的代理配置,包括监听地址、远程地址、标签和作用域主机信息。 --- crates/desktop/src/daemon/api_controller.rs | 8 +- crates/desktop/src/daemon/mod.rs | 185 +++++++++++++++++++- crates/desktop/src/daemon/ui_controller.rs | 3 + crates/desktop/src/launcher.rs | 1 + 4 files changed, 193 insertions(+), 4 deletions(-) diff --git a/crates/desktop/src/daemon/api_controller.rs b/crates/desktop/src/daemon/api_controller.rs index 20b7151..bd88230 100644 --- a/crates/desktop/src/daemon/api_controller.rs +++ b/crates/desktop/src/daemon/api_controller.rs @@ -182,8 +182,10 @@ async fn launch_instance( if instance.label != instance_data.label { instance.label = instance_data.label.clone(); } - - return Ok(Json(instance.data.clone())); + let data = instance.data.clone(); + drop(instances); + super::save_instances(&state).await; + return Ok(Json(data)); } let instance = ProxyInstance::new( @@ -196,6 +198,7 @@ async fn launch_instance( let instance_resp: InstanceData = (&instance).into(); instances.push(instance); drop(instances); + super::save_instances(&state).await; let state_clone = state.clone(); let instance = instance_resp.clone(); @@ -273,6 +276,7 @@ async fn close_instance( } instances.retain(|i| i.local.as_str() != req.local); + super::save_instances(&state).await; match slint::invoke_from_event_loop(move || { let ui_handle = state.ui.upgrade().unwrap(); diff --git a/crates/desktop/src/daemon/mod.rs b/crates/desktop/src/daemon/mod.rs index 19dfbe7..25f81b1 100644 --- a/crates/desktop/src/daemon/mod.rs +++ b/crates/desktop/src/daemon/mod.rs @@ -2,13 +2,18 @@ use std::{process, rc::Rc, sync::Arc}; use api_controller::router; use directories::ProjectDirs; -use model::{ScopeData, ServerState}; +use latency_worker::{update_instance_latency, update_instance_state}; +use model::{InstanceData, ProxyInstance, ScopeData, ServerState}; use serde::{Deserialize, Serialize}; use slint::{ComponentHandle, Model, ToSharedString, VecModel}; use tokio::{net::TcpListener, sync::RwLock}; use tracing::{debug, error, info, warn}; +use wsrx::utils::create_tcp_listener; -use crate::ui::{Instance, InstanceBridge, MainWindow, Scope, ScopeBridge, SettingsBridge}; +use crate::{ + bridges::ui_state::sync_scoped_instance, + ui::{Instance, InstanceBridge, MainWindow, Scope, ScopeBridge, SettingsBridge}, +}; mod api_controller; mod latency_worker; @@ -20,6 +25,11 @@ struct ScopesConfig { scopes: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize)] +struct InstancesConfig { + instances: Vec, +} + pub fn setup(ui: &MainWindow) { use rustls::crypto; @@ -165,6 +175,8 @@ pub fn setup(ui: &MainWindow) { }); let router = router(state_d.clone()); + let state_for_restore = state_d.clone(); + let handle_for_restore = handle.clone(); match slint::spawn_local(async_compat::Compat::new(async move { let listener = match TcpListener::bind(&format!("{}:{}", "127.0.0.1", 3307)).await { @@ -205,6 +217,9 @@ pub fn setup(ui: &MainWindow) { std::process::exit(1); }); + let config_file = proj_dirs.config_dir().join("instances.toml"); + restore_instances(state_for_restore, handle_for_restore, config_file).await; + info!( "API server is listening on [[ {} ]]", listener.local_addr().expect("failed to bind port") @@ -272,6 +287,172 @@ pub fn save_scopes(ui: &slint::Weak) { debug!("Saved scopes to: {:?}", config_file); } +/// Persist the current list of proxy instances to disk so they can be +/// restored on the next launch, even when the application has been running +/// only in the system tray. +pub async fn save_instances(state: &ServerState) { + let instances = state.instances.read().await; + let mut instances_data: Vec = instances.iter().map(|i| i.into()).collect(); + drop(instances); + for instance in &mut instances_data { + instance.latency = -1; + } + save_instances_data(&instances_data); +} + +/// Synchronous variant used during shutdown when an async runtime may no +/// longer be available. Reads the instances from the UI model. +pub fn save_instances_sync(ui: &slint::Weak) { + let window = match ui.upgrade() { + Some(w) => w, + None => return, + }; + let instance_bridge = window.global::(); + let instances = instance_bridge.get_instances(); + let instances = instances + .as_any() + .downcast_ref::>() + .unwrap(); + let instances_data: Vec = instances + .iter() + .map(|i| InstanceData { + label: i.label.to_string(), + remote: i.remote.to_string(), + local: i.local.to_string(), + latency: -1, + scope_host: i.scope_host.to_string(), + }) + .collect(); + save_instances_data(&instances_data); +} + +fn save_instances_data(instances: &[InstanceData]) { + let proj_dirs = match ProjectDirs::from("org", "xdsec", "wsrx") { + Some(dirs) => dirs, + None => { + error!("Unable to find project config directories"); + return; + } + }; + let config_file = proj_dirs.config_dir().join("instances.toml"); + let config_obj = InstancesConfig { + instances: instances.to_vec(), + }; + let config = toml::to_string(&config_obj).unwrap_or_else(|e| { + error!("Failed to serialize instances: {}", e); + String::new() + }); + if config.is_empty() { + return; + } + if let Err(e) = std::fs::create_dir_all(proj_dirs.config_dir()) { + error!("Failed to create config directory: {}", e); + return; + } + if let Err(e) = std::fs::write(&config_file, config) { + error!("Failed to write instances file: {}", e); + } else { + debug!("Saved instances to: {:?}", config_file); + } +} + +fn load_instances_from_path(path: &std::path::Path) -> Vec { + let config = match std::fs::read_to_string(path) { + Ok(config) => config, + Err(_) => return vec![], + }; + let config: InstancesConfig = match toml::from_str(&config) { + Ok(config) => config, + Err(e) => { + error!("Failed to parse instances file: {}", e); + InstancesConfig { instances: vec![] } + } + }; + config.instances +} + +async fn restore_instances( + state: ServerState, + ui: slint::Weak, + config_file: std::path::PathBuf, +) { + let instances_data = load_instances_from_path(&config_file); + if instances_data.is_empty() { + return; + } + + info!("Restoring {} persisted instance(s)...", instances_data.len()); + + let mut restored = Vec::with_capacity(instances_data.len()); + let mut ui_instances = Vec::with_capacity(instances_data.len()); + + for data in instances_data { + match create_tcp_listener(&data.local).await { + Ok(listener) => { + let instance = ProxyInstance::new( + data.label.clone(), + data.scope_host.clone(), + listener, + data.remote.clone(), + ); + let local = instance.local.clone(); + let instance_data: InstanceData = (&instance).into(); + + let state_clone = state.clone(); + tokio::spawn(async move { + let client = reqwest::Client::new(); + match update_instance_latency(&instance_data, &client).await { + Ok(elapsed) => { + update_instance_state(state_clone, &instance_data, elapsed).await + } + Err(_) => update_instance_state(state_clone, &instance_data, -1).await, + }; + }); + + ui_instances.push(Instance { + label: data.label.as_str().into(), + remote: data.remote.as_str().into(), + local: local.as_str().into(), + latency: -1, + scope_host: data.scope_host.as_str().into(), + }); + restored.push(instance); + } + Err((status, msg)) => { + warn!( + "Failed to restore instance {} -> {} (scope: {}): {} {}", + data.local, data.remote, data.scope_host, status, msg + ); + } + } + } + + { + let mut instances = state.instances.write().await; + instances.extend(restored); + } + + let restored_count = ui_instances.len(); + let _ = slint::invoke_from_event_loop(move || { + let ui_handle = match ui.upgrade() { + Some(w) => w, + None => return, + }; + let instance_bridge = ui_handle.global::(); + let instances_rc = instance_bridge.get_instances(); + let instances_rc = instances_rc + .as_any() + .downcast_ref::>() + .unwrap(); + for instance in ui_instances { + instances_rc.push(instance); + } + sync_scoped_instance(ui_handle.as_weak()); + }); + + info!("Restored {} instance(s) with active tunnels", restored_count); +} + fn default_label() -> String { format!("inst-{:06x}", rand::random::()) } diff --git a/crates/desktop/src/daemon/ui_controller.rs b/crates/desktop/src/daemon/ui_controller.rs index a7e9289..33112cb 100644 --- a/crates/desktop/src/daemon/ui_controller.rs +++ b/crates/desktop/src/daemon/ui_controller.rs @@ -53,6 +53,7 @@ pub async fn on_instance_add(state: &ServerState, remote: &str, local: &str) { let label = instance.label.clone(); state.instances.write().await.push(instance); + super::save_instances(&state).await; let state = state.clone(); @@ -89,6 +90,7 @@ pub async fn on_instance_del(state: &ServerState, local: &str) { .write() .await .retain(|instance| instance.local.as_str() != local); + super::save_instances(state).await; let state = state.clone(); let local = local.to_string(); @@ -194,6 +196,7 @@ pub async fn on_scope_del(state: &ServerState, ui: slint::Weak, scop } None => return, }; + super::save_instances(state).await; let scope_host = scope_host.to_string(); let state = state.clone(); diff --git a/crates/desktop/src/launcher.rs b/crates/desktop/src/launcher.rs index f4dfedc..c0de5e7 100644 --- a/crates/desktop/src/launcher.rs +++ b/crates/desktop/src/launcher.rs @@ -114,6 +114,7 @@ pub fn cleanup_runtime_state(ui: &slint::Weak) { if let Some(window) = ui.upgrade() { bridges::settings::save_config(&window); daemon::save_scopes(ui); + daemon::save_instances_sync(ui); } let proj_dirs = match ProjectDirs::from("org", "xdsec", "wsrx") { From 92e7e9e17aa3dcd3cdb11cd79b74b461e35b8592 Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Sun, 12 Jul 2026 22:00:00 +0800 Subject: [PATCH 7/8] =?UTF-8?q?:recycle:refactor(desktop):=20=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2tray-icon=E5=BA=93=E4=B8=BASlint=E5=86=85=E7=BD=AE?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=89=98=E7=9B=98=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 684 ++++------------------------ Cargo.toml | 2 - crates/desktop/Cargo.toml | 2 - crates/desktop/src/bridges/tray.rs | 159 ++----- crates/desktop/ui/blocks/tray.slint | 33 ++ crates/desktop/ui/main.slint | 3 +- 6 files changed, 144 insertions(+), 739 deletions(-) create mode 100644 crates/desktop/ui/blocks/tray.slint diff --git a/Cargo.lock b/Cargo.lock index 933806d..ee6275c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,7 +293,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -429,7 +429,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -464,30 +464,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "syn", ] [[package]] @@ -542,7 +519,7 @@ dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -680,7 +657,7 @@ checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -716,7 +693,7 @@ dependencies = [ "regex", "rustc-hash 2.1.2", "shlex 1.3.0", - "syn 2.0.118", + "syn", ] [[package]] @@ -846,7 +823,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -867,31 +844,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.13.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "calloop" version = "0.13.0" @@ -964,16 +916,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", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1028,7 +970,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.9", + "libloading", ] [[package]] @@ -1059,10 +1001,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1147,7 +1089,7 @@ checksum = "9b5dc851b7ea80cc4f69aa28632385ffd3e0f88014d53db3f2e5a781896ca783" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1357,7 +1299,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.118", + "syn", ] [[package]] @@ -1368,7 +1310,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1407,7 +1349,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1417,7 +1359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.118", + "syn", ] [[package]] @@ -1439,7 +1381,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.118", + "syn", "unicode-xid", ] @@ -1451,7 +1393,7 @@ checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1473,15 +1415,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-sys" version = "0.5.0" @@ -1518,7 +1451,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1527,7 +1460,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading 0.8.9", + "libloading", ] [[package]] @@ -1633,7 +1566,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1653,7 +1586,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1870,7 +1803,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -1963,7 +1896,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -2017,64 +1950,6 @@ dependencies = [ "libc", ] -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2151,7 +2026,7 @@ checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -2184,38 +2059,6 @@ dependencies = [ "weezl", ] -[[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", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - [[package]] name = "git-version" version = "0.3.9" @@ -2233,7 +2076,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -2247,53 +2090,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.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "glob" version = "0.3.3" @@ -2325,7 +2121,7 @@ dependencies = [ "glutin_egl_sys", "glutin_glx_sys", "glutin_wgl_sys", - "libloading 0.8.9", + "libloading", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", @@ -2378,75 +2174,12 @@ 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", - "libc", - "system-deps", -] - [[package]] name = "grid" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "h2" version = "0.4.15" @@ -2515,12 +2248,6 @@ dependencies = [ "foldhash", ] -[[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" @@ -2869,7 +2596,7 @@ checksum = "4e66e7fc31239ceba98646825614df8148abc30f8326296c4d762008cc22ddff" dependencies = [ "quote", "serde_json", - "syn 2.0.118", + "syn", ] [[package]] @@ -3241,7 +2968,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -3337,7 +3064,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.118", + "syn", ] [[package]] @@ -3365,7 +3092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -3430,30 +3157,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", -] - [[package]] name = "libc" version = "0.2.186" @@ -3470,16 +3173,6 @@ dependencies = [ "cc", ] -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.9" @@ -3518,25 +3211,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libxdo" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" -dependencies = [ - "libxdo-sys", -] - -[[package]] -name = "libxdo-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" -dependencies = [ - "libc", - "x11", -] - [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -3786,9 +3460,7 @@ checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" dependencies = [ "crossbeam-channel", "dpi", - "gtk", "keyboard-types", - "libxdo", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", @@ -3861,7 +3533,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.118", + "syn", ] [[package]] @@ -3958,7 +3630,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -4007,10 +3679,10 @@ 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.118", + "syn", ] [[package]] @@ -4495,31 +4167,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", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "parking" version = "2.2.1" @@ -4637,7 +4284,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -4672,7 +4319,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -4794,27 +4441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.118", -] - -[[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", + "syn", ] [[package]] @@ -4823,31 +4450,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.12+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]] @@ -4875,7 +4478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -5591,7 +5194,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -5626,16 +5229,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", + "syn", ] [[package]] @@ -5756,7 +5350,7 @@ dependencies = [ "bindgen", "cc", "flate2", - "heck 0.5.0", + "heck", "pkg-config", "regex", "serde_json", @@ -5822,7 +5416,7 @@ dependencies = [ "fontique", "i-slint-compiler", "spin_on", - "toml_edit 0.25.12+spec-1.1.0", + "toml_edit", ] [[package]] @@ -5949,10 +5543,10 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6048,10 +5642,10 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6097,16 +5691,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" -[[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.118" @@ -6135,7 +5719,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6185,19 +5769,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - [[package]] name = "taffy" version = "0.10.1" @@ -6221,12 +5792,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "temp-dir" version = "0.1.16" @@ -6278,7 +5843,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6289,7 +5854,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6404,7 +5969,7 @@ checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" dependencies = [ "as-raw-xcb-connection", "ctor", - "libloading 0.8.9", + "libloading", "pkg-config", "tracing", ] @@ -6460,7 +6025,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6511,18 +6076,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - [[package]] name = "toml" version = "1.1.2+spec-1.1.0" @@ -6531,20 +6084,11 @@ checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" 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.3", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", + "winnow", ] [[package]] @@ -6556,30 +6100,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.12+spec-1.1.0" @@ -6587,10 +6107,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.3", + "winnow", ] [[package]] @@ -6599,7 +6119,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow", ] [[package]] @@ -6688,7 +6208,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -6743,27 +6263,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "tray-icon" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "once_cell", - "png", - "thiserror 2.0.18", - "windows-sys 0.61.2", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -7000,12 +6499,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - [[package]] name = "version_check" version = "0.9.5" @@ -7032,7 +6525,7 @@ checksum = "482a37f9777480673d3a99c80ac2edc69f92382230440946855fa27ebc47cba6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -7111,7 +6604,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.118", + "syn", "wasm-bindgen-shared", ] @@ -7394,7 +6887,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -7405,7 +6898,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -7745,15 +7238,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.3" @@ -7832,7 +7316,6 @@ dependencies = [ "directories", "git-version", "i-slint-backend-winit", - "image", "local-ip-address", "once_cell", "open", @@ -7855,22 +7338,11 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", - "tray-icon", "winit", "winres", "wsrx", ] -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "x11-clipboard" version = "0.9.3" @@ -7901,7 +7373,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.9", + "libloading", "once_cell", "rustix 1.1.4", "x11rb-protocol", @@ -8013,7 +7485,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", "synstructure", ] @@ -8046,7 +7518,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.3", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -8070,7 +7542,7 @@ checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", "zbus-lockstep", "zbus_xml", "zvariant", @@ -8082,10 +7554,10 @@ version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn", "zbus_names", "zvariant", "zvariant_utils", @@ -8098,7 +7570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 1.0.3", + "winnow", "zvariant", ] @@ -8137,7 +7609,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -8157,7 +7629,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", "synstructure", ] @@ -8199,7 +7671,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn", ] [[package]] @@ -8241,7 +7713,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.3", + "winnow", "zvariant_derive", "zvariant_utils", ] @@ -8252,10 +7724,10 @@ version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn", "zvariant_utils", ] @@ -8268,6 +7740,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.118", - "winnow 1.0.3", + "syn", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index d7c24cb..f7878f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,8 +72,6 @@ slint = { version = "1.15", default-features = false, features = [ sys-locale = "0.3" sysinfo = "0.38" toml = "1.0" -tray-icon = "0.24" -image = { version = "0.25", default-features = false, features = ["png"] } winit = "0.30" # build dependencies diff --git a/crates/desktop/Cargo.toml b/crates/desktop/Cargo.toml index ca64d6b..84a39a2 100644 --- a/crates/desktop/Cargo.toml +++ b/crates/desktop/Cargo.toml @@ -19,12 +19,10 @@ slint = { workspace = true } winit = { workspace = true } directories = { workspace = true } -image = { workspace = true } local-ip-address = { workspace = true } open = { workspace = true } sys-locale = { workspace = true } sysinfo = { workspace = true } -tray-icon = { workspace = true } async-compat = { workspace = true } bitflags = { workspace = true } diff --git a/crates/desktop/src/bridges/tray.rs b/crates/desktop/src/bridges/tray.rs index 2fb2389..4a4da36 100644 --- a/crates/desktop/src/bridges/tray.rs +++ b/crates/desktop/src/bridges/tray.rs @@ -2,50 +2,12 @@ use std::cell::RefCell; use i_slint_backend_winit::WinitWindowAccessor; use slint::ComponentHandle; -use tray_icon::{ - Icon, MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent, - menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem}, -}; use tracing::{debug, error}; -use crate::ui::{MainWindow, SettingsBridge}; - -/// Holds the live tray icon together with the menu item ids we need to -/// match incoming [`MenuEvent`]s against. The icon is not `Send` (it has -/// thread affinity on Windows / macOS), so we keep it in a `thread_local` -/// that is only ever touched from the UI thread. -struct TrayState { - _icon: TrayIcon, - show_item_id: tray_icon::menu::MenuId, - quit_item_id: tray_icon::menu::MenuId, -} +use crate::ui::{MainWindow, SettingsBridge, WsrxTray}; thread_local! { - static TRAY: RefCell> = const { RefCell::new(None) }; -} - -/// Decode the bundled application logo into the RGBA buffer expected by -/// [`tray_icon::Icon::from_rgba`]. Returns `None` on failure — the tray -/// will still be created, just without a custom icon. -fn load_icon() -> Option { - let bytes: &[u8] = include_bytes!("../../ui/assets/logo.png"); - match image::load_from_memory(bytes) { - Ok(img) => { - let rgba = img.to_rgba8(); - let (width, height) = rgba.dimensions(); - match Icon::from_rgba(rgba.into_raw(), width, height) { - Ok(icon) => Some(icon), - Err(e) => { - error!("Failed to build tray icon from RGBA data: {e}"); - None - } - } - } - Err(e) => { - error!("Failed to decode embedded logo.png for tray icon: {e}"); - None - } - } + static TRAY: RefCell> = const { RefCell::new(None) }; } /// Localised labels for the tray context menu, picked from the active UI @@ -58,55 +20,7 @@ fn localized_labels(language: &str) -> (&'static str, &'static str) { } } -/// Register the global tray / menu event handlers. Must be called once -/// during startup, before any tray icon is created. -pub fn setup(window: &MainWindow) { - let window_weak = window.as_weak(); - MenuEvent::set_event_handler(Some(move |event: MenuEvent| { - let id = event.id; - let window_weak = window_weak.clone(); - let _ = slint::invoke_from_event_loop(move || { - handle_menu_event(&window_weak, &id); - }); - })); - - let window_weak = window.as_weak(); - TrayIconEvent::set_event_handler(Some(move |event: TrayIconEvent| { - if let TrayIconEvent::Click { - button: MouseButton::Left, - button_state: MouseButtonState::Up, - .. - } = event - { - let window_weak = window_weak.clone(); - let _ = slint::invoke_from_event_loop(move || { - show_window(&window_weak); - }); - } - })); -} - -fn handle_menu_event(window_weak: &slint::Weak, id: &tray_icon::menu::MenuId) { - // 0 = show, 1 = quit - let action = TRAY.with(|t| { - t.borrow().as_ref().and_then(|state| { - if id == &state.show_item_id { - Some(0u8) - } else if id == &state.quit_item_id { - Some(1u8) - } else { - None - } - }) - }); - - match action { - Some(0) => show_window(window_weak), - Some(1) => crate::launcher::shutdown(window_weak), - _ => {} - } -} - +/// Restore and focus the main window. fn show_window(window_weak: &slint::Weak) { if let Some(window) = window_weak.upgrade() { window.window().with_winit_window(|winit_window| { @@ -118,6 +32,10 @@ fn show_window(window_weak: &slint::Weak) { } } +/// No global setup is required when using Slint's built-in [`SystemTrayIcon`]. +/// The tray instance is created on demand in [`ensure_created`]. +pub fn setup(_window: &MainWindow) {} + /// Create the tray icon if it does not already exist. Safe to call /// repeatedly — subsequent calls are no-ops. pub fn ensure_created(window: &MainWindow) { @@ -128,53 +46,38 @@ pub fn ensure_created(window: &MainWindow) { let language = window.global::().get_language().to_string(); let (show_label, quit_label) = localized_labels(&language); + let window_weak = window.as_weak(); - let show_item = MenuItem::new(show_label, true, None); - let quit_item = MenuItem::new(quit_label, true, None); - let show_item_id = show_item.id().clone(); - let quit_item_id = quit_item.id().clone(); + let tray = match WsrxTray::new() { + Ok(tray) => tray, + Err(e) => { + error!("Failed to create system tray icon: {e}"); + return; + } + }; - let menu = Menu::new(); - if let Err(e) = menu.append(&show_item) { - error!("Failed to append show item to tray menu: {e}"); - } - if let Err(e) = menu.append(&PredefinedMenuItem::separator()) { - error!("Failed to append separator to tray menu: {e}"); - } - if let Err(e) = menu.append(&quit_item) { - error!("Failed to append quit item to tray menu: {e}"); - } + tray.set_show_label(show_label.into()); + tray.set_quit_label(quit_label.into()); - let mut builder = TrayIconBuilder::new() - .with_tooltip("WebSocket Reflector X") - .with_menu(Box::new(menu)) - // Left-click should bring the window back instead of opening the menu; - // the menu is still reachable via right-click. - .with_menu_on_left_click(false); + let weak = window_weak.clone(); + tray.on_show_window(move || show_window(&weak)); - if let Some(icon) = load_icon() { - builder = builder.with_icon(icon); - } + let weak = window_weak; + tray.on_quit(move || crate::launcher::shutdown(&weak)); - match builder.build() { - Ok(tray) => { - TRAY.with(|t| { - *t.borrow_mut() = Some(TrayState { - _icon: tray, - show_item_id, - quit_item_id, - }); - }); - debug!("System tray icon created"); - } - Err(e) => { - error!("Failed to create system tray icon: {e}"); - } + if let Err(e) = tray.show() { + error!("Failed to show system tray icon: {e}"); + return; } + + TRAY.with(|t| { + *t.borrow_mut() = Some(tray); + }); + debug!("System tray icon created"); } -/// Remove the tray icon if it exists. Dropping the [`TrayIcon`] removes it -/// from the system tray. +/// Hide and drop the tray icon if it exists. Dropping the [`WsrxTray`] +/// instance removes it from the system tray. pub fn destroy() { TRAY.with(|t| { if t.borrow().is_some() { diff --git a/crates/desktop/ui/blocks/tray.slint b/crates/desktop/ui/blocks/tray.slint new file mode 100644 index 0000000..c68f245 --- /dev/null +++ b/crates/desktop/ui/blocks/tray.slint @@ -0,0 +1,33 @@ +export component WsrxTray inherits SystemTrayIcon { + in-out property show-label: "Show"; + in-out property quit-label: "Quit"; + + icon: @image-url("../assets/logo.png"); + tooltip: "WebSocket Reflector X"; + title: "WebSocket Reflector X"; + + callback show-window(); + callback quit(); + + clicked => { + root.show-window(); + } + + Menu { + MenuItem { + title: root.show-label; + activated => { + root.show-window(); + } + } + + MenuSeparator {} + + MenuItem { + title: root.quit-label; + activated => { + root.quit(); + } + } + } +} diff --git a/crates/desktop/ui/main.slint b/crates/desktop/ui/main.slint index 73b559f..30b529c 100644 --- a/crates/desktop/ui/main.slint +++ b/crates/desktop/ui/main.slint @@ -2,6 +2,7 @@ import "fonts/reverier-mono-regular.ttf"; import { Styles } from "widgets/styles.slint"; import { SideBar } from "blocks/side-bar.slint"; +import { WsrxTray } from "blocks/tray.slint"; import { WindowControlBridge, Log, SystemInfoBridge, Instance, Scope, InstanceBridge, ScopeBridge, SettingsBridge } from "blocks/bridges.slint"; import { FramelessWindow } from "widgets/frameless-window.slint"; @@ -9,7 +10,7 @@ import { TitleBar } from "blocks/title-bar.slint"; import { UiState } from "blocks/globals.slint"; import { Stack } from "blocks/stack.slint"; -export { WindowControlBridge, Log, SystemInfoBridge, Instance, Scope, InstanceBridge, ScopeBridge, SettingsBridge, UiState } +export { WindowControlBridge, Log, SystemInfoBridge, Instance, Scope, InstanceBridge, ScopeBridge, SettingsBridge, UiState, WsrxTray } export component MainWindow inherits FramelessWindow { title: "WebSocket Reflector X"; From 58a565f9e19d293c2b502e79bd66dea77f897521 Mon Sep 17 00:00:00 2001 From: Teror Fox Date: Mon, 13 Jul 2026 00:32:35 +0800 Subject: [PATCH 8/8] =?UTF-8?q?:bug:fix(desktop):=20=E5=8D=87=E7=BA=A7=20S?= =?UTF-8?q?lint=20=E5=88=B0=201.17=20=E4=BF=AE=E5=A4=8D=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=89=98=E7=9B=98=E5=9B=BE=E6=A0=87=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SystemTrayIcon 在 Slint 1.17 才正式稳定,1.15 中为实验性功能 且存在实现缺陷,导致托盘图标创建后不可见。升级至 1.17 并 启用 system-tray feature 即可解决。 --- Cargo.lock | 27 ++++++++++++++++++++++++++- Cargo.toml | 7 ++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee6275c..3213603 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,7 +541,7 @@ dependencies = [ "log", "num-rational", "num-traits", - "pastey", + "pastey 0.1.1", "rayon", "thiserror 2.0.18", "v_frame", @@ -2541,6 +2541,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b7d306bc9d6fe1d1fd5e775837ec1fa1e41d1c8063789181b7de631bd5db41c" dependencies = [ + "async-channel", "auto_enums", "bitflags 2.13.0", "cfg-if", @@ -2554,6 +2555,7 @@ dependencies = [ "i-slint-core-macros", "icu_normalizer", "image", + "ksni", "lyon_algorithms", "lyon_extra", "lyon_geom", @@ -3133,6 +3135,23 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "ksni" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9eeb3f510b6148ae68f963af2c1fbb0de4d9e4e05f82813cfb319837c3ad2b" +dependencies = [ + "async-executor", + "async-io", + "async-lock", + "futures-channel", + "futures-lite", + "futures-util", + "pastey 0.2.3", + "serde", + "zbus", +] + [[package]] name = "kurbo" version = "0.13.1" @@ -4241,6 +4260,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + [[package]] name = "pathdiff" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index f7878f5..efd907d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ url = { version = "2.5" } # GUI async-compat = { version = "0.2" } -i-slint-backend-winit = "1.15" +i-slint-backend-winit = "1.17" open = "5.3" reqwest = { version = "0.13", features = [ @@ -58,7 +58,7 @@ reqwest = { version = "0.13", features = [ "json", ] } -slint = { version = "1.15", default-features = false, features = [ +slint = { version = "1.17", default-features = false, features = [ "accessibility", "backend-winit", "compat-1-2", @@ -67,6 +67,7 @@ slint = { version = "1.15", default-features = false, features = [ "renderer-software", "serde", "std", + "system-tray", ] } sys-locale = "0.3" @@ -78,5 +79,5 @@ winit = "0.30" build-target = "0.8" git-version = "0.3" rustc_version = "0.4" -slint-build = "1.15" +slint-build = "1.17" winres = "0.1"