├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── crossbeam │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── winapi │ ├── Cargo.toml │ └── src │ │ └── main.rs └── winit │ ├── Cargo.toml │ └── src │ └── main.rs └── src ├── icon.rs ├── lib.rs ├── menubuilder.rs ├── sys └── windows │ ├── mod.rs │ ├── wchar.rs │ ├── winhicon.rs │ ├── winhmenu.rs │ ├── winnotifyicon.rs │ └── wintrayicon.rs ├── testresource ├── icon1.ico └── icon2.ico ├── trayicon.rs ├── trayiconbuilder.rs └── trayiconsender.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | "program": "${workspaceFolder}/", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "rust-analyzer.inlayHints.enable": true 4 | } 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ab_glyph" 7 | version = "0.2.25" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775" 10 | dependencies = [ 11 | "ab_glyph_rasterizer", 12 | "owned_ttf_parser", 13 | ] 14 | 15 | [[package]] 16 | name = "ab_glyph_rasterizer" 17 | version = "0.1.4" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "d9fe5e32de01730eb1f6b7f5b51c17e03e2325bf40a74f754f04f130043affff" 20 | 21 | [[package]] 22 | name = "ahash" 23 | version = "0.8.11" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 26 | dependencies = [ 27 | "cfg-if 1.0.0", 28 | "getrandom", 29 | "once_cell", 30 | "version_check", 31 | "zerocopy", 32 | ] 33 | 34 | [[package]] 35 | name = "android-activity" 36 | version = "0.6.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" 39 | dependencies = [ 40 | "android-properties", 41 | "bitflags 2.5.0", 42 | "cc", 43 | "cesu8", 44 | "jni", 45 | "jni-sys", 46 | "libc", 47 | "log", 48 | "ndk", 49 | "ndk-context", 50 | "ndk-sys", 51 | "num_enum", 52 | "thiserror", 53 | ] 54 | 55 | [[package]] 56 | name = "android-properties" 57 | version = "0.2.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" 60 | 61 | [[package]] 62 | name = "arrayref" 63 | version = "0.3.7" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" 66 | 67 | [[package]] 68 | name = "arrayvec" 69 | version = "0.7.4" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 72 | 73 | [[package]] 74 | name = "as-raw-xcb-connection" 75 | version = "1.0.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" 78 | 79 | [[package]] 80 | name = "atomic-waker" 81 | version = "1.1.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.0.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 90 | 91 | [[package]] 92 | name = "bitflags" 93 | version = "1.2.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 96 | 97 | [[package]] 98 | name = "bitflags" 99 | version = "2.5.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 102 | 103 | [[package]] 104 | name = "block2" 105 | version = "0.5.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" 108 | dependencies = [ 109 | "objc2", 110 | ] 111 | 112 | [[package]] 113 | name = "bumpalo" 114 | version = "3.16.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 117 | 118 | [[package]] 119 | name = "bytemuck" 120 | version = "1.15.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" 123 | 124 | [[package]] 125 | name = "bytes" 126 | version = "1.6.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 129 | 130 | [[package]] 131 | name = "calloop" 132 | version = "0.12.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" 135 | dependencies = [ 136 | "bitflags 2.5.0", 137 | "log", 138 | "polling", 139 | "rustix", 140 | "slab", 141 | "thiserror", 142 | ] 143 | 144 | [[package]] 145 | name = "calloop-wayland-source" 146 | version = "0.2.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" 149 | dependencies = [ 150 | "calloop", 151 | "rustix", 152 | "wayland-backend", 153 | "wayland-client", 154 | ] 155 | 156 | [[package]] 157 | name = "cc" 158 | version = "1.0.58" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" 161 | dependencies = [ 162 | "jobserver", 163 | ] 164 | 165 | [[package]] 166 | name = "cesu8" 167 | version = "1.1.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 170 | 171 | [[package]] 172 | name = "cfg-if" 173 | version = "0.1.10" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 176 | 177 | [[package]] 178 | name = "cfg-if" 179 | version = "1.0.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 182 | 183 | [[package]] 184 | name = "cfg_aliases" 185 | version = "0.2.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f" 188 | 189 | [[package]] 190 | name = "combine" 191 | version = "4.6.7" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 194 | dependencies = [ 195 | "bytes", 196 | "memchr", 197 | ] 198 | 199 | [[package]] 200 | name = "concurrent-queue" 201 | version = "2.5.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 204 | dependencies = [ 205 | "crossbeam-utils", 206 | ] 207 | 208 | [[package]] 209 | name = "core-foundation" 210 | version = "0.9.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 213 | dependencies = [ 214 | "core-foundation-sys", 215 | "libc", 216 | ] 217 | 218 | [[package]] 219 | name = "core-foundation-sys" 220 | version = "0.8.6" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 223 | 224 | [[package]] 225 | name = "core-graphics" 226 | version = "0.23.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" 229 | dependencies = [ 230 | "bitflags 1.2.1", 231 | "core-foundation", 232 | "core-graphics-types", 233 | "foreign-types", 234 | "libc", 235 | ] 236 | 237 | [[package]] 238 | name = "core-graphics-types" 239 | version = "0.1.3" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 242 | dependencies = [ 243 | "bitflags 1.2.1", 244 | "core-foundation", 245 | "libc", 246 | ] 247 | 248 | [[package]] 249 | name = "crossbeam-channel" 250 | version = "0.5.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" 253 | dependencies = [ 254 | "cfg-if 1.0.0", 255 | "crossbeam-utils", 256 | ] 257 | 258 | [[package]] 259 | name = "crossbeam-utils" 260 | version = "0.8.19" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 263 | 264 | [[package]] 265 | name = "cursor-icon" 266 | version = "1.1.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" 269 | 270 | [[package]] 271 | name = "dispatch" 272 | version = "0.2.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 275 | 276 | [[package]] 277 | name = "dlib" 278 | version = "0.5.2" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 281 | dependencies = [ 282 | "libloading 0.7.0", 283 | ] 284 | 285 | [[package]] 286 | name = "downcast-rs" 287 | version = "1.2.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 290 | 291 | [[package]] 292 | name = "dpi" 293 | version = "0.1.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" 296 | 297 | [[package]] 298 | name = "errno" 299 | version = "0.3.9" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 302 | dependencies = [ 303 | "libc", 304 | "windows-sys 0.52.0", 305 | ] 306 | 307 | [[package]] 308 | name = "foreign-types" 309 | version = "0.5.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 312 | dependencies = [ 313 | "foreign-types-macros", 314 | "foreign-types-shared", 315 | ] 316 | 317 | [[package]] 318 | name = "foreign-types-macros" 319 | version = "0.2.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 322 | dependencies = [ 323 | "proc-macro2", 324 | "quote", 325 | "syn 2.0.61", 326 | ] 327 | 328 | [[package]] 329 | name = "foreign-types-shared" 330 | version = "0.3.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 333 | 334 | [[package]] 335 | name = "gethostname" 336 | version = "0.4.3" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 339 | dependencies = [ 340 | "libc", 341 | "windows-targets 0.48.5", 342 | ] 343 | 344 | [[package]] 345 | name = "getrandom" 346 | version = "0.2.15" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 349 | dependencies = [ 350 | "cfg-if 1.0.0", 351 | "libc", 352 | "wasi", 353 | ] 354 | 355 | [[package]] 356 | name = "hermit-abi" 357 | version = "0.3.9" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 360 | 361 | [[package]] 362 | name = "jni" 363 | version = "0.21.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 366 | dependencies = [ 367 | "cesu8", 368 | "cfg-if 1.0.0", 369 | "combine", 370 | "jni-sys", 371 | "log", 372 | "thiserror", 373 | "walkdir", 374 | "windows-sys 0.45.0", 375 | ] 376 | 377 | [[package]] 378 | name = "jni-sys" 379 | version = "0.3.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 382 | 383 | [[package]] 384 | name = "jobserver" 385 | version = "0.1.31" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 388 | dependencies = [ 389 | "libc", 390 | ] 391 | 392 | [[package]] 393 | name = "js-sys" 394 | version = "0.3.69" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 397 | dependencies = [ 398 | "wasm-bindgen", 399 | ] 400 | 401 | [[package]] 402 | name = "libc" 403 | version = "0.2.154" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 406 | 407 | [[package]] 408 | name = "libloading" 409 | version = "0.7.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" 412 | dependencies = [ 413 | "cfg-if 1.0.0", 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "libloading" 419 | version = "0.8.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" 422 | dependencies = [ 423 | "cfg-if 1.0.0", 424 | "windows-targets 0.52.5", 425 | ] 426 | 427 | [[package]] 428 | name = "libredox" 429 | version = "0.0.2" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" 432 | dependencies = [ 433 | "bitflags 2.5.0", 434 | "libc", 435 | "redox_syscall", 436 | ] 437 | 438 | [[package]] 439 | name = "linux-raw-sys" 440 | version = "0.4.13" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 443 | 444 | [[package]] 445 | name = "log" 446 | version = "0.4.8" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 449 | dependencies = [ 450 | "cfg-if 0.1.10", 451 | ] 452 | 453 | [[package]] 454 | name = "memchr" 455 | version = "2.3.4" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 458 | 459 | [[package]] 460 | name = "memmap2" 461 | version = "0.9.4" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 464 | dependencies = [ 465 | "libc", 466 | ] 467 | 468 | [[package]] 469 | name = "ndk" 470 | version = "0.9.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" 473 | dependencies = [ 474 | "bitflags 2.5.0", 475 | "jni-sys", 476 | "log", 477 | "ndk-sys", 478 | "num_enum", 479 | "raw-window-handle", 480 | "thiserror", 481 | ] 482 | 483 | [[package]] 484 | name = "ndk-context" 485 | version = "0.1.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 488 | 489 | [[package]] 490 | name = "ndk-sys" 491 | version = "0.6.0+11769913" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" 494 | dependencies = [ 495 | "jni-sys", 496 | ] 497 | 498 | [[package]] 499 | name = "nom" 500 | version = "6.1.2" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" 503 | dependencies = [ 504 | "memchr", 505 | "version_check", 506 | ] 507 | 508 | [[package]] 509 | name = "num_enum" 510 | version = "0.7.2" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" 513 | dependencies = [ 514 | "num_enum_derive", 515 | ] 516 | 517 | [[package]] 518 | name = "num_enum_derive" 519 | version = "0.7.2" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" 522 | dependencies = [ 523 | "proc-macro-crate", 524 | "proc-macro2", 525 | "quote", 526 | "syn 2.0.61", 527 | ] 528 | 529 | [[package]] 530 | name = "objc-sys" 531 | version = "0.3.3" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" 534 | 535 | [[package]] 536 | name = "objc2" 537 | version = "0.5.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" 540 | dependencies = [ 541 | "objc-sys", 542 | "objc2-encode", 543 | ] 544 | 545 | [[package]] 546 | name = "objc2-app-kit" 547 | version = "0.2.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" 550 | dependencies = [ 551 | "block2", 552 | "objc2", 553 | "objc2-core-data", 554 | "objc2-foundation", 555 | ] 556 | 557 | [[package]] 558 | name = "objc2-core-data" 559 | version = "0.2.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" 562 | dependencies = [ 563 | "block2", 564 | "objc2", 565 | "objc2-foundation", 566 | ] 567 | 568 | [[package]] 569 | name = "objc2-encode" 570 | version = "4.0.1" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" 573 | 574 | [[package]] 575 | name = "objc2-foundation" 576 | version = "0.2.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" 579 | dependencies = [ 580 | "block2", 581 | "dispatch", 582 | "objc2", 583 | ] 584 | 585 | [[package]] 586 | name = "once_cell" 587 | version = "1.19.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 590 | 591 | [[package]] 592 | name = "orbclient" 593 | version = "0.3.47" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" 596 | dependencies = [ 597 | "libredox", 598 | ] 599 | 600 | [[package]] 601 | name = "owned_ttf_parser" 602 | version = "0.20.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" 605 | dependencies = [ 606 | "ttf-parser", 607 | ] 608 | 609 | [[package]] 610 | name = "percent-encoding" 611 | version = "2.1.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 614 | 615 | [[package]] 616 | name = "pin-project" 617 | version = "1.1.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 620 | dependencies = [ 621 | "pin-project-internal", 622 | ] 623 | 624 | [[package]] 625 | name = "pin-project-internal" 626 | version = "1.1.5" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 629 | dependencies = [ 630 | "proc-macro2", 631 | "quote", 632 | "syn 2.0.61", 633 | ] 634 | 635 | [[package]] 636 | name = "pin-project-lite" 637 | version = "0.2.14" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 640 | 641 | [[package]] 642 | name = "pkg-config" 643 | version = "0.3.30" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 646 | 647 | [[package]] 648 | name = "polling" 649 | version = "3.7.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" 652 | dependencies = [ 653 | "cfg-if 1.0.0", 654 | "concurrent-queue", 655 | "hermit-abi", 656 | "pin-project-lite", 657 | "rustix", 658 | "tracing", 659 | "windows-sys 0.52.0", 660 | ] 661 | 662 | [[package]] 663 | name = "proc-macro-crate" 664 | version = "1.1.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" 667 | dependencies = [ 668 | "thiserror", 669 | "toml", 670 | ] 671 | 672 | [[package]] 673 | name = "proc-macro2" 674 | version = "1.0.82" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 677 | dependencies = [ 678 | "unicode-ident", 679 | ] 680 | 681 | [[package]] 682 | name = "quick-xml" 683 | version = "0.31.0" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" 686 | dependencies = [ 687 | "memchr", 688 | ] 689 | 690 | [[package]] 691 | name = "quote" 692 | version = "1.0.36" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 695 | dependencies = [ 696 | "proc-macro2", 697 | ] 698 | 699 | [[package]] 700 | name = "raw-window-handle" 701 | version = "0.6.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "8cc3bcbdb1ddfc11e700e62968e6b4cc9c75bb466464ad28fb61c5b2c964418b" 704 | 705 | [[package]] 706 | name = "redox_syscall" 707 | version = "0.4.1" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 710 | dependencies = [ 711 | "bitflags 1.2.1", 712 | ] 713 | 714 | [[package]] 715 | name = "rustix" 716 | version = "0.38.34" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 719 | dependencies = [ 720 | "bitflags 2.5.0", 721 | "errno", 722 | "libc", 723 | "linux-raw-sys", 724 | "windows-sys 0.52.0", 725 | ] 726 | 727 | [[package]] 728 | name = "same-file" 729 | version = "1.0.6" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 732 | dependencies = [ 733 | "winapi-util", 734 | ] 735 | 736 | [[package]] 737 | name = "scoped-tls" 738 | version = "1.0.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 741 | 742 | [[package]] 743 | name = "sctk-adwaita" 744 | version = "0.9.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "7de61fa7334ee8ee1f5c3c58dcc414fb9361e7e8f5bff9d45f4d69eeb89a7169" 747 | dependencies = [ 748 | "ab_glyph", 749 | "log", 750 | "memmap2", 751 | "smithay-client-toolkit", 752 | "tiny-skia", 753 | ] 754 | 755 | [[package]] 756 | name = "serde" 757 | version = "1.0.201" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" 760 | dependencies = [ 761 | "serde_derive", 762 | ] 763 | 764 | [[package]] 765 | name = "serde_derive" 766 | version = "1.0.201" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" 769 | dependencies = [ 770 | "proc-macro2", 771 | "quote", 772 | "syn 2.0.61", 773 | ] 774 | 775 | [[package]] 776 | name = "slab" 777 | version = "0.4.9" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 780 | dependencies = [ 781 | "autocfg", 782 | ] 783 | 784 | [[package]] 785 | name = "smallvec" 786 | version = "1.13.2" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 789 | 790 | [[package]] 791 | name = "smithay-client-toolkit" 792 | version = "0.18.1" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" 795 | dependencies = [ 796 | "bitflags 2.5.0", 797 | "calloop", 798 | "calloop-wayland-source", 799 | "cursor-icon", 800 | "libc", 801 | "log", 802 | "memmap2", 803 | "rustix", 804 | "thiserror", 805 | "wayland-backend", 806 | "wayland-client", 807 | "wayland-csd-frame", 808 | "wayland-cursor", 809 | "wayland-protocols", 810 | "wayland-protocols-wlr", 811 | "wayland-scanner", 812 | "xkeysym", 813 | ] 814 | 815 | [[package]] 816 | name = "smol_str" 817 | version = "0.2.1" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" 820 | dependencies = [ 821 | "serde", 822 | ] 823 | 824 | [[package]] 825 | name = "strict-num" 826 | version = "0.1.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" 829 | 830 | [[package]] 831 | name = "syn" 832 | version = "1.0.82" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 835 | dependencies = [ 836 | "proc-macro2", 837 | "quote", 838 | "unicode-xid", 839 | ] 840 | 841 | [[package]] 842 | name = "syn" 843 | version = "2.0.61" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "unicode-ident", 850 | ] 851 | 852 | [[package]] 853 | name = "thiserror" 854 | version = "1.0.30" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 857 | dependencies = [ 858 | "thiserror-impl", 859 | ] 860 | 861 | [[package]] 862 | name = "thiserror-impl" 863 | version = "1.0.30" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 866 | dependencies = [ 867 | "proc-macro2", 868 | "quote", 869 | "syn 1.0.82", 870 | ] 871 | 872 | [[package]] 873 | name = "tiny-skia" 874 | version = "0.11.4" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" 877 | dependencies = [ 878 | "arrayref", 879 | "arrayvec", 880 | "bytemuck", 881 | "cfg-if 1.0.0", 882 | "log", 883 | "tiny-skia-path", 884 | ] 885 | 886 | [[package]] 887 | name = "tiny-skia-path" 888 | version = "0.11.4" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" 891 | dependencies = [ 892 | "arrayref", 893 | "bytemuck", 894 | "strict-num", 895 | ] 896 | 897 | [[package]] 898 | name = "toml" 899 | version = "0.5.6" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 902 | dependencies = [ 903 | "serde", 904 | ] 905 | 906 | [[package]] 907 | name = "tracing" 908 | version = "0.1.40" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 911 | dependencies = [ 912 | "pin-project-lite", 913 | "tracing-core", 914 | ] 915 | 916 | [[package]] 917 | name = "tracing-core" 918 | version = "0.1.32" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 921 | 922 | [[package]] 923 | name = "trayicon" 924 | version = "0.2.0" 925 | dependencies = [ 926 | "winapi", 927 | ] 928 | 929 | [[package]] 930 | name = "trayicon-crossbeam-example" 931 | version = "0.0.1" 932 | dependencies = [ 933 | "crossbeam-channel", 934 | "trayicon", 935 | "winapi", 936 | ] 937 | 938 | [[package]] 939 | name = "trayicon-winapi-example" 940 | version = "0.0.1" 941 | dependencies = [ 942 | "trayicon", 943 | "winapi", 944 | ] 945 | 946 | [[package]] 947 | name = "trayicon-winit-example" 948 | version = "0.0.1" 949 | dependencies = [ 950 | "trayicon", 951 | "winit", 952 | ] 953 | 954 | [[package]] 955 | name = "ttf-parser" 956 | version = "0.20.0" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" 959 | 960 | [[package]] 961 | name = "unicode-ident" 962 | version = "1.0.12" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 965 | 966 | [[package]] 967 | name = "unicode-segmentation" 968 | version = "1.11.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 971 | 972 | [[package]] 973 | name = "unicode-xid" 974 | version = "0.2.1" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 977 | 978 | [[package]] 979 | name = "version_check" 980 | version = "0.9.4" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 983 | 984 | [[package]] 985 | name = "walkdir" 986 | version = "2.3.1" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 989 | dependencies = [ 990 | "same-file", 991 | "winapi", 992 | "winapi-util", 993 | ] 994 | 995 | [[package]] 996 | name = "wasi" 997 | version = "0.11.0+wasi-snapshot-preview1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1000 | 1001 | [[package]] 1002 | name = "wasm-bindgen" 1003 | version = "0.2.92" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1006 | dependencies = [ 1007 | "cfg-if 1.0.0", 1008 | "wasm-bindgen-macro", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "wasm-bindgen-backend" 1013 | version = "0.2.92" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1016 | dependencies = [ 1017 | "bumpalo", 1018 | "log", 1019 | "once_cell", 1020 | "proc-macro2", 1021 | "quote", 1022 | "syn 2.0.61", 1023 | "wasm-bindgen-shared", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "wasm-bindgen-futures" 1028 | version = "0.4.42" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1031 | dependencies = [ 1032 | "cfg-if 1.0.0", 1033 | "js-sys", 1034 | "wasm-bindgen", 1035 | "web-sys", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "wasm-bindgen-macro" 1040 | version = "0.2.92" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1043 | dependencies = [ 1044 | "quote", 1045 | "wasm-bindgen-macro-support", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "wasm-bindgen-macro-support" 1050 | version = "0.2.92" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1053 | dependencies = [ 1054 | "proc-macro2", 1055 | "quote", 1056 | "syn 2.0.61", 1057 | "wasm-bindgen-backend", 1058 | "wasm-bindgen-shared", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "wasm-bindgen-shared" 1063 | version = "0.2.92" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1066 | 1067 | [[package]] 1068 | name = "wayland-backend" 1069 | version = "0.3.3" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" 1072 | dependencies = [ 1073 | "cc", 1074 | "downcast-rs", 1075 | "rustix", 1076 | "scoped-tls", 1077 | "smallvec", 1078 | "wayland-sys", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "wayland-client" 1083 | version = "0.31.2" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" 1086 | dependencies = [ 1087 | "bitflags 2.5.0", 1088 | "rustix", 1089 | "wayland-backend", 1090 | "wayland-scanner", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "wayland-csd-frame" 1095 | version = "0.3.0" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" 1098 | dependencies = [ 1099 | "bitflags 2.5.0", 1100 | "cursor-icon", 1101 | "wayland-backend", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "wayland-cursor" 1106 | version = "0.31.1" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" 1109 | dependencies = [ 1110 | "rustix", 1111 | "wayland-client", 1112 | "xcursor", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "wayland-protocols" 1117 | version = "0.31.2" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" 1120 | dependencies = [ 1121 | "bitflags 2.5.0", 1122 | "wayland-backend", 1123 | "wayland-client", 1124 | "wayland-scanner", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "wayland-protocols-plasma" 1129 | version = "0.2.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" 1132 | dependencies = [ 1133 | "bitflags 2.5.0", 1134 | "wayland-backend", 1135 | "wayland-client", 1136 | "wayland-protocols", 1137 | "wayland-scanner", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "wayland-protocols-wlr" 1142 | version = "0.2.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" 1145 | dependencies = [ 1146 | "bitflags 2.5.0", 1147 | "wayland-backend", 1148 | "wayland-client", 1149 | "wayland-protocols", 1150 | "wayland-scanner", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "wayland-scanner" 1155 | version = "0.31.1" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quick-xml", 1161 | "quote", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "wayland-sys" 1166 | version = "0.31.1" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" 1169 | dependencies = [ 1170 | "dlib", 1171 | "log", 1172 | "once_cell", 1173 | "pkg-config", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "web-sys" 1178 | version = "0.3.69" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1181 | dependencies = [ 1182 | "js-sys", 1183 | "wasm-bindgen", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "web-time" 1188 | version = "1.1.0" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1191 | dependencies = [ 1192 | "js-sys", 1193 | "wasm-bindgen", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "winapi" 1198 | version = "0.3.9" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1201 | dependencies = [ 1202 | "winapi-i686-pc-windows-gnu", 1203 | "winapi-x86_64-pc-windows-gnu", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "winapi-i686-pc-windows-gnu" 1208 | version = "0.4.0" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1211 | 1212 | [[package]] 1213 | name = "winapi-util" 1214 | version = "0.1.5" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1217 | dependencies = [ 1218 | "winapi", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "winapi-x86_64-pc-windows-gnu" 1223 | version = "0.4.0" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1226 | 1227 | [[package]] 1228 | name = "windows-sys" 1229 | version = "0.45.0" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1232 | dependencies = [ 1233 | "windows-targets 0.42.2", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "windows-sys" 1238 | version = "0.52.0" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1241 | dependencies = [ 1242 | "windows-targets 0.52.5", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "windows-targets" 1247 | version = "0.42.2" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1250 | dependencies = [ 1251 | "windows_aarch64_gnullvm 0.42.2", 1252 | "windows_aarch64_msvc 0.42.2", 1253 | "windows_i686_gnu 0.42.2", 1254 | "windows_i686_msvc 0.42.2", 1255 | "windows_x86_64_gnu 0.42.2", 1256 | "windows_x86_64_gnullvm 0.42.2", 1257 | "windows_x86_64_msvc 0.42.2", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "windows-targets" 1262 | version = "0.48.5" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1265 | dependencies = [ 1266 | "windows_aarch64_gnullvm 0.48.5", 1267 | "windows_aarch64_msvc 0.48.5", 1268 | "windows_i686_gnu 0.48.5", 1269 | "windows_i686_msvc 0.48.5", 1270 | "windows_x86_64_gnu 0.48.5", 1271 | "windows_x86_64_gnullvm 0.48.5", 1272 | "windows_x86_64_msvc 0.48.5", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "windows-targets" 1277 | version = "0.52.5" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1280 | dependencies = [ 1281 | "windows_aarch64_gnullvm 0.52.5", 1282 | "windows_aarch64_msvc 0.52.5", 1283 | "windows_i686_gnu 0.52.5", 1284 | "windows_i686_gnullvm", 1285 | "windows_i686_msvc 0.52.5", 1286 | "windows_x86_64_gnu 0.52.5", 1287 | "windows_x86_64_gnullvm 0.52.5", 1288 | "windows_x86_64_msvc 0.52.5", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "windows_aarch64_gnullvm" 1293 | version = "0.42.2" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1296 | 1297 | [[package]] 1298 | name = "windows_aarch64_gnullvm" 1299 | version = "0.48.5" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1302 | 1303 | [[package]] 1304 | name = "windows_aarch64_gnullvm" 1305 | version = "0.52.5" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1308 | 1309 | [[package]] 1310 | name = "windows_aarch64_msvc" 1311 | version = "0.42.2" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1314 | 1315 | [[package]] 1316 | name = "windows_aarch64_msvc" 1317 | version = "0.48.5" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1320 | 1321 | [[package]] 1322 | name = "windows_aarch64_msvc" 1323 | version = "0.52.5" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1326 | 1327 | [[package]] 1328 | name = "windows_i686_gnu" 1329 | version = "0.42.2" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1332 | 1333 | [[package]] 1334 | name = "windows_i686_gnu" 1335 | version = "0.48.5" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1338 | 1339 | [[package]] 1340 | name = "windows_i686_gnu" 1341 | version = "0.52.5" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1344 | 1345 | [[package]] 1346 | name = "windows_i686_gnullvm" 1347 | version = "0.52.5" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1350 | 1351 | [[package]] 1352 | name = "windows_i686_msvc" 1353 | version = "0.42.2" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1356 | 1357 | [[package]] 1358 | name = "windows_i686_msvc" 1359 | version = "0.48.5" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1362 | 1363 | [[package]] 1364 | name = "windows_i686_msvc" 1365 | version = "0.52.5" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1368 | 1369 | [[package]] 1370 | name = "windows_x86_64_gnu" 1371 | version = "0.42.2" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1374 | 1375 | [[package]] 1376 | name = "windows_x86_64_gnu" 1377 | version = "0.48.5" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1380 | 1381 | [[package]] 1382 | name = "windows_x86_64_gnu" 1383 | version = "0.52.5" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1386 | 1387 | [[package]] 1388 | name = "windows_x86_64_gnullvm" 1389 | version = "0.42.2" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1392 | 1393 | [[package]] 1394 | name = "windows_x86_64_gnullvm" 1395 | version = "0.48.5" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1398 | 1399 | [[package]] 1400 | name = "windows_x86_64_gnullvm" 1401 | version = "0.52.5" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1404 | 1405 | [[package]] 1406 | name = "windows_x86_64_msvc" 1407 | version = "0.42.2" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1410 | 1411 | [[package]] 1412 | name = "windows_x86_64_msvc" 1413 | version = "0.48.5" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1416 | 1417 | [[package]] 1418 | name = "windows_x86_64_msvc" 1419 | version = "0.52.5" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1422 | 1423 | [[package]] 1424 | name = "winit" 1425 | version = "0.30.0" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "ea9e6d5d66cbf702e0dd820302144f51b69a95acdc495dd98ca280ff206562b1" 1428 | dependencies = [ 1429 | "ahash", 1430 | "android-activity", 1431 | "atomic-waker", 1432 | "bitflags 2.5.0", 1433 | "bytemuck", 1434 | "calloop", 1435 | "cfg_aliases", 1436 | "concurrent-queue", 1437 | "core-foundation", 1438 | "core-graphics", 1439 | "cursor-icon", 1440 | "dpi", 1441 | "js-sys", 1442 | "libc", 1443 | "memmap2", 1444 | "ndk", 1445 | "objc2", 1446 | "objc2-app-kit", 1447 | "objc2-foundation", 1448 | "orbclient", 1449 | "percent-encoding", 1450 | "pin-project", 1451 | "raw-window-handle", 1452 | "redox_syscall", 1453 | "rustix", 1454 | "sctk-adwaita", 1455 | "smithay-client-toolkit", 1456 | "smol_str", 1457 | "tracing", 1458 | "unicode-segmentation", 1459 | "wasm-bindgen", 1460 | "wasm-bindgen-futures", 1461 | "wayland-backend", 1462 | "wayland-client", 1463 | "wayland-protocols", 1464 | "wayland-protocols-plasma", 1465 | "web-sys", 1466 | "web-time", 1467 | "windows-sys 0.52.0", 1468 | "x11-dl", 1469 | "x11rb", 1470 | "xkbcommon-dl", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "x11-dl" 1475 | version = "2.21.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" 1478 | dependencies = [ 1479 | "libc", 1480 | "once_cell", 1481 | "pkg-config", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "x11rb" 1486 | version = "0.13.1" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 1489 | dependencies = [ 1490 | "as-raw-xcb-connection", 1491 | "gethostname", 1492 | "libc", 1493 | "libloading 0.8.3", 1494 | "once_cell", 1495 | "rustix", 1496 | "x11rb-protocol", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "x11rb-protocol" 1501 | version = "0.13.1" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 1504 | 1505 | [[package]] 1506 | name = "xcursor" 1507 | version = "0.3.3" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "3a9a231574ae78801646617cefd13bfe94be907c0e4fa979cfd8b770aa3c5d08" 1510 | dependencies = [ 1511 | "nom", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "xkbcommon-dl" 1516 | version = "0.4.2" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" 1519 | dependencies = [ 1520 | "bitflags 2.5.0", 1521 | "dlib", 1522 | "log", 1523 | "once_cell", 1524 | "xkeysym", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "xkeysym" 1529 | version = "0.2.0" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" 1532 | 1533 | [[package]] 1534 | name = "zerocopy" 1535 | version = "0.7.34" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 1538 | dependencies = [ 1539 | "zerocopy-derive", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "zerocopy-derive" 1544 | version = "0.7.34" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 1547 | dependencies = [ 1548 | "proc-macro2", 1549 | "quote", 1550 | "syn 2.0.61", 1551 | ] 1552 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trayicon" 3 | version = "0.2.0" 4 | authors = ["Jari Otto Oskari Pennanen "] 5 | edition = "2018" 6 | description = "Tray Icon, that thing in the corner" 7 | license = "MIT" 8 | readme = "README.md" 9 | homepage = "https://github.com/ciantic/trayicon-rs/" 10 | repository = "https://github.com/ciantic/trayicon-rs/" 11 | 12 | [target.'cfg(windows)'.dependencies] 13 | winapi = { version = "0.3.9", features = [ 14 | "winuser", 15 | "windef", 16 | "minwindef", 17 | "shellapi", 18 | "libloaderapi", 19 | "basetsd", 20 | ] } 21 | 22 | [lib] 23 | name = "trayicon" 24 | path = "src/lib.rs" 25 | 26 | [package.metadata.docs.rs] 27 | default-target = "x86_64-pc-windows-msvc" 28 | all-features = true 29 | 30 | [workspace] 31 | members = ["examples/winit", "examples/winapi", "examples/crossbeam"] 32 | 33 | [features] 34 | default = [] 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Jari Otto Oskari Pennanen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrayIcon 2 | 3 | Currently I target Windows tray icon implementation, with popup menu, click, double click events. Goal is to provide a channel for events and ability to plug in [winit](https://github.com/rust-windowing/winit) event loop easily. 4 | 5 | [Open here a full working example with winit crate 🢅](https://github.com/Ciantic/trayicon-rs/blob/master/examples/winit/src/main.rs) 6 | 7 | ## TODO 8 | 9 | Provide coordinates of the Tray Icon area for custom popups. 10 | 11 | ## Alternatives 12 | 13 | Most mature alternative is qdot's [systray-rs](https://github.com/qdot/systray-rs). Unfortunately I got frustrated with the API in it and decided to rewrite my own. This however largely does not use the code in it, instead I loaned my old C/C++ code repository as a template. 14 | 15 | ## Change log 16 | 17 | * 0.2.0 - 2024-05-09 18 | * Removed dependency to `winit` crate, now setting a sender is a function. 19 | * Added `show_menu`, this means user must call it to show the menu even on right click. Previously right click always showed the menu. -------------------------------------------------------------------------------- /examples/crossbeam/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trayicon-crossbeam-example" 3 | version = "0.0.1" 4 | authors = ["Jari Otto Oskari Pennanen"] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | winapi = { version = "0.3.9", features = [ 10 | "winuser", 11 | "windef", 12 | "minwindef", 13 | "shellapi", 14 | "libloaderapi", 15 | "commctrl", 16 | "basetsd", 17 | ] } 18 | crossbeam-channel = "0.5" 19 | trayicon = { path = "../../" } 20 | -------------------------------------------------------------------------------- /examples/crossbeam/src/main.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | use trayicon::*; 3 | use winapi::um::winuser; 4 | 5 | fn main() { 6 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 7 | enum Events { 8 | RightClickTrayIcon, 9 | LeftClickTrayIcon, 10 | DoubleClickTrayIcon, 11 | Exit, 12 | Item1, 13 | Item2, 14 | Item3, 15 | Item4, 16 | CheckItem1, 17 | SubItem1, 18 | SubItem2, 19 | SubItem3, 20 | } 21 | 22 | let (s, r) = crossbeam_channel::unbounded(); 23 | let icon = include_bytes!("../../../src/testresource/icon1.ico"); 24 | let icon2 = include_bytes!("../../../src/testresource/icon2.ico"); 25 | 26 | let second_icon = Icon::from_buffer(icon2, None, None).unwrap(); 27 | let first_icon = Icon::from_buffer(icon, None, None).unwrap(); 28 | 29 | // Needlessly complicated tray icon with all the whistles and bells 30 | let mut tray_icon = TrayIconBuilder::new() 31 | .sender(move |e| { 32 | let _ = s.send(*e); 33 | }) 34 | .icon_from_buffer(icon) 35 | .tooltip("Cool Tray 👀 Icon") 36 | .on_right_click(Events::RightClickTrayIcon) 37 | .on_click(Events::LeftClickTrayIcon) 38 | .on_double_click(Events::DoubleClickTrayIcon) 39 | .menu( 40 | MenuBuilder::new() 41 | .item("Item 3 Replace Menu 👍", Events::Item3) 42 | .item("Item 2 Change Icon Green", Events::Item2) 43 | .item("Item 1 Change Icon Red", Events::Item1) 44 | .separator() 45 | .checkable("This is checkable", true, Events::CheckItem1) 46 | .submenu( 47 | "Sub Menu", 48 | MenuBuilder::new() 49 | .item("Sub item 1", Events::SubItem1) 50 | .item("Sub Item 2", Events::SubItem2) 51 | .item("Sub Item 3", Events::SubItem3), 52 | ) 53 | .with(MenuItem::Item { 54 | name: "Item Disabled".into(), 55 | disabled: true, // Disabled entry example 56 | id: Events::Item4, 57 | icon: None, 58 | }) 59 | .separator() 60 | .item("E&xit", Events::Exit), 61 | ) 62 | .build() 63 | .unwrap(); 64 | 65 | std::thread::spawn(move || { 66 | r.iter().for_each(|m| match m { 67 | Events::RightClickTrayIcon => { 68 | tray_icon.show_menu().unwrap(); 69 | } 70 | Events::DoubleClickTrayIcon => { 71 | println!("Double click"); 72 | } 73 | Events::LeftClickTrayIcon => { 74 | tray_icon.show_menu().unwrap(); 75 | } 76 | Events::Exit => { 77 | println!("Please exit"); 78 | std::process::exit(0); 79 | } 80 | Events::Item1 => { 81 | tray_icon.set_icon(&second_icon).unwrap(); 82 | } 83 | Events::Item2 => { 84 | tray_icon.set_icon(&first_icon).unwrap(); 85 | } 86 | Events::Item3 => { 87 | tray_icon 88 | .set_menu( 89 | &MenuBuilder::new() 90 | .item("New menu item", Events::Item1) 91 | .item("Exit", Events::Exit), 92 | ) 93 | .unwrap(); 94 | } 95 | e => { 96 | println!("{:?}", e); 97 | } 98 | }) 99 | }); 100 | 101 | // Your applications message loop. Because all applications require an 102 | // application loop, you are best served using an `winit` crate. 103 | loop { 104 | unsafe { 105 | let mut msg = MaybeUninit::uninit(); 106 | let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0); 107 | if bret > 0 { 108 | winuser::TranslateMessage(msg.as_ptr()); 109 | winuser::DispatchMessageA(msg.as_ptr()); 110 | } else { 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /examples/winapi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trayicon-winapi-example" 3 | version = "0.0.1" 4 | authors = ["Jari Otto Oskari Pennanen"] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | winapi = { version = "0.3.9", features = [ 10 | "winuser", 11 | "windef", 12 | "minwindef", 13 | "shellapi", 14 | "libloaderapi", 15 | "commctrl", 16 | "basetsd", 17 | ] } 18 | trayicon = { path = "../../" } 19 | -------------------------------------------------------------------------------- /examples/winapi/src/main.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | use trayicon::*; 3 | use winapi::um::winuser; 4 | 5 | fn main() { 6 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 7 | enum Events { 8 | RightClickTrayIcon, 9 | LeftClickTrayIcon, 10 | DoubleClickTrayIcon, 11 | Exit, 12 | Item1, 13 | Item2, 14 | Item3, 15 | Item4, 16 | CheckItem1, 17 | SubItem1, 18 | SubItem2, 19 | SubItem3, 20 | } 21 | 22 | let (s, r) = std::sync::mpsc::channel::(); 23 | let icon = include_bytes!("../../../src/testresource/icon1.ico"); 24 | let icon2 = include_bytes!("../../../src/testresource/icon2.ico"); 25 | 26 | let second_icon = Icon::from_buffer(icon2, None, None).unwrap(); 27 | let first_icon = Icon::from_buffer(icon, None, None).unwrap(); 28 | 29 | // Needlessly complicated tray icon with all the whistles and bells 30 | let mut tray_icon = TrayIconBuilder::new() 31 | .sender(move |e: &Events| { 32 | let _ = s.send(*e); 33 | }) 34 | .icon_from_buffer(icon) 35 | .tooltip("Cool Tray 👀 Icon") 36 | .on_right_click(Events::RightClickTrayIcon) 37 | .on_click(Events::LeftClickTrayIcon) 38 | .on_double_click(Events::DoubleClickTrayIcon) 39 | .menu( 40 | MenuBuilder::new() 41 | .item("Item 3 Replace Menu 👍", Events::Item3) 42 | .item("Item 2 Change Icon Green", Events::Item2) 43 | .item("Item 1 Change Icon Red", Events::Item1) 44 | .separator() 45 | .checkable("This is checkable", true, Events::CheckItem1) 46 | .submenu( 47 | "Sub Menu", 48 | MenuBuilder::new() 49 | .item("Sub item 1", Events::SubItem1) 50 | .item("Sub Item 2", Events::SubItem2) 51 | .item("Sub Item 3", Events::SubItem3), 52 | ) 53 | .with(MenuItem::Item { 54 | name: "Item Disabled".into(), 55 | disabled: true, // Disabled entry example 56 | id: Events::Item4, 57 | icon: None, 58 | }) 59 | .separator() 60 | .item("E&xit", Events::Exit), 61 | ) 62 | .build() 63 | .unwrap(); 64 | 65 | std::thread::spawn(move || { 66 | r.iter().for_each(|m| match m { 67 | Events::RightClickTrayIcon => { 68 | tray_icon.show_menu().unwrap(); 69 | } 70 | Events::DoubleClickTrayIcon => { 71 | println!("Double click"); 72 | } 73 | Events::LeftClickTrayIcon => { 74 | tray_icon.show_menu().unwrap(); 75 | } 76 | Events::Exit => { 77 | println!("Please exit"); 78 | std::process::exit(0); 79 | } 80 | Events::Item1 => { 81 | tray_icon.set_icon(&second_icon).unwrap(); 82 | } 83 | Events::Item2 => { 84 | tray_icon.set_icon(&first_icon).unwrap(); 85 | } 86 | Events::Item3 => { 87 | tray_icon 88 | .set_menu( 89 | &MenuBuilder::new() 90 | .item("New menu item", Events::Item1) 91 | .item("Exit", Events::Exit), 92 | ) 93 | .unwrap(); 94 | } 95 | e => { 96 | println!("{:?}", e); 97 | } 98 | }) 99 | }); 100 | 101 | // Your applications message loop. Because all applications require an 102 | // application loop, you are best served using an `winit` crate. 103 | loop { 104 | unsafe { 105 | let mut msg = MaybeUninit::uninit(); 106 | let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0); 107 | if bret > 0 { 108 | winuser::TranslateMessage(msg.as_ptr()); 109 | winuser::DispatchMessageA(msg.as_ptr()); 110 | } else { 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /examples/winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trayicon-winit-example" 3 | version = "0.0.1" 4 | authors = ["Jari Otto Oskari Pennanen"] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | winit = "0.30" 10 | trayicon = { path = "../../" } 11 | -------------------------------------------------------------------------------- /examples/winit/src/main.rs: -------------------------------------------------------------------------------- 1 | use winit::{ 2 | application::ApplicationHandler, 3 | event::WindowEvent, 4 | event_loop::{ActiveEventLoop, EventLoop}, 5 | window::Window, 6 | }; 7 | 8 | use trayicon::{Icon, MenuBuilder, MenuItem, TrayIcon, TrayIconBuilder}; 9 | 10 | #[derive(Clone, Eq, PartialEq, Debug)] 11 | enum UserEvents { 12 | RightClickTrayIcon, 13 | LeftClickTrayIcon, 14 | DoubleClickTrayIcon, 15 | Exit, 16 | Item1, 17 | Item2, 18 | Item3, 19 | Item4, 20 | DisabledItem1, 21 | CheckItem1, 22 | SubItem1, 23 | SubItem2, 24 | SubItem3, 25 | } 26 | 27 | fn main() { 28 | let event_loop = EventLoop::::with_user_event().build().unwrap(); 29 | let proxy = event_loop.create_proxy(); 30 | 31 | let icon = include_bytes!("../../../src/testresource/icon1.ico"); 32 | let icon2 = include_bytes!("../../../src/testresource/icon2.ico"); 33 | let second_icon = Icon::from_buffer(icon2, None, None).unwrap(); 34 | let first_icon = Icon::from_buffer(icon, None, None).unwrap(); 35 | 36 | // Needlessly complicated tray icon with all the whistles and bells 37 | let tray_icon = TrayIconBuilder::new() 38 | .sender(move |e: &UserEvents| { 39 | let _ = proxy.send_event(e.clone()); 40 | }) 41 | .icon_from_buffer(icon) 42 | .tooltip("Cool Tray 👀 Icon") 43 | .on_click(UserEvents::LeftClickTrayIcon) 44 | .on_double_click(UserEvents::DoubleClickTrayIcon) 45 | .on_right_click(UserEvents::RightClickTrayIcon) 46 | .menu( 47 | MenuBuilder::new() 48 | .item("Item 4 Set Tooltip", UserEvents::Item4) 49 | .item("Item 3 Replace Menu 👍", UserEvents::Item3) 50 | .item("Item 2 Change Icon Green", UserEvents::Item2) 51 | .item("Item 1 Change Icon Red", UserEvents::Item1) 52 | .separator() 53 | .submenu( 54 | "Sub Menu", 55 | MenuBuilder::new() 56 | .item("Sub item 1", UserEvents::SubItem1) 57 | .item("Sub Item 2", UserEvents::SubItem2) 58 | .item("Sub Item 3", UserEvents::SubItem3), 59 | ) 60 | .checkable( 61 | "This checkbox toggles disable", 62 | true, 63 | UserEvents::CheckItem1, 64 | ) 65 | .with(MenuItem::Item { 66 | name: "Item Disabled".into(), 67 | disabled: true, // Disabled entry example 68 | id: UserEvents::DisabledItem1, 69 | icon: Result::ok(Icon::from_buffer(icon, None, None)), 70 | }) 71 | .separator() 72 | .item("E&xit", UserEvents::Exit), 73 | ) 74 | .build() 75 | .unwrap(); 76 | 77 | let mut app = MyApplication { 78 | window: None, 79 | tray_icon, 80 | first_icon, 81 | second_icon, 82 | }; 83 | event_loop.run_app(&mut app).unwrap(); 84 | } 85 | 86 | struct MyApplication { 87 | window: Option, 88 | tray_icon: TrayIcon, 89 | first_icon: Icon, 90 | second_icon: Icon, 91 | } 92 | 93 | impl ApplicationHandler for MyApplication { 94 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 95 | self.window = Some( 96 | event_loop 97 | .create_window(Window::default_attributes()) 98 | .unwrap(), 99 | ); 100 | } 101 | 102 | // Platform specific events 103 | fn window_event( 104 | &mut self, 105 | event_loop: &ActiveEventLoop, 106 | _window_id: winit::window::WindowId, 107 | event: WindowEvent, 108 | ) { 109 | match event { 110 | WindowEvent::CloseRequested => { 111 | event_loop.exit(); 112 | } 113 | _ => {} 114 | } 115 | } 116 | 117 | // Application specific events 118 | fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvents) { 119 | match event { 120 | UserEvents::Exit => event_loop.exit(), 121 | UserEvents::RightClickTrayIcon => { 122 | self.tray_icon.show_menu().unwrap(); 123 | } 124 | UserEvents::CheckItem1 => { 125 | // You can mutate single checked, disabled value followingly. 126 | // 127 | // However, I think better way is to use reactively 128 | // `set_menu` by building the menu based on application 129 | // state. 130 | if let Some(old_value) = self 131 | .tray_icon 132 | .get_menu_item_checkable(UserEvents::CheckItem1) 133 | { 134 | // Set checkable example 135 | let _ = self 136 | .tray_icon 137 | .set_menu_item_checkable(UserEvents::CheckItem1, !old_value); 138 | 139 | // Set disabled example 140 | let _ = self 141 | .tray_icon 142 | .set_menu_item_disabled(UserEvents::DisabledItem1, !old_value); 143 | } 144 | } 145 | UserEvents::Item1 => { 146 | self.tray_icon.set_icon(&self.second_icon).unwrap(); 147 | } 148 | UserEvents::Item2 => { 149 | self.tray_icon.set_icon(&self.first_icon).unwrap(); 150 | } 151 | UserEvents::Item3 => { 152 | self.tray_icon 153 | .set_menu( 154 | &MenuBuilder::new() 155 | .item("Another item", UserEvents::Item1) 156 | .item("Exit", UserEvents::Exit), 157 | ) 158 | .unwrap(); 159 | } 160 | UserEvents::Item4 => { 161 | self.tray_icon.set_tooltip("Menu changed!").unwrap(); 162 | } 163 | UserEvents::LeftClickTrayIcon => { 164 | self.tray_icon.show_menu().unwrap(); 165 | } 166 | // Events::DoubleClickTrayIcon => todo!(), 167 | // Events::DisabledItem1 => todo!(), 168 | // Events::SubItem1 => todo!(), 169 | // Events::SubItem2 => todo!(), 170 | // Events::SubItem3 => todo!(), 171 | _ => {} 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/icon.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, IconBase}; 2 | use std::fmt::Debug; 3 | 4 | #[derive(Clone)] 5 | pub struct Icon { 6 | buffer: Option<&'static [u8]>, 7 | pub(crate) sys: crate::IconSys, 8 | } 9 | 10 | impl Debug for Icon { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "Icon") 13 | } 14 | } 15 | 16 | impl Icon { 17 | pub fn from_buffer( 18 | buffer: &'static [u8], 19 | width: Option, 20 | height: Option, 21 | ) -> Result { 22 | Ok(Icon { 23 | buffer: Some(buffer), 24 | sys: crate::IconSys::from_buffer(buffer, width, height)?, 25 | }) 26 | } 27 | } 28 | 29 | impl PartialEq for Icon { 30 | fn eq(&self, other: &Self) -> bool { 31 | self.buffer == other.buffer 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ## Example 2 | //! [Open full example with winit here 🢅](https://github.com/Ciantic/trayicon-rs/blob/master/examples/winit/src/main.rs) 3 | 4 | #[cfg(target_os = "windows")] 5 | #[path = "./sys/windows/mod.rs"] 6 | mod sys; 7 | 8 | mod icon; 9 | mod menubuilder; 10 | mod trayicon; 11 | mod trayiconbuilder; 12 | mod trayiconsender; 13 | 14 | // Public api 15 | pub use crate::icon::Icon; 16 | pub use crate::menubuilder::{MenuBuilder, MenuItem}; 17 | pub use crate::trayicon::TrayIcon; 18 | pub use crate::trayiconbuilder::Error; 19 | pub use crate::trayiconbuilder::TrayIconBuilder; 20 | 21 | // Each OS specific implementation must export following: 22 | pub(crate) use crate::sys::{ 23 | // MenuBuilder -> Result, Error> 24 | build_menu, 25 | 26 | // TrayIconBuilder -> Result>, Error> 27 | build_trayicon, 28 | 29 | // Struct that must implement IconBase + Clone 30 | IconSys, 31 | 32 | // Struct 33 | MenuSys, 34 | 35 | // Struct that must implement TrayIconBase 36 | TrayIconSys, 37 | }; 38 | 39 | /// TrayIconSys must implement this 40 | pub(crate) trait TrayIconBase 41 | where 42 | T: PartialEq + Clone + 'static, 43 | { 44 | fn set_icon(&mut self, icon: &Icon) -> Result<(), Error>; 45 | fn set_menu(&mut self, menu: &MenuBuilder) -> Result<(), Error>; 46 | fn set_tooltip(&mut self, tooltip: &str) -> Result<(), Error>; 47 | fn show_menu(&mut self) -> Result<(), Error>; 48 | } 49 | 50 | /// IconSys must implement this 51 | pub(crate) trait IconBase { 52 | fn from_buffer( 53 | buffer: &'static [u8], 54 | width: Option, 55 | height: Option, 56 | ) -> Result; 57 | } 58 | -------------------------------------------------------------------------------- /src/menubuilder.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Icon}; 2 | 3 | #[derive(Debug, Clone, PartialEq)] 4 | pub enum MenuItem 5 | where 6 | T: PartialEq + Clone + 'static, 7 | { 8 | Separator, 9 | Item { 10 | id: T, 11 | name: String, 12 | disabled: bool, 13 | icon: Option, 14 | }, 15 | Checkable { 16 | id: T, 17 | name: String, 18 | is_checked: bool, 19 | disabled: bool, 20 | icon: Option, 21 | }, 22 | Submenu { 23 | id: Option, 24 | name: String, 25 | children: MenuBuilder, 26 | disabled: bool, 27 | icon: Option, 28 | }, 29 | } 30 | 31 | #[derive(Debug, Default, Clone, PartialEq)] 32 | pub struct MenuBuilder 33 | where 34 | T: PartialEq + Clone + 'static, 35 | { 36 | pub(crate) menu_items: Vec>, 37 | } 38 | 39 | /// Menu Builder 40 | /// 41 | /// This is defined as consuming builder, could be converted to non-consuming 42 | /// one. This builder includes conditional helper `when` for composing 43 | /// conditionally some items. 44 | impl MenuBuilder 45 | where 46 | T: PartialEq + Clone + 'static, 47 | { 48 | pub fn new() -> MenuBuilder { 49 | MenuBuilder { menu_items: vec![] } 50 | } 51 | 52 | /// Conditionally include items, poor mans function composition 53 | pub fn when(self, f: F) -> Self 54 | where 55 | F: FnOnce(Self) -> Self, 56 | { 57 | f(self) 58 | } 59 | 60 | pub fn with(mut self, item: MenuItem) -> Self { 61 | self.menu_items.push(item); 62 | self 63 | } 64 | 65 | pub fn separator(mut self) -> Self { 66 | self.menu_items.push(MenuItem::Separator); 67 | self 68 | } 69 | 70 | pub fn item(mut self, name: &str, id: T) -> Self { 71 | self.menu_items.push(MenuItem::Item { 72 | id, 73 | name: name.to_string(), 74 | disabled: false, 75 | icon: None, 76 | }); 77 | self 78 | } 79 | 80 | pub fn checkable(mut self, name: &str, is_checked: bool, id: T) -> Self { 81 | self.menu_items.push(MenuItem::Checkable { 82 | id, 83 | name: name.to_string(), 84 | is_checked, 85 | disabled: false, 86 | icon: None, 87 | }); 88 | self 89 | } 90 | 91 | pub fn submenu(mut self, name: &str, menu: MenuBuilder) -> Self { 92 | self.menu_items.push(MenuItem::Submenu { 93 | id: None, 94 | name: name.to_string(), 95 | children: menu, 96 | disabled: false, 97 | icon: None, 98 | }); 99 | self 100 | } 101 | 102 | pub(crate) fn build(&self) -> Result, Error> { 103 | crate::build_menu(self) 104 | } 105 | 106 | /// Get checkable state, if found. 107 | /// 108 | /// Prefer maintaining proper application state instead of getting checkable 109 | /// state with this method. 110 | pub(crate) fn get_checkable(&mut self, find_id: T) -> Option { 111 | let mut found_item = None; 112 | let _ = self.mutate_item(find_id, |i| { 113 | if let MenuItem::Checkable { is_checked, .. } = i { 114 | found_item = Some(*is_checked); 115 | Ok(()) 116 | } else { 117 | Err(Error::MenuItemNotFound) 118 | } 119 | }); 120 | found_item 121 | } 122 | 123 | /// Set checkable 124 | /// 125 | /// Prefer building a new menu instead of mutating it with this method. 126 | pub(crate) fn set_checkable(&mut self, id: T, checked: bool) -> Result<(), Error> { 127 | self.mutate_item(id, |i| { 128 | if let MenuItem::Checkable { is_checked, .. } = i { 129 | *is_checked = checked; 130 | Ok(()) 131 | } else { 132 | Err(Error::MenuItemNotFound) 133 | } 134 | }) 135 | } 136 | 137 | /// Set disabled state 138 | /// 139 | /// Prefer building a new menu instead of mutating it with this method. 140 | pub(crate) fn set_disabled(&mut self, id: T, disabled: bool) -> Result<(), Error> { 141 | self.mutate_item(id, |i| match i { 142 | MenuItem::Item { disabled: d, .. } => { 143 | *d = disabled; 144 | Ok(()) 145 | } 146 | MenuItem::Checkable { disabled: d, .. } => { 147 | *d = disabled; 148 | Ok(()) 149 | } 150 | MenuItem::Submenu { disabled: d, .. } => { 151 | *d = disabled; 152 | Ok(()) 153 | } 154 | MenuItem::Separator => Err(Error::MenuItemNotFound), 155 | }) 156 | } 157 | 158 | /// Find item and optionally mutate 159 | /// 160 | /// Recursively searches for item with id, and applies function f to item if 161 | /// found. There is no recursion depth limitation and may cause stack 162 | /// issues. 163 | fn mutate_item(&mut self, id: T, f: F) -> Result<(), Error> 164 | where 165 | F: FnOnce(&mut MenuItem) -> Result<(), Error>, 166 | { 167 | self._mutate_item_recurse_ref(id, f) 168 | } 169 | 170 | fn _mutate_item_recurse_ref(&mut self, find_id: T, f: F) -> Result<(), Error> 171 | where 172 | F: FnOnce(&mut MenuItem) -> Result<(), Error>, 173 | { 174 | let found_item = self.menu_items.iter_mut().find(|f| match f { 175 | MenuItem::Item { id, .. } if id == &find_id => true, 176 | MenuItem::Checkable { id, .. } if id == &find_id => true, 177 | MenuItem::Submenu { id, .. } if id.as_ref() == Some(&find_id) => true, 178 | _ => false, 179 | }); 180 | 181 | if let Some(item) = found_item { 182 | f(item) 183 | } else { 184 | // Try to find submenu 185 | let maybe_found_submenu = self 186 | .menu_items 187 | .iter_mut() 188 | .find(|i| matches!(i, MenuItem::Submenu { .. })); 189 | 190 | // Reurse 191 | if let Some(MenuItem::Submenu { children, .. }) = maybe_found_submenu { 192 | return children._mutate_item_recurse_ref(find_id, f); 193 | } 194 | 195 | Err(Error::MenuItemNotFound) 196 | } 197 | } 198 | } 199 | 200 | #[cfg(test)] 201 | pub(crate) mod tests { 202 | use super::*; 203 | 204 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 205 | enum Events { 206 | Item1, 207 | Item2, 208 | Item3, 209 | Item4, 210 | DisabledItem1, 211 | CheckItem1, 212 | CheckItem2, 213 | SubItem1, 214 | SubItem2, 215 | SubItem3, 216 | } 217 | 218 | #[test] 219 | fn test_menu_mutation() { 220 | // This is a good way to create menu conditionally on application state, define a function "State -> Menu" 221 | let menu_builder = |checked, disabled| { 222 | MenuBuilder::new() 223 | .item("Item 4 Set Tooltip", Events::Item4) 224 | .item("Item 3 Replace Menu 👍", Events::Item3) 225 | .item("Item 2 Change Icon Green", Events::Item2) 226 | .item("Item 1 Change Icon Red", Events::Item1) 227 | .separator() 228 | .checkable("This is checkable", checked, Events::CheckItem1) 229 | .submenu( 230 | "Sub Menu", 231 | MenuBuilder::new() 232 | .item("Sub item 1", Events::SubItem1) 233 | .item("Sub Item 2", Events::SubItem2) 234 | .checkable("This is checkable", checked, Events::CheckItem2) 235 | .item("Sub Item 3", Events::SubItem3), 236 | ) 237 | .with(MenuItem::Item { 238 | name: "Item Disabled".into(), 239 | disabled, 240 | id: Events::DisabledItem1, 241 | icon: None, 242 | }) 243 | }; 244 | 245 | let mut old = menu_builder(false, false); 246 | let _ = old.set_checkable(Events::CheckItem1, true); 247 | let _ = old.set_disabled(Events::DisabledItem1, true); 248 | let _ = old.set_checkable(Events::CheckItem2, true); 249 | assert_eq!(old, menu_builder(true, true)); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/sys/windows/mod.rs: -------------------------------------------------------------------------------- 1 | mod wchar; 2 | mod winhicon; 3 | mod winhmenu; 4 | mod winnotifyicon; 5 | mod wintrayicon; 6 | 7 | use std::collections::HashMap; 8 | use wintrayicon::WinTrayIconImpl; 9 | 10 | use crate::{Error, MenuBuilder, MenuItem, TrayIconBuilder}; 11 | use winhmenu::WinHMenu; 12 | use winnotifyicon::WinNotifyIcon; 13 | 14 | // Windows implementations of Icon, TrayIcon, and Menu 15 | pub use winhicon::WinHIcon as IconSys; 16 | pub use wintrayicon::WinTrayIcon as TrayIconSys; 17 | 18 | #[derive(Debug)] 19 | pub struct MenuSys 20 | where 21 | T: PartialEq + Clone + 'static, 22 | { 23 | ids: HashMap, 24 | menu: WinHMenu, 25 | } 26 | 27 | /// Build the tray icon 28 | pub fn build_trayicon(builder: &TrayIconBuilder) -> Result, Error> 29 | where 30 | T: PartialEq + Clone + 'static, 31 | { 32 | let mut menu: Option> = None; 33 | let tooltip = &builder.tooltip; 34 | let hicon = &builder.icon.as_ref()?.sys; 35 | let on_click = builder.on_click.clone(); 36 | let on_right_click = builder.on_right_click.clone(); 37 | let sender = builder.sender.clone().ok_or(Error::SenderMissing)?; 38 | let on_double_click = builder.on_double_click.clone(); 39 | let notify_icon = WinNotifyIcon::new(hicon, tooltip); 40 | 41 | // Try to get a popup menu 42 | if let Some(rhmenu) = &builder.menu { 43 | menu = Some(rhmenu.build()?); 44 | } 45 | 46 | Ok(WinTrayIconImpl::new( 47 | sender, 48 | menu, 49 | notify_icon, 50 | on_click, 51 | on_double_click, 52 | on_right_click, 53 | )?) 54 | } 55 | 56 | /// Build the menu from Windows HMENU 57 | pub fn build_menu(builder: &MenuBuilder) -> Result, Error> 58 | where 59 | T: PartialEq + Clone + 'static, 60 | { 61 | let mut j = 0; 62 | build_menu_inner(&mut j, builder) 63 | } 64 | 65 | /// Recursive menu builder 66 | /// 67 | /// Having a j value as mutable reference it's capable of handling nested 68 | /// submenus 69 | fn build_menu_inner(j: &mut usize, builder: &MenuBuilder) -> Result, Error> 70 | where 71 | T: PartialEq + Clone + 'static, 72 | { 73 | let mut hmenu = WinHMenu::new()?; 74 | let mut map: HashMap = HashMap::new(); 75 | builder.menu_items.iter().for_each(|item| match item { 76 | MenuItem::Submenu { 77 | id, 78 | name, 79 | children, 80 | disabled, 81 | .. 82 | } => { 83 | if let Some(id) = id { 84 | *j += 1; 85 | map.insert(*j, id.clone()); 86 | } 87 | if let Ok(menusys) = build_menu_inner(j, children) { 88 | map.extend(menusys.ids.into_iter()); 89 | hmenu.add_child_menu(&name, menusys.menu, *disabled); 90 | } 91 | } 92 | 93 | MenuItem::Checkable { 94 | name, 95 | is_checked, 96 | id, 97 | disabled, 98 | .. 99 | } => { 100 | *j += 1; 101 | map.insert(*j, id.clone()); 102 | hmenu.add_checkable_item(&name, *is_checked, *j, *disabled); 103 | } 104 | 105 | MenuItem::Item { 106 | name, id, disabled, .. 107 | } => { 108 | *j += 1; 109 | map.insert(*j, id.clone()); 110 | hmenu.add_menu_item(&name, *j, *disabled); 111 | } 112 | 113 | MenuItem::Separator => { 114 | hmenu.add_separator(); 115 | } 116 | }); 117 | 118 | Ok(MenuSys { 119 | ids: map, 120 | menu: hmenu, 121 | }) 122 | } 123 | 124 | // For pattern matching, these are in own mod 125 | mod msgs { 126 | pub const WM_USER_TRAYICON: u32 = 0x400 + 1001; 127 | pub const WM_USER_SHOW_MENU: u32 = 0x400 + 1002; 128 | } 129 | 130 | #[cfg(test)] 131 | pub(crate) mod tests { 132 | use super::*; 133 | 134 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 135 | enum Events { 136 | CheckableItem1, 137 | Item1, 138 | SubItem1, 139 | SubItem2, 140 | SubItem3, 141 | SubItem4, 142 | SubSubItem1, 143 | SubSubItem2, 144 | SubSubItem3, 145 | } 146 | 147 | #[test] 148 | fn test_menu_build() { 149 | let cond = false; 150 | let builder = MenuBuilder::new() 151 | .checkable("This is checkable", true, Events::CheckableItem1) 152 | .submenu( 153 | "Sub Menu", 154 | MenuBuilder::new() 155 | .item("Sub item 1", Events::SubItem1) 156 | .item("Sub Item 2", Events::SubItem2) 157 | .item("Sub Item 3", Events::SubItem3) 158 | .submenu( 159 | "Sub Sub menu", 160 | MenuBuilder::new() 161 | .item("Sub Sub item 1", Events::SubSubItem1) 162 | .item("Sub Sub Item 2", Events::SubSubItem2) 163 | .item("Sub Sub Item 3", Events::SubSubItem3), 164 | ) 165 | .when(|f| { 166 | if cond { 167 | f.item("Foo", Events::Item1) 168 | } else { 169 | f 170 | } 171 | }) 172 | .item("Sub Item 4", Events::SubItem4), 173 | ) 174 | .item("Item 1", Events::Item1); 175 | 176 | if let Ok(menusys) = build_menu(&builder) { 177 | assert_eq!(menusys.ids.len(), 9); 178 | } else { 179 | panic!() 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/sys/windows/wchar.rs: -------------------------------------------------------------------------------- 1 | //! This module naively converts strings to UTF-16 for windows FFI, even though 2 | //! Windows does not follow to UTF-16 implementation very well. There might be 3 | //! some issues with this approach. 4 | //! 5 | //! For more robust implementation it would be better to use [OsString, 6 | //! OsStringExt and OsStrExt](https://doc.rust-lang.org/std/os/windows/ffi/index.html) 7 | 8 | /// Returns wchar vector 9 | /// 10 | /// Usage with winapi: wchar("Foo").as_ptr() gives LPCWSTR 11 | pub fn wchar(string: &str) -> Vec { 12 | format!("{}\0", string).encode_utf16().collect::>() 13 | } 14 | 15 | /// Copies string to WCHAR array, ensuring that array has null terminator 16 | /// 17 | /// Use this if winapi struct of certain size requires WCHAR array 18 | pub fn wchar_array(string: &str, dst: &mut [u16]) { 19 | let mut s = string.encode_utf16().collect::>(); 20 | 21 | // Truncate utf16 array to fit in the buffer with null terminator 22 | s.truncate(dst.len() - 1); 23 | 24 | dst[..s.len()].copy_from_slice(s.as_slice()); 25 | 26 | // Null terminator 27 | dst[s.len()] = 0; 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | /// Ensure that too long strings gets truncated and is null terminated 35 | #[test] 36 | fn test_wchar_too_long() { 37 | let mut dst: [u16; 5] = [99, 99, 99, 99, 99]; 38 | wchar_array("HELLO WORLD", dst.as_mut()); 39 | assert_eq!(dst, [72, 69, 76, 76, 0]); 40 | } 41 | 42 | /// Ensure that too short strings is null terminated 43 | #[test] 44 | fn test_wchar_too_short() { 45 | let mut dst: [u16; 5] = [99, 99, 99, 99, 99]; 46 | wchar_array("HI!", dst.as_mut()); 47 | assert_eq!(dst, [72, 73, 33, 0, 99]); 48 | } 49 | 50 | /// Ensure that empty string is null terminated 51 | #[test] 52 | fn test_wchar_empty() { 53 | let mut dst: [u16; 5] = [99, 99, 99, 99, 99]; 54 | wchar_array("", dst.as_mut()); 55 | assert_eq!(dst, [0, 99, 99, 99, 99]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/sys/windows/winhicon.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, IconBase}; 2 | use winapi::shared::minwindef::PBYTE; 3 | use winapi::shared::windef::HICON; 4 | use winapi::um::winuser; 5 | 6 | /// Purpose of this struct is to keep hicon handle, and drop it when the struct 7 | /// is dropped 8 | pub struct WinHIcon { 9 | pub hicon: HICON, 10 | } 11 | 12 | impl IconBase for WinHIcon { 13 | fn from_buffer( 14 | buffer: &'static [u8], 15 | width: Option, 16 | height: Option, 17 | ) -> Result { 18 | let offset = unsafe { 19 | winuser::LookupIconIdFromDirectoryEx( 20 | buffer.as_ptr() as PBYTE, 21 | 1, 22 | width.unwrap_or_default() as i32, 23 | height.unwrap_or_default() as i32, 24 | winuser::LR_DEFAULTCOLOR, 25 | ) 26 | }; 27 | if offset <= 0 { 28 | return Err(Error::IconLoadingFailed); 29 | } 30 | let icon_data = &buffer[offset as usize..]; 31 | let hicon = unsafe { 32 | winuser::CreateIconFromResourceEx( 33 | icon_data.as_ptr() as PBYTE, 34 | icon_data.len() as u32, 35 | 1, 36 | 0x30000, 37 | width.unwrap_or_default() as i32, 38 | height.unwrap_or_default() as i32, 39 | winuser::LR_DEFAULTCOLOR, 40 | ) 41 | }; 42 | if hicon.is_null() { 43 | return Err(Error::IconLoadingFailed); 44 | } 45 | Ok(WinHIcon { hicon }) 46 | } 47 | } 48 | 49 | impl Clone for WinHIcon { 50 | fn clone(&self) -> Self { 51 | WinHIcon { 52 | hicon: unsafe { winuser::CopyIcon(self.hicon) }, 53 | } 54 | } 55 | } 56 | 57 | unsafe impl Send for WinHIcon {} 58 | unsafe impl Sync for WinHIcon {} 59 | 60 | impl Drop for WinHIcon { 61 | fn drop(&mut self) { 62 | unsafe { winuser::DestroyIcon(self.hicon) }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/sys/windows/winhmenu.rs: -------------------------------------------------------------------------------- 1 | use super::wchar::wchar; 2 | use crate::Error; 3 | use std::fmt::Debug; 4 | use winapi::shared::windef::{HMENU, HWND}; 5 | use winapi::um::winuser; 6 | 7 | /// Purpose of this struct is to keep hmenu handle, and drop it when the struct 8 | /// is dropped 9 | #[derive(Debug)] 10 | pub struct WinHMenu { 11 | hmenu: HMENU, 12 | child_menus: Vec, 13 | } 14 | 15 | impl WinHMenu { 16 | pub(crate) fn new() -> Result { 17 | Ok(WinHMenu { 18 | hmenu: unsafe { 19 | let res = winuser::CreatePopupMenu(); 20 | if res.is_null() { 21 | return Err(Error::OsError); 22 | } 23 | res 24 | }, 25 | child_menus: vec![], 26 | }) 27 | } 28 | 29 | pub fn add_menu_item(&self, name: &str, id: usize, disabled: bool) -> bool { 30 | let res = unsafe { 31 | winuser::AppendMenuW( 32 | self.hmenu, 33 | { 34 | if disabled { 35 | winuser::MF_GRAYED 36 | } else { 37 | winuser::MF_STRING 38 | } 39 | }, 40 | id, 41 | wchar(name).as_ptr() as _, 42 | ) 43 | }; 44 | res >= 0 45 | } 46 | 47 | pub fn add_checkable_item( 48 | &self, 49 | name: &str, 50 | is_checked: bool, 51 | id: usize, 52 | disabled: bool, 53 | ) -> bool { 54 | let mut flags = if is_checked { 55 | winuser::MF_CHECKED 56 | } else { 57 | winuser::MF_UNCHECKED 58 | }; 59 | 60 | if disabled { 61 | flags |= winuser::MF_GRAYED 62 | } 63 | let res = unsafe { winuser::AppendMenuW(self.hmenu, flags, id, wchar(name).as_ptr() as _) }; 64 | res >= 0 65 | } 66 | pub fn add_child_menu(&mut self, name: &str, menu: WinHMenu, disabled: bool) -> bool { 67 | let mut flags = winuser::MF_POPUP; 68 | if disabled { 69 | flags |= winuser::MF_GRAYED 70 | } 71 | let res = unsafe { 72 | winuser::AppendMenuW( 73 | self.hmenu, 74 | flags, 75 | menu.hmenu as _, 76 | wchar(name).as_ptr() as _, 77 | ) 78 | }; 79 | self.child_menus.push(menu); 80 | res >= 0 81 | } 82 | 83 | pub fn add_separator(&self) -> bool { 84 | let res = unsafe { winuser::AppendMenuW(self.hmenu, winuser::MF_SEPARATOR, 0, 0 as _) }; 85 | res >= 0 86 | } 87 | 88 | pub fn track(&self, hwnd: HWND, x: i32, y: i32) { 89 | unsafe { winuser::TrackPopupMenu(self.hmenu, 0, x, y, 0, hwnd, std::ptr::null_mut()) }; 90 | } 91 | } 92 | 93 | unsafe impl Send for WinHMenu {} 94 | unsafe impl Sync for WinHMenu {} 95 | 96 | impl Drop for WinHMenu { 97 | fn drop(&mut self) { 98 | unsafe { winuser::DestroyMenu(self.hmenu) }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/sys/windows/winnotifyicon.rs: -------------------------------------------------------------------------------- 1 | use super::{msgs, wchar::wchar_array, winhicon::WinHIcon}; 2 | use std::fmt::Debug; 3 | use winapi::shared::windef::HWND; 4 | 5 | /// Purpose of this struct is to retain NotifyIconDataW and remove it on drop 6 | pub struct WinNotifyIcon { 7 | winhicon: WinHIcon, 8 | nid: winapi::um::shellapi::NOTIFYICONDATAW, 9 | } 10 | 11 | impl WinNotifyIcon { 12 | pub fn new(winhicon: &WinHIcon, tooltip: &Option) -> WinNotifyIcon { 13 | static mut ICON_ID: u32 = 1000; 14 | unsafe { 15 | ICON_ID += 1; 16 | } 17 | let mut icon = WinNotifyIcon { 18 | winhicon: winhicon.clone(), 19 | nid: unsafe { std::mem::zeroed() }, 20 | }; 21 | if let Some(tooltip) = tooltip { 22 | wchar_array(tooltip, icon.nid.szTip.as_mut()); 23 | } 24 | icon.nid.cbSize = std::mem::size_of::() as u32; 25 | icon.nid.uID = unsafe { ICON_ID }; 26 | icon.nid.uCallbackMessage = msgs::WM_USER_TRAYICON; 27 | icon.nid.hIcon = icon.winhicon.hicon; 28 | icon.nid.uFlags = winapi::um::shellapi::NIF_MESSAGE 29 | | winapi::um::shellapi::NIF_ICON 30 | | winapi::um::shellapi::NIF_TIP; 31 | 32 | icon 33 | } 34 | } 35 | 36 | impl WinNotifyIcon { 37 | pub fn add(&mut self, hwnd: HWND) -> bool { 38 | self.nid.hWnd = hwnd; 39 | let res = unsafe { 40 | winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_ADD, &mut self.nid) 41 | }; 42 | res == 1 43 | } 44 | 45 | pub fn remove(&mut self) -> bool { 46 | let res = unsafe { 47 | winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_DELETE, &mut self.nid) 48 | }; 49 | res == 1 50 | } 51 | 52 | pub fn set_icon(&mut self, winhicon: &WinHIcon) -> bool { 53 | self.winhicon = winhicon.clone(); 54 | self.nid.hIcon = self.winhicon.hicon; 55 | let res = unsafe { 56 | winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_MODIFY, &mut self.nid) 57 | }; 58 | res == 1 59 | } 60 | 61 | pub fn set_tooltip(&mut self, tooltip: &str) -> bool { 62 | wchar_array(tooltip, self.nid.szTip.as_mut()); 63 | let res = unsafe { 64 | winapi::um::shellapi::Shell_NotifyIconW(winapi::um::shellapi::NIM_MODIFY, &mut self.nid) 65 | }; 66 | res == 1 67 | } 68 | } 69 | unsafe impl Send for WinNotifyIcon {} 70 | unsafe impl Sync for WinNotifyIcon {} 71 | 72 | impl Debug for WinNotifyIcon { 73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 74 | write!(f, "TrayIcon") 75 | } 76 | } 77 | 78 | impl Drop for WinNotifyIcon { 79 | fn drop(&mut self) { 80 | unsafe { 81 | winapi::um::shellapi::Shell_NotifyIconW( 82 | winapi::um::shellapi::NIM_DELETE, 83 | &mut self.nid, 84 | ); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/sys/windows/wintrayicon.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | ops::{Deref, DerefMut}, 4 | }; 5 | use winapi::shared::minwindef::{HIWORD, LOWORD, LPARAM, LPVOID, LRESULT, UINT, WPARAM}; 6 | use winapi::shared::windef::{HBRUSH, HICON, HMENU, HWND, POINT}; 7 | use winapi::um::libloaderapi::GetModuleHandleW; 8 | use winapi::um::winuser; 9 | 10 | use super::wchar::wchar; 11 | use super::{msgs, winnotifyicon::WinNotifyIcon, MenuSys}; 12 | use crate::{trayiconsender::TrayIconSender, Error, Icon, MenuBuilder, TrayIconBase}; 13 | 14 | pub type WinTrayIcon = WindowBox; 15 | 16 | /// WindowBox retains the memory for the Window object until WM_NCDESTROY 17 | #[derive(Debug)] 18 | pub struct WindowBox(*mut WinTrayIconImpl) 19 | where 20 | T: PartialEq + Clone + 'static; 21 | 22 | impl Drop for WindowBox 23 | where 24 | T: PartialEq + Clone + 'static, 25 | { 26 | fn drop(&mut self) { 27 | unsafe { 28 | // PostMessage doesn't seem to work here, because winit exits before it manages to be processed 29 | 30 | // https://devblogs.microsoft.com/oldnewthing/20110926-00/?p=9553 31 | winuser::SendMessageW(self.hwnd, winuser::WM_CLOSE, 0, 0); 32 | } 33 | } 34 | } 35 | 36 | impl Deref for WindowBox 37 | where 38 | T: PartialEq + Clone + 'static, 39 | { 40 | type Target = WinTrayIconImpl; 41 | 42 | fn deref(&self) -> &WinTrayIconImpl { 43 | unsafe { &mut *(self.0) } 44 | } 45 | } 46 | 47 | impl DerefMut for WindowBox 48 | where 49 | T: PartialEq + Clone + 'static, 50 | { 51 | fn deref_mut(&mut self) -> &mut Self::Target { 52 | unsafe { &mut *(self.0) } 53 | } 54 | } 55 | 56 | /// Tray Icon WINAPI Window 57 | /// 58 | /// In Windows the Tray Icon requires a window for message pump, it's not shown. 59 | #[derive(Debug)] 60 | pub struct WinTrayIconImpl 61 | where 62 | T: PartialEq + Clone + 'static, 63 | { 64 | hwnd: HWND, 65 | sender: TrayIconSender, 66 | menu: Option>, 67 | notify_icon: WinNotifyIcon, 68 | on_click: Option, 69 | on_double_click: Option, 70 | on_right_click: Option, 71 | msg_taskbarcreated: Option, 72 | } 73 | 74 | unsafe impl Send for WinTrayIconImpl where T: PartialEq + Clone {} 75 | unsafe impl Sync for WinTrayIconImpl where T: PartialEq + Clone {} 76 | 77 | impl WinTrayIconImpl 78 | where 79 | T: PartialEq + Clone + 'static, 80 | { 81 | #[allow(clippy::new_ret_no_self)] 82 | #[allow(clippy::too_many_arguments)] 83 | pub(crate) fn new( 84 | sender: TrayIconSender, 85 | menu: Option>, 86 | notify_icon: WinNotifyIcon, 87 | on_click: Option, 88 | on_double_click: Option, 89 | on_right_click: Option, 90 | ) -> Result, Error> 91 | where 92 | T: PartialEq + Clone + 'static, 93 | { 94 | unsafe { 95 | let hinstance = GetModuleHandleW(0 as _); 96 | let wnd_class_name = wchar("TrayIconCls"); 97 | let wnd_class = winuser::WNDCLASSW { 98 | style: 0, 99 | lpfnWndProc: Some(WinTrayIconImpl::::winproc), 100 | hInstance: hinstance, 101 | lpszClassName: wnd_class_name.as_ptr() as _, 102 | cbClsExtra: 0, 103 | cbWndExtra: 0, 104 | hIcon: 0 as HICON, 105 | hCursor: 0 as HICON, 106 | hbrBackground: 0 as HBRUSH, 107 | lpszMenuName: 0 as _, 108 | }; 109 | winuser::RegisterClassW(&wnd_class); 110 | 111 | // Create window in a memory location that doesn't change 112 | let window = Box::new(WinTrayIconImpl { 113 | hwnd: 0 as HWND, 114 | notify_icon, 115 | menu, 116 | on_click, 117 | on_right_click, 118 | on_double_click, 119 | sender, 120 | msg_taskbarcreated: None, 121 | }); 122 | let ptr = Box::into_raw(window); 123 | let hwnd = winuser::CreateWindowExW( 124 | 0, 125 | wnd_class_name.as_ptr() as _, 126 | wchar("TrayIcon").as_ptr() as _, 127 | 0, //winuser::WS_OVERLAPPEDWINDOW | winuser::WS_VISIBLE, 128 | winuser::CW_USEDEFAULT, 129 | winuser::CW_USEDEFAULT, 130 | winuser::CW_USEDEFAULT, 131 | winuser::CW_USEDEFAULT, 132 | 0 as _, 133 | 0 as HMENU, 134 | hinstance, 135 | ptr as *mut _ as LPVOID, 136 | ) as u32; 137 | 138 | if hwnd == 0 { 139 | return Err(Error::OsError); 140 | } 141 | 142 | Ok(WindowBox(ptr)) 143 | } 144 | } 145 | 146 | pub fn wndproc(&mut self, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT { 147 | match msg { 148 | winuser::WM_CREATE => { 149 | // Create notification area icon 150 | self.notify_icon.add(self.hwnd); 151 | 152 | // Register to listen taskbar creation 153 | self.msg_taskbarcreated = unsafe { 154 | Some(winuser::RegisterWindowMessageA( 155 | "TaskbarCreated\0".as_ptr() as _ 156 | )) 157 | }; 158 | } 159 | 160 | msgs::WM_USER_SHOW_MENU => { 161 | if let Some(menu) = &self.menu { 162 | let mut pos = POINT { x: 0, y: 0 }; 163 | unsafe { 164 | winuser::GetCursorPos(&mut pos as _); 165 | winuser::SetForegroundWindow(self.hwnd); 166 | } 167 | menu.menu.track(self.hwnd, pos.x, pos.y); 168 | } 169 | } 170 | 171 | // Mouse events on the tray icon 172 | msgs::WM_USER_TRAYICON => { 173 | match lparam as u32 { 174 | // Left click tray icon 175 | winuser::WM_LBUTTONUP => { 176 | if let Some(e) = self.on_click.as_ref() { 177 | self.sender.send(e); 178 | } 179 | } 180 | 181 | // Right click tray icon 182 | winuser::WM_RBUTTONUP => { 183 | // Send right click event 184 | if let Some(e) = self.on_right_click.as_ref() { 185 | self.sender.send(e); 186 | } 187 | } 188 | 189 | // Double click tray icon 190 | winuser::WM_LBUTTONDBLCLK => { 191 | if let Some(e) = self.on_double_click.as_ref() { 192 | self.sender.send(e); 193 | } 194 | } 195 | _ => {} 196 | } 197 | } 198 | 199 | // Any of the menu commands 200 | // 201 | // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#parameters 202 | winuser::WM_COMMAND => { 203 | let identifier = LOWORD(wparam as u32); 204 | let cmd = HIWORD(wparam as u32); 205 | 206 | // Menu command 207 | if cmd == 0 { 208 | if let Some(v) = self.menu.as_ref() { 209 | if let Some(event) = v.ids.get(&(identifier as usize)) { 210 | self.sender.send(event); 211 | } 212 | } 213 | } 214 | } 215 | 216 | // TaskbarCreated 217 | x if Some(x) == self.msg_taskbarcreated => { 218 | self.notify_icon.add(self.hwnd); 219 | } 220 | 221 | // Default 222 | _ => { 223 | return unsafe { winuser::DefWindowProcW(self.hwnd, msg, wparam, lparam) }; 224 | } 225 | } 226 | 0 227 | } 228 | 229 | // This serves as a conduit for actual winproc in the subproc 230 | pub unsafe extern "system" fn winproc( 231 | hwnd: HWND, 232 | msg: UINT, 233 | wparam: WPARAM, 234 | lparam: LPARAM, 235 | ) -> LRESULT { 236 | match msg { 237 | winuser::WM_CREATE => { 238 | let create_struct: &mut winuser::CREATESTRUCTW = &mut *(lparam as *mut _); 239 | // Arc::from_raw(ptr) 240 | let window: &mut WinTrayIconImpl = 241 | &mut *(create_struct.lpCreateParams as *mut _); 242 | window.hwnd = hwnd; 243 | winuser::SetWindowLongPtrW(hwnd, winuser::GWL_USERDATA, window as *mut _ as _); 244 | window.wndproc(msg, wparam, lparam) 245 | } 246 | winuser::WM_NCDESTROY => { 247 | let window_ptr = winuser::SetWindowLongPtrW(hwnd, winuser::GWL_USERDATA, 0); 248 | if window_ptr != 0 { 249 | let ptr = window_ptr as *mut WinTrayIconImpl; 250 | let mut window = Box::from_raw(ptr); 251 | window.wndproc(msg, wparam, lparam) 252 | } else { 253 | winuser::DefWindowProcW(hwnd, msg, wparam, lparam) 254 | } 255 | } 256 | _ => { 257 | let window_ptr = winuser::GetWindowLongPtrW(hwnd, winuser::GWL_USERDATA); 258 | if window_ptr != 0 { 259 | let window: &mut WinTrayIconImpl = &mut *(window_ptr as *mut _); 260 | window.wndproc(msg, wparam, lparam) 261 | } else { 262 | winuser::DefWindowProcW(hwnd, msg, wparam, lparam) 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | impl TrayIconBase for WinTrayIconImpl 270 | where 271 | T: PartialEq + Clone + 'static, 272 | { 273 | /// Set the tooltip 274 | fn set_tooltip(&mut self, tooltip: &str) -> Result<(), Error> { 275 | if !self.notify_icon.set_tooltip(tooltip) { 276 | return Err(Error::OsError); 277 | } 278 | Ok(()) 279 | } 280 | 281 | /// Set icon 282 | fn set_icon(&mut self, icon: &Icon) -> Result<(), Error> { 283 | if !self.notify_icon.set_icon(&icon.sys) { 284 | return Err(Error::IconLoadingFailed); 285 | } 286 | Ok(()) 287 | } 288 | 289 | /// Set menu 290 | fn set_menu(&mut self, menu: &MenuBuilder) -> Result<(), Error> { 291 | if menu.menu_items.is_empty() { 292 | self.menu = None 293 | } else { 294 | self.menu = Some(menu.build()?); 295 | } 296 | Ok(()) 297 | } 298 | 299 | /// Show menu 300 | /// 301 | /// Currently shows always in mouse cursor coordinates 302 | fn show_menu(&mut self) -> Result<(), Error> { 303 | unsafe { 304 | winuser::PostMessageW(self.hwnd, msgs::WM_USER_SHOW_MENU, 0, 0); 305 | } 306 | Ok(()) 307 | } 308 | } 309 | 310 | impl Drop for WinTrayIconImpl 311 | where 312 | T: PartialEq + Clone + 'static, 313 | { 314 | fn drop(&mut self) { 315 | self.notify_icon.remove(); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/testresource/icon1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciantic/trayicon-rs/89eaa302cf2bb3fd079361763f5b92764db0aec6/src/testresource/icon1.ico -------------------------------------------------------------------------------- /src/testresource/icon2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ciantic/trayicon-rs/89eaa302cf2bb3fd079361763f5b92764db0aec6/src/testresource/icon2.ico -------------------------------------------------------------------------------- /src/trayicon.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Icon, MenuBuilder, TrayIconBase, TrayIconBuilder}; 2 | 3 | pub struct TrayIcon 4 | where 5 | T: PartialEq + Clone + 'static, 6 | { 7 | sys: crate::TrayIconSys, 8 | builder: TrayIconBuilder, 9 | } 10 | 11 | impl TrayIcon 12 | where 13 | T: PartialEq + Clone + 'static, 14 | { 15 | pub(crate) fn new(sys: crate::TrayIconSys, builder: TrayIconBuilder) -> TrayIcon { 16 | TrayIcon { builder, sys } 17 | } 18 | 19 | /// Set the icon if changed 20 | pub fn set_icon(&mut self, icon: &Icon) -> Result<(), Error> { 21 | if self.builder.icon.as_ref() == Ok(icon) { 22 | return Ok(()); 23 | } 24 | self.builder.icon = Ok(icon.clone()); 25 | self.sys.set_icon(icon) 26 | } 27 | 28 | /// Set the menu if changed 29 | /// 30 | /// This can be used reactively, each time the application state changes, 31 | /// build a new menu and set it with this method. This way one can avoid 32 | /// using more imperative `set_item_checkable`, `get_item_checkable` and 33 | /// `set_item_disabled` methods. 34 | pub fn set_menu(&mut self, menu: &MenuBuilder) -> Result<(), Error> { 35 | if self.builder.menu.as_ref() == Some(menu) { 36 | return Ok(()); 37 | } 38 | self.builder.menu = Some(menu.clone()); 39 | self.sys.set_menu(menu) 40 | } 41 | 42 | /// Set the tooltip if changed 43 | pub fn set_tooltip(&mut self, tooltip: &str) -> Result<(), Error> { 44 | if self.builder.tooltip.as_deref() == Some(tooltip) { 45 | return Ok(()); 46 | } 47 | self.builder.tooltip = Some(tooltip.to_string()); 48 | self.sys.set_tooltip(tooltip) 49 | } 50 | 51 | /// Set disabled 52 | /// 53 | /// Prefer building a new menu if application state changes instead of 54 | /// mutating a menu with this method. Suggestion is to use just `set_menu` 55 | /// method instead of this. 56 | pub fn set_menu_item_disabled(&mut self, id: T, disabled: bool) -> Result<(), Error> { 57 | if let Some(menu) = self.builder.menu.as_mut() { 58 | let _ = menu.set_disabled(id, disabled); 59 | let _ = self.sys.set_menu(menu); 60 | } 61 | Ok(()) 62 | } 63 | 64 | /// Set checkable 65 | /// 66 | /// Prefer building a new menu when application state changes instead of 67 | /// mutating a menu with this method. Suggestion is to use just `set_menu` 68 | /// method instead of this. 69 | pub fn set_menu_item_checkable(&mut self, id: T, checked: bool) -> Result<(), Error> { 70 | if let Some(menu) = self.builder.menu.as_mut() { 71 | let _ = menu.set_checkable(id, checked); 72 | let _ = self.sys.set_menu(menu); 73 | } 74 | Ok(()) 75 | } 76 | 77 | /// Get checkable state 78 | /// 79 | /// Prefer maintaining proper application state instead of getting checkable 80 | /// state with this method. Suggestion is to use just `set_menu` method 81 | /// instead of this. 82 | pub fn get_menu_item_checkable(&mut self, id: T) -> Option { 83 | if let Some(menu) = self.builder.menu.as_mut() { 84 | menu.get_checkable(id) 85 | } else { 86 | None 87 | } 88 | } 89 | 90 | pub fn show_menu(&mut self) -> Result<(), Error> { 91 | self.sys.show_menu() 92 | } 93 | } 94 | 95 | unsafe impl Sync for TrayIcon where T: PartialEq + Clone + 'static {} 96 | 97 | unsafe impl Send for TrayIcon where T: PartialEq + Clone + 'static {} 98 | -------------------------------------------------------------------------------- /src/trayiconbuilder.rs: -------------------------------------------------------------------------------- 1 | use crate::{trayiconsender::TrayIconSender, Icon, MenuBuilder, TrayIcon}; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | pub enum Error { 6 | MenuItemNotFound, 7 | IconLoadingFailed, 8 | SenderMissing, 9 | IconMissing, 10 | OsError, 11 | } 12 | 13 | // Why do I need to do this, can't Rust do this automatically? 14 | impl From<&Error> for Error { 15 | fn from(e: &Error) -> Self { 16 | *e 17 | } 18 | } 19 | 20 | impl Display for Error { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 22 | write!(f, "{}", self) 23 | } 24 | } 25 | 26 | impl std::error::Error for Error {} 27 | 28 | /// Tray Icon builder 29 | /// 30 | /// [Open full example with winit here 🢅](https://github.com/Ciantic/trayicon-rs/blob/master/examples/winit/src/main.rs) 31 | #[derive(Debug, Clone)] 32 | pub struct TrayIconBuilder 33 | where 34 | T: PartialEq + Clone + 'static, 35 | { 36 | pub(crate) icon: Result, 37 | pub(crate) menu: Option>, 38 | pub(crate) tooltip: Option, 39 | pub(crate) on_click: Option, 40 | pub(crate) on_double_click: Option, 41 | pub(crate) on_right_click: Option, 42 | pub(crate) sender: Option>, 43 | } 44 | 45 | impl TrayIconBuilder 46 | where 47 | T: PartialEq + Clone + 'static, 48 | { 49 | #[allow(clippy::new_without_default)] 50 | pub fn new() -> TrayIconBuilder { 51 | TrayIconBuilder { 52 | icon: Err(Error::IconMissing), 53 | menu: None, 54 | tooltip: None, 55 | on_click: None, 56 | on_double_click: None, 57 | on_right_click: None, 58 | sender: None, 59 | } 60 | } 61 | 62 | /// Conditionally include items, poor mans function composition 63 | pub fn when(self, f: F) -> Self 64 | where 65 | F: FnOnce(Self) -> Self, 66 | { 67 | f(self) 68 | } 69 | 70 | pub fn sender(mut self, cb: impl Fn(&T) + 'static) -> Self { 71 | self.sender = Some(TrayIconSender::new(cb)); 72 | self 73 | } 74 | 75 | pub fn tooltip(mut self, tooltip: &str) -> Self { 76 | self.tooltip = Some(tooltip.to_string()); 77 | self 78 | } 79 | 80 | pub fn on_click(mut self, id: T) -> Self { 81 | self.on_click = Some(id); 82 | self 83 | } 84 | 85 | pub fn on_double_click(mut self, id: T) -> Self { 86 | self.on_double_click = Some(id); 87 | self 88 | } 89 | 90 | pub fn on_right_click(mut self, id: T) -> Self { 91 | self.on_right_click = Some(id); 92 | self 93 | } 94 | 95 | pub fn icon(mut self, icon: Icon) -> Self { 96 | self.icon = Ok(icon); 97 | self 98 | } 99 | 100 | pub fn icon_from_buffer(mut self, buffer: &'static [u8]) -> Self { 101 | self.icon = Icon::from_buffer(buffer, None, None); 102 | self 103 | } 104 | 105 | pub fn menu(mut self, menu: MenuBuilder) -> Self 106 | where 107 | T: PartialEq + Clone + 'static, 108 | { 109 | self.menu = Some(menu); 110 | self 111 | } 112 | 113 | pub fn build(self) -> Result, Error> { 114 | Ok(TrayIcon::new(crate::build_trayicon(&self)?, self)) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/trayiconsender.rs: -------------------------------------------------------------------------------- 1 | /// Tray Icon event sender 2 | #[derive(Clone)] 3 | pub(crate) struct TrayIconSender(std::sync::Arc); 4 | 5 | impl std::fmt::Debug for TrayIconSender { 6 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 7 | f.debug_struct("TrayIconSender") 8 | .field("0", &"") 9 | .finish() 10 | } 11 | } 12 | 13 | impl TrayIconSender { 14 | pub(crate) fn new(f: impl Fn(&T) + 'static) -> Self { 15 | TrayIconSender(std::sync::Arc::new(f)) 16 | } 17 | } 18 | 19 | impl TrayIconSender 20 | where 21 | T: PartialEq + Clone + 'static, 22 | { 23 | pub fn send(&self, e: &T) { 24 | self.0(e) 25 | } 26 | } 27 | --------------------------------------------------------------------------------