From d29a82a74160092b6b8b8f70ba1ea6c5f166677d Mon Sep 17 00:00:00 2001 From: jvthompson Date: Sat, 8 Aug 2026 08:56:33 -0500 Subject: [PATCH] Initial Version of Desktop Icon Hider. --- .claude/settings.local.json | 8 + .gitignore | 1 + Cargo.lock | 1377 +++++++++++++++++++++++++++++++++++ Cargo.toml | 38 + assets/tray-32.png | Bin 0 -> 998 bytes assets/tray.ico | Bin 0 -> 7520 bytes build.rs | 7 + src/autostart.rs | 67 ++ src/desktop.rs | 235 ++++++ src/hook.rs | 63 ++ src/main.rs | 133 ++++ src/state.rs | 20 + src/tray.rs | 57 ++ 13 files changed, 2006 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 assets/tray-32.png create mode 100644 assets/tray.ico create mode 100644 build.rs create mode 100644 src/autostart.rs create mode 100644 src/desktop.rs create mode 100644 src/hook.rs create mode 100644 src/main.rs create mode 100644 src/state.rs create mode 100644 src/tray.rs diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b84e882 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(cargo --version)", + "Bash(rustc --version)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..57ecc56 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1377 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[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]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags", + "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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "desktop-icon-hider" +version = "0.1.0" +dependencies = [ + "image", + "log", + "muda", + "tray-icon", + "windows", + "windows-registry", + "winresource", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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 = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags", + "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.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags", + "serde", + "unicode-segmentation", +] + +[[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", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[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 = "libredox" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +dependencies = [ + "libc", +] + +[[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 = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.19", + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[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 = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror 2.0.19", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +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_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.19", + "windows-sys", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml 1.1.4+spec-1.1.0", + "version_check", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8289022 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "desktop-icon-hider" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "desktop-icon-hider" +path = "src/main.rs" + +[dependencies] +tray-icon = "0.24" +muda = "0.19" +windows-registry = "0.6" +image = { version = "0.25", default-features = false, features = ["png"] } +log = "0.4" + +[dependencies.windows] +version = "0.62" +features = [ + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_UI_Controls", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_System_Threading", + "Win32_System_Memory", + "Win32_System_Diagnostics_Debug", + "Win32_Graphics_Gdi", + "Win32_UI_HiDpi", + "Win32_Security", +] + +[build-dependencies] +winresource = "0.1" + +[profile.release] +opt-level = "s" +lto = true +strip = true diff --git a/assets/tray-32.png b/assets/tray-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ef877e71de36110641a1d0ef2239149ec1718d8b GIT binary patch literal 998 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 KW6D4t90LI6-=MSr literal 0 HcmV?d00001 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..32a9a09 --- /dev/null +++ b/build.rs @@ -0,0 +1,7 @@ +fn main() { + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + let mut res = winresource::WindowsResource::new(); + res.set_icon("assets/tray.ico"); + res.compile().expect("failed to embed icon resource"); + } +} diff --git a/src/autostart.rs b/src/autostart.rs new file mode 100644 index 0000000..149ef48 --- /dev/null +++ b/src/autostart.rs @@ -0,0 +1,67 @@ +use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND; +use windows::core::HRESULT; +use windows_registry::{CURRENT_USER, Result}; + +const RUN_KEY_PATH: &str = r"Software\Microsoft\Windows\CurrentVersion\Run"; +const APP_KEY_PATH: &str = r"Software\DesktopIconHider"; +const VALUE_NAME: &str = "DesktopIconHider"; +const FIRST_RUN_MARKER: &str = "AutostartConfigured"; + +fn exe_path_quoted() -> String { + let exe_path = std::env::current_exe() + .expect("cannot resolve current exe path") + .to_string_lossy() + .into_owned(); + format!("\"{exe_path}\"") +} + +fn is_not_found(e: &windows::core::Error) -> bool { + e.code() == HRESULT::from_win32(ERROR_FILE_NOT_FOUND.0) +} + +/// True if the Run key currently points at this exe. A missing key or value means +/// autostart is simply off (`Ok(false)`), not an error condition. +pub fn is_enabled() -> Result { + match CURRENT_USER.open(RUN_KEY_PATH) { + Ok(key) => match key.get_string(VALUE_NAME) { + Ok(value) => Ok(value == exe_path_quoted()), + Err(e) if is_not_found(&e) => Ok(false), + Err(e) => Err(e), + }, + Err(e) if is_not_found(&e) => Ok(false), + Err(e) => Err(e), + } +} + +/// Writes or removes the Run-key value. +pub fn set_enabled(enabled: bool) -> Result<()> { + if enabled { + CURRENT_USER + .create(RUN_KEY_PATH)? + .set_string(VALUE_NAME, exe_path_quoted())?; + Ok(()) + } else { + match CURRENT_USER.open(RUN_KEY_PATH) { + Ok(key) => match key.remove_value(VALUE_NAME) { + Ok(()) => Ok(()), + Err(e) if is_not_found(&e) => Ok(()), + Err(e) => Err(e), + }, + Err(e) if is_not_found(&e) => Ok(()), + Err(e) => Err(e), + } + } +} + +/// On a genuine first-ever run (no marker yet under our own app key), force-enables +/// autostart once and writes the marker — so it defaults on for new installs, but never +/// re-forces itself back on after the user explicitly disables it via the tray menu. +/// Call once at startup, before reading `is_enabled()` for the menu's initial state. +pub fn ensure_first_run_default() -> Result<()> { + let app_key = CURRENT_USER.create(APP_KEY_PATH)?; + if app_key.get_u32(FIRST_RUN_MARKER).is_err() { + set_enabled(true)?; + app_key.set_u32(FIRST_RUN_MARKER, 1)?; + } + Ok(()) +} diff --git a/src/desktop.rs b/src/desktop.rs new file mode 100644 index 0000000..05595d1 --- /dev/null +++ b/src/desktop.rs @@ -0,0 +1,235 @@ +use std::ffi::c_void; +use std::mem::size_of; + +use windows::Win32::Foundation::{CloseHandle, HWND, LPARAM, POINT, WPARAM}; +use windows::Win32::Graphics::Gdi::ScreenToClient; +use windows::Win32::System::Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory}; +use windows::Win32::System::Memory::{ + MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx, +}; +use windows::Win32::System::Threading::{ + OpenProcess, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, +}; +use windows::Win32::UI::Controls::{LVHITTESTINFO, LVHT_NOWHERE, LVM_HITTEST}; +use windows::Win32::UI::WindowsAndMessaging::{ + EnumWindows, FindWindowExW, FindWindowW, GA_PARENT, GetAncestor, GetClassNameW, + GetWindowThreadProcessId, IsWindowVisible, SMTO_ABORTIFHUNG, SendMessageTimeoutW, + SendMessageW, WM_COMMAND, WindowFromPoint, +}; +use windows::core::{BOOL, w}; + +/// The documented "Show desktop icons" toggle command, sent as a WM_COMMAND wParam +/// to Explorer's desktop view (the same command right-click -> View -> "Show desktop +/// icons" sends). +const TOGGLE_DESKTOP_ICONS_CMD: usize = 0x7402; + +/// LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON — not exposed as a +/// combined constant by the `windows` crate, so it's reconstructed here. +const LVHT_ONITEM_MASK: u32 = 0x000E; + +/// True if the given screen point landed on the desktop's *empty* area. +/// +/// When desktop icons are visible, the window under the cursor is the real desktop +/// SysListView32 (walked up through SHELLDLL_DefView -> Progman/WorkerW, to rule out +/// other SysListView32 controls such as File Explorer's file list); a list-view hit +/// test at that point must report "nowhere" rather than an actual icon. +/// +/// When desktop icons are currently hidden, Explorer hides the SysListView32 window +/// itself (rather than just its items), so WindowFromPoint skips straight past it to +/// its parent SHELLDLL_DefView (or, if that's absent too, Progman/WorkerW directly). +/// In that state there are no icons to click at all, so landing on those surfaces +/// counts as empty space too — this is what lets a second double-click toggle icons +/// back on. +/// +/// Any other mismatch along the way conservatively returns false so we never toggle +/// when unsure. +pub fn is_desktop_empty_space_click(screen_x: i32, screen_y: i32) -> bool { + unsafe { + let pt = POINT { x: screen_x, y: screen_y }; + let hwnd = WindowFromPoint(pt); + if hwnd.is_invalid() { + return false; + } + + if class_name_is(hwnd, "SysListView32") { + let parent = GetAncestor(hwnd, GA_PARENT); + if !class_name_is(parent, "SHELLDLL_DefView") { + return false; + } + let grandparent = GetAncestor(parent, GA_PARENT); + if !(class_name_is(grandparent, "Progman") || class_name_is(grandparent, "WorkerW")) { + return false; + } + + let mut client_pt = pt; + if !ScreenToClient(hwnd, &mut client_pt).as_bool() { + return false; + } + + match remote_hit_test(hwnd, client_pt) { + Some(info) => { + let flags = info.flags.0; + (flags & LVHT_NOWHERE.0) != 0 && (flags & LVHT_ONITEM_MASK) == 0 + } + None => false, + } + } else if class_name_is(hwnd, "SHELLDLL_DefView") { + let parent = GetAncestor(hwnd, GA_PARENT); + class_name_is(parent, "Progman") || class_name_is(parent, "WorkerW") + } else { + class_name_is(hwnd, "Progman") || class_name_is(hwnd, "WorkerW") + } + } +} + +/// Sends LVM_HITTEST to `hwnd`, a SysListView32 owned by another process (Explorer). +/// +/// SendMessage does not marshal arbitrary struct pointers across a process boundary — +/// only a small whitelist of messages (WM_COPYDATA, WM_GETTEXT, etc.) get that +/// treatment. Passing a pointer to *our* stack directly, as an earlier version of this +/// code did, made Explorer write the hit-test result through an address that's +/// meaningless in its own address space: sometimes landing on unmapped memory (crashing +/// Explorer, which Windows then restarts) and sometimes silently corrupting or reading +/// back garbage. The correct approach is the same one automation tools use for +/// cross-process list-view access: allocate a buffer inside Explorer's own process, +/// write the input there, send the message, then read the result back. +unsafe fn remote_hit_test(hwnd: HWND, client_pt: POINT) -> Option { + unsafe { + let mut pid = 0u32; + GetWindowThreadProcessId(hwnd, Some(&mut pid)); + if pid == 0 { + return None; + } + + let process = OpenProcess( + PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, + false, + pid, + ) + .ok()?; + + let size = size_of::(); + let remote_ptr = VirtualAllocEx(process, None, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if remote_ptr.is_null() { + let _ = CloseHandle(process); + return None; + } + + let input = LVHITTESTINFO { + pt: client_pt, + ..Default::default() + }; + + let result = if WriteProcessMemory( + process, + remote_ptr, + &input as *const LVHITTESTINFO as *const c_void, + size, + None, + ) + .is_ok() + { + SendMessageW( + hwnd, + LVM_HITTEST, + Some(WPARAM(0)), + Some(LPARAM(remote_ptr as isize)), + ); + + let mut out = LVHITTESTINFO::default(); + if ReadProcessMemory( + process, + remote_ptr, + &mut out as *mut LVHITTESTINFO as *mut c_void, + size, + None, + ) + .is_ok() + { + Some(out) + } else { + None + } + } else { + None + }; + + let _ = VirtualFreeEx(process, remote_ptr, 0, MEM_RELEASE); + let _ = CloseHandle(process); + result + } +} + +unsafe fn class_name_is(hwnd: HWND, expected: &str) -> bool { + if hwnd.is_invalid() { + return false; + } + let mut buf = [0u16; 256]; + let len = unsafe { GetClassNameW(hwnd, &mut buf) }; + if len <= 0 { + return false; + } + String::from_utf16_lossy(&buf[..len as usize]).eq_ignore_ascii_case(expected) +} + +/// Sends the "Show desktop icons" toggle command to Explorer's desktop view, then reads +/// back the actual resulting visibility rather than assuming success. Returns +/// `Some(true)`/`Some(false)` for icons now shown/hidden, or `None` if the toggle +/// couldn't be delivered or confirmed. Reading back (instead of tracking our own +/// "current state") avoids drift if the user toggles icons some other way (e.g. +/// Explorer's own View menu) between our calls. +pub fn toggle_desktop_icons() -> Option { + let defview = unsafe { find_shelldll_defview() }?; + + let sent = unsafe { + let mut result = 0usize; + SendMessageTimeoutW( + defview, + WM_COMMAND, + WPARAM(TOGGLE_DESKTOP_ICONS_CMD), + LPARAM(0), + SMTO_ABORTIFHUNG, + 1000, + Some(&mut result), + ) + }; + if sent.0 == 0 { + log::warn!("toggle_desktop_icons: SendMessageTimeoutW failed or timed out"); + return None; + } + + // SendMessageTimeoutW blocks until Explorer's WM_COMMAND handler returns, so the + // listview's visibility already reflects the new state by now. + let listview = unsafe { FindWindowExW(Some(defview), None, w!("SysListView32"), None) }.ok()?; + Some(unsafe { IsWindowVisible(listview) }.as_bool()) +} + +/// Locates Explorer's desktop-view window. Normally a child of "Progman", but on some +/// multi-monitor / wallpaper-slideshow configurations Explorer reparents it under a +/// sibling "WorkerW" window instead, so that's checked as a fallback. +unsafe fn find_shelldll_defview() -> Option { + unsafe { + if let Ok(progman) = FindWindowW(w!("Progman"), None) { + if let Ok(dv) = FindWindowExW(Some(progman), None, w!("SHELLDLL_DefView"), None) { + return Some(dv); + } + } + + let mut found: Option = None; + let _ = EnumWindows( + Some(enum_worker_w), + LPARAM(&mut found as *mut Option as isize), + ); + found + } +} + +unsafe extern "system" fn enum_worker_w(hwnd: HWND, lparam: LPARAM) -> BOOL { + unsafe { + if let Ok(dv) = FindWindowExW(Some(hwnd), None, w!("SHELLDLL_DefView"), None) { + *(lparam.0 as *mut Option) = Some(dv); + return BOOL(0); // stop enumerating + } + } + BOOL(1) // continue +} diff --git a/src/hook.rs b/src/hook.rs new file mode 100644 index 0000000..e882ea8 --- /dev/null +++ b/src/hook.rs @@ -0,0 +1,63 @@ +use std::cell::Cell; +use std::sync::atomic::Ordering; + +use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; +use windows::Win32::System::Threading::GetCurrentThreadId; +use windows::Win32::UI::Input::KeyboardAndMouse::{GetDoubleClickTime}; +use windows::Win32::UI::WindowsAndMessaging::{ + CallNextHookEx, GetSystemMetrics, PostThreadMessageW, MSLLHOOKSTRUCT, SM_CXDOUBLECLK, + SM_CYDOUBLECLK, WM_LBUTTONDOWN, +}; + +use crate::state::{PAUSED, WM_APP_CANDIDATE_DBLCLICK}; + +thread_local! { + // (x, y, tick_ms) of the last recorded left-button-down. + static LAST_CLICK: Cell<(i32, i32, u32)> = const { Cell::new((0, 0, 0)) }; +} + +/// Low-level mouse hook callback (`WH_MOUSE_LL`). Runs on the thread that installed +/// the hook, for every left-button-down system-wide. Detects double-clicks manually +/// (WH_MOUSE_LL never sees a synthesized WM_LBUTTONDBLCLK) and, on a match, posts a +/// thread message so the actual hit-test/toggle work happens on the main loop instead +/// of inside this callback. Always chains via CallNextHookEx and never swallows the +/// click, so double-click-to-open still works normally on real icons. +pub unsafe extern "system" fn low_level_mouse_proc( + code: i32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + if code >= 0 && wparam.0 as u32 == WM_LBUTTONDOWN && !PAUSED.load(Ordering::Relaxed) { + let info = unsafe { &*(lparam.0 as *const MSLLHOOKSTRUCT) }; + let (x, y, t) = (info.pt.x, info.pt.y, info.time); + + LAST_CLICK.with(|cell| { + let (lx, ly, lt) = cell.get(); + let dt = t.wrapping_sub(lt); + let dx = (x - lx).abs(); + let dy = (y - ly).abs(); + + let is_double_click = lt != 0 + && dt <= unsafe { GetDoubleClickTime() } + && dx <= unsafe { GetSystemMetrics(SM_CXDOUBLECLK) } + && dy <= unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }; + + if is_double_click { + // Reset so a third click isn't misread as another double-click. + cell.set((0, 0, 0)); + unsafe { + let _ = PostThreadMessageW( + GetCurrentThreadId(), + WM_APP_CANDIDATE_DBLCLICK, + WPARAM(x as usize), + LPARAM(y as isize), + ); + } + } else { + cell.set((x, y, t)); + } + }); + } + + unsafe { CallNextHookEx(None, code, wparam, lparam) } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b5a20ee --- /dev/null +++ b/src/main.rs @@ -0,0 +1,133 @@ +#![windows_subsystem = "windows"] + +mod autostart; +mod desktop; +mod hook; +mod state; +mod tray; + +use std::sync::atomic::Ordering; + +use muda::MenuEvent; +use windows::Win32::Foundation::{ERROR_ALREADY_EXISTS, GetLastError, LPARAM, WPARAM}; +use windows::Win32::System::Threading::{CreateMutexW, GetCurrentThreadId}; +use windows::Win32::UI::HiDpi::{ + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, SetProcessDpiAwarenessContext, +}; +use windows::Win32::UI::WindowsAndMessaging::{ + DispatchMessageW, GetMessageW, PostQuitMessage, PostThreadMessageW, SetWindowsHookExW, + TranslateMessage, UnhookWindowsHookEx, MSG, WH_MOUSE_LL, +}; +use windows::core::w; + +fn main() { + // If another instance already holds this mutex, exit immediately — a second copy + // would install a second mouse hook, and every double-click would toggle icons + // twice (net no-op). CreateMutexW returns Ok(handle) even when the mutex already + // existed, so the only reliable signal is GetLastError(), checked unconditionally. + let _single_instance_mutex = unsafe { + let handle = CreateMutexW(None, false, w!("DesktopIconHider-9F2E7B1A-SingleInstance")); + if GetLastError() == ERROR_ALREADY_EXISTS { + return; + } + handle + }; + + // Must happen before any window is created (including the tray icon's hidden + // window) — otherwise Windows DPI-virtualizes our coordinate space, which + // desyncs from the raw physical-pixel coordinates the mouse hook reports + // whenever the display scale isn't 100%. + unsafe { + let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + + if let Err(e) = autostart::ensure_first_run_default() { + log::warn!("failed to configure autostart default: {e}"); + } + + let tray_handles = tray::build_tray(); + let pause_item = tray_handles.pause_item.clone(); + let autostart_item = tray_handles.autostart_item.clone(); + let (toggle_id, pause_id, autostart_id, exit_id) = ( + tray_handles.toggle_id.clone(), + tray_handles.pause_item.id().clone(), + tray_handles.autostart_item.id().clone(), + tray_handles.exit_id.clone(), + ); + + // muda's event handler must be Send + Sync, but MenuItem/CheckMenuItem (needed to + // update the pause item's label and correct the autostart checkbox) aren't — both + // are Rc-based. So the handler only touches ids, atomics, and plain FFI calls (the + // desktop/registry calls don't touch any Rc-based muda type), then posts a + // thread message for anything that needs the actual menu item; the main loop, which + // owns those items locally, handles the rest. + MenuEvent::set_event_handler(Some(move |event: MenuEvent| { + if event.id == toggle_id { + desktop::toggle_desktop_icons(); + } else if event.id == pause_id { + state::PAUSED.fetch_xor(true, Ordering::Relaxed); + unsafe { + let _ = PostThreadMessageW( + GetCurrentThreadId(), + state::WM_APP_PAUSE_TOGGLED, + WPARAM(0), + LPARAM(0), + ); + } + } else if event.id == autostart_id { + // muda already flipped the native checkmark before this handler ran; this + // just performs the actual registry write and reports back the real result + // so the main loop can correct the checkbox if the write failed. + let desired = !autostart::is_enabled().unwrap_or(false); + if let Err(e) = autostart::set_enabled(desired) { + log::warn!("failed to update autostart: {e}"); + } + let actual = autostart::is_enabled().unwrap_or(false); + unsafe { + let _ = PostThreadMessageW( + GetCurrentThreadId(), + state::WM_APP_AUTOSTART_TOGGLED, + WPARAM(actual as usize), + LPARAM(0), + ); + } + } else if event.id == exit_id { + unsafe { PostQuitMessage(0) }; + } + })); + + let hook = unsafe { SetWindowsHookExW(WH_MOUSE_LL, Some(hook::low_level_mouse_proc), None, 0) } + .expect("failed to install low-level mouse hook"); + + let mut msg = MSG::default(); + unsafe { + while GetMessageW(&mut msg, None, 0, 0).as_bool() { + if msg.message == state::WM_APP_CANDIDATE_DBLCLICK { + let x = msg.wParam.0 as i32; + let y = msg.lParam.0 as i32; + if !state::PAUSED.load(Ordering::Relaxed) + && desktop::is_desktop_empty_space_click(x, y) + { + desktop::toggle_desktop_icons(); + } + } else if msg.message == state::WM_APP_PAUSE_TOGGLED { + let now_paused = state::PAUSED.load(Ordering::Relaxed); + pause_item.set_text(if now_paused { + "Unpause Monitoring" + } else { + "Pause Monitoring" + }); + } else if msg.message == state::WM_APP_AUTOSTART_TOGGLED { + let actual = msg.wParam.0 != 0; + autostart_item.set_checked(actual); + } else { + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + let _ = UnhookWindowsHookEx(hook); + } + + drop(tray_handles.tray); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..e3868dc --- /dev/null +++ b/src/state.rs @@ -0,0 +1,20 @@ +use std::sync::atomic::AtomicBool; +use windows::Win32::UI::WindowsAndMessaging::WM_APP; + +/// When true, double-click monitoring is temporarily disabled (tray menu toggle). +pub static PAUSED: AtomicBool = AtomicBool::new(false); + +/// Posted from the mouse-hook callback to the main thread's message loop when a +/// candidate double-click is detected. WPARAM = screen x, LPARAM = screen y. +/// Kept out of the hook callback itself so the hook stays cheap. +pub const WM_APP_CANDIDATE_DBLCLICK: u32 = WM_APP + 1; + +/// Posted from the (Send + Sync-bound) menu event handler when the pause state +/// changes, so the main loop — which owns the actual (non-Send) MenuItem — can update +/// its label without needing to move the item into that handler. +pub const WM_APP_PAUSE_TOGGLED: u32 = WM_APP + 2; + +/// Posted from the menu event handler after an autostart registry write, carrying the +/// actual resulting enabled state in wParam. muda already flips the native checkmark +/// itself on click, so the main loop only needs to correct it if the write failed. +pub const WM_APP_AUTOSTART_TOGGLED: u32 = WM_APP + 3; diff --git a/src/tray.rs b/src/tray.rs new file mode 100644 index 0000000..d05209c --- /dev/null +++ b/src/tray.rs @@ -0,0 +1,57 @@ +use muda::{CheckMenuItem, Menu, MenuId, MenuItem}; +use tray_icon::{Icon, TrayIcon, TrayIconBuilder}; + +use crate::autostart; + +const TRAY_PNG: &[u8] = include_bytes!("../assets/tray-32.png"); + +pub struct TrayHandles { + // Kept alive for as long as the tray icon should remain visible. + pub tray: TrayIcon, + pub toggle_id: MenuId, + // Kept as the full item (not just its id) so its label can be flipped between + // "Pause Monitoring" / "Unpause Monitoring" as the paused state changes. + pub pause_item: MenuItem, + // Kept as the full item so its checked state can be corrected if a registry write + // fails (muda already flips the native checkmark itself on click). + pub autostart_item: CheckMenuItem, + pub exit_id: MenuId, +} + +pub fn build_tray() -> TrayHandles { + let icon = load_icon(); + let autostart_enabled = autostart::is_enabled().unwrap_or(false); + + let menu = Menu::new(); + let toggle_item = MenuItem::new("Toggle Icons Now", true, None); + let pause_item = MenuItem::new("Pause Monitoring", true, None); + let autostart_item = CheckMenuItem::new("Start with Windows", true, autostart_enabled, None); + let exit_item = MenuItem::new("Exit", true, None); + menu.append(&toggle_item).expect("failed to append menu item"); + menu.append(&pause_item).expect("failed to append menu item"); + menu.append(&autostart_item).expect("failed to append menu item"); + menu.append(&exit_item).expect("failed to append menu item"); + + let tray = TrayIconBuilder::new() + .with_icon(icon) + .with_menu(Box::new(menu)) + .with_tooltip("Desktop Icon Hider") + .build() + .expect("failed to create tray icon"); + + TrayHandles { + tray, + toggle_id: toggle_item.id().clone(), + pause_item, + autostart_item, + exit_id: exit_item.id().clone(), + } +} + +fn load_icon() -> Icon { + let img = image::load_from_memory(TRAY_PNG) + .expect("embedded tray icon is invalid") + .into_rgba8(); + let (width, height) = img.dimensions(); + Icon::from_rgba(img.into_raw(), width, height).expect("failed to build tray icon") +}