diff --git a/Cargo.lock b/Cargo.lock index 0248b85e20..43e935a4f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -187,7 +187,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading", + "libloading 0.8.8", ] [[package]] @@ -349,7 +349,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -410,7 +410,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -430,6 +430,17 @@ dependencies = [ "webpki-roots 0.26.11", ] +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + [[package]] name = "atk-sys" version = "0.18.2" @@ -606,6 +617,15 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.1", +] + [[package]] name = "blocking" version = "1.6.2" @@ -660,7 +680,7 @@ checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -687,12 +707,27 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + [[package]] name = "cairo-sys-rs" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ + "glib-sys", "libc", "system-deps", ] @@ -882,7 +917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" dependencies = [ "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -1144,6 +1179,15 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1288,6 +1332,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -1306,6 +1359,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1314,7 +1377,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1323,7 +1386,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.8", ] [[package]] @@ -1354,6 +1417,12 @@ name = "dpi" version = "0.1.1" source = "git+https://github.com/iced-rs/winit.git?rev=11414b6aa45699f038114e61b4ddf5102b2d3b4b#11414b6aa45699f038114e61b4ddf5102b2d3b4b" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "drm" version = "0.12.0" @@ -1441,7 +1510,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1461,7 +1530,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1589,6 +1658,16 @@ dependencies = [ "iced", ] +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.2" @@ -1696,7 +1775,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1790,7 +1869,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -1848,6 +1927,34 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + [[package]] name = "gdk-pixbuf-sys" version = "0.18.0" @@ -1955,6 +2062,25 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + [[package]] name = "gio-sys" version = "0.18.1" @@ -1988,6 +2114,43 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "glib-sys" version = "0.18.1" @@ -2101,6 +2264,27 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + [[package]] name = "gtk-sys" version = "0.18.2" @@ -2119,6 +2303,19 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "guillotiere" version = "0.6.2" @@ -2483,6 +2680,7 @@ dependencies = [ "serde", "smol_str", "thiserror 1.0.69", + "tray-icon", "web-time", ] @@ -2657,6 +2855,7 @@ dependencies = [ "sysinfo", "thiserror 1.0.69", "tracing", + "tray-icon", "wasm-bindgen-futures", "web-sys", "window_clipboard", @@ -2848,7 +3047,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -2989,6 +3188,17 @@ dependencies = [ "mutate_once", ] +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.1", + "serde", + "unicode-segmentation", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -2996,7 +3206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading", + "libloading 0.8.8", "pkg-config", ] @@ -3062,6 +3272,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + [[package]] name = "libc" version = "0.2.174" @@ -3078,6 +3312,16 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.8" @@ -3105,6 +3349,25 @@ dependencies = [ "redox_syscall 0.5.15", ] +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + [[package]] name = "lilt" version = "0.8.1" @@ -3388,6 +3651,27 @@ dependencies = [ "iced", ] +[[package]] +name = "muda" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" +dependencies = [ + "crossbeam-channel", + "dpi 0.1.2", + "gtk", + "keyboard-types", + "libxdo", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + [[package]] name = "multer" version = "2.1.0" @@ -3579,7 +3863,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3638,10 +3922,10 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -3696,7 +3980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", @@ -3705,6 +3989,18 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-cloud-kit" version = "0.2.2" @@ -3712,7 +4008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -3724,7 +4020,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3736,18 +4032,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "objc2-core-foundation", +] + [[package]] name = "objc2-core-image" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -3759,7 +4076,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", "objc2-foundation 0.2.2", @@ -3778,7 +4095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2 0.5.2", @@ -3791,7 +4108,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.9.1", + "block2 0.6.1", "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -3800,9 +4119,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3813,7 +4132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3825,7 +4144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -3848,7 +4167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", @@ -3868,7 +4187,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -3880,7 +4199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.1", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -3972,7 +4291,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4048,7 +4367,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4087,7 +4406,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4097,6 +4416,19 @@ dependencies = [ "iced", ] +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + [[package]] name = "pango-sys" version = "0.18.0" @@ -4186,7 +4518,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4228,7 +4560,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4385,13 +4717,56 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit", + "toml_edit 0.22.27", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -4411,7 +4786,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "version_check", "yansi", ] @@ -4432,7 +4807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -4878,6 +5253,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -5106,7 +5490,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5129,7 +5513,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5458,7 +5842,7 @@ checksum = "0e9884adf2ac9e1f7ee7924be9130e000619fb10ecaa108ba39f20ba773e9ab4" dependencies = [ "js-sys", "libc", - "libloading", + "libloading 0.8.8", "memfd", "memmap2", "serde", @@ -5518,6 +5902,16 @@ dependencies = [ "zeno", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.104" @@ -5546,7 +5940,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5706,7 +6100,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5717,7 +6111,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5805,7 +6199,7 @@ checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" dependencies = [ "as-raw-xcb-connection", "ctor-lite", - "libloading", + "libloading 0.8.8", "pkg-config", "tracing", ] @@ -5895,7 +6289,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -5963,7 +6357,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -5975,6 +6369,28 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -5985,7 +6401,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.7.12", ] [[package]] @@ -6071,7 +6487,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -6109,6 +6525,35 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tray-icon" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "once_cell", + "png", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "tray_icon" +version = "0.1.0" +dependencies = [ + "iced", + "image", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6475,7 +6920,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -6510,7 +6955,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6852,7 +7297,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading", + "libloading 0.8.8", "log", "metal", "naga", @@ -7000,7 +7445,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7011,7 +7456,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7022,7 +7467,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7033,7 +7478,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7044,7 +7489,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7055,7 +7500,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7417,7 +7862,7 @@ dependencies = [ "android-activity", "atomic-waker", "bitflags 2.9.1", - "block2", + "block2 0.5.1", "bytemuck", "calloop", "cfg_aliases", @@ -7425,13 +7870,13 @@ dependencies = [ "core-foundation 0.9.4", "core-graphics 0.23.2", "cursor-icon", - "dpi", + "dpi 0.1.1", "js-sys", "libc", "memmap2", "ndk", "objc2 0.5.2", - "objc2-app-kit", + "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit", "orbclient", @@ -7459,6 +7904,15 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.12" @@ -7493,6 +7947,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -7513,7 +7977,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", + "libloading 0.8.8", "once_cell", "rustix 0.38.44", "x11rb-protocol", @@ -7603,7 +8067,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -7634,7 +8098,7 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow", + "winnow 0.7.12", "zbus_macros", "zbus_names", "zvariant", @@ -7646,10 +8110,10 @@ version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "zbus_names", "zvariant", "zvariant_utils", @@ -7663,7 +8127,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow", + "winnow 0.7.12", "zvariant", ] @@ -7690,7 +8154,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7710,7 +8174,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", "synstructure", ] @@ -7750,7 +8214,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.104", ] [[package]] @@ -7787,7 +8251,7 @@ dependencies = [ "enumflags2", "serde", "url", - "winnow", + "winnow 0.7.12", "zvariant_derive", "zvariant_utils", ] @@ -7798,10 +8262,10 @@ version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn", + "syn 2.0.104", "zvariant_utils", ] @@ -7815,6 +8279,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn", - "winnow", + "syn 2.0.104", + "winnow 0.7.12", ] diff --git a/Cargo.toml b/Cargo.toml index e1f0c671e2..5f6d525992 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,8 @@ strict-assertions = ["iced_renderer/strict-assertions"] unconditional-rendering = ["iced_winit/unconditional-rendering"] # Enables support for the `sipper` library sipper = ["iced_runtime/sipper"] +# Enables tray icon settings +tray-icon = ["iced_core/tray-icon", "iced_runtime/tray-icon", "iced_winit/tray-icon"] [dependencies] iced_debug.workspace = true @@ -202,6 +204,7 @@ softbuffer = "0.4" syntect = "5.1" sysinfo = "0.33" thiserror = "1.0" +tray-icon = "0.21" tiny-skia = "0.11" tokio = "1.0" tracing = "0.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index f57aaa4d3a..26c5ac50a6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -17,6 +17,7 @@ workspace = true auto-detect-theme = ["dep:dark-light"] advanced = [] crisp = [] +tray-icon = ["dep:tray-icon"] [dependencies] bitflags.workspace = true @@ -36,3 +37,6 @@ dark-light.optional = true serde.workspace = true serde.optional = true serde.features = ["derive"] + +tray-icon.workspace = true +tray-icon.optional = true diff --git a/core/src/event.rs b/core/src/event.rs index 7f0ab91438..dcf13ff187 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -5,6 +5,9 @@ use crate::mouse; use crate::touch; use crate::window; +#[cfg(feature = "tray-icon")] +use crate::tray_icon; + /// A user interface event. /// /// _**Note:** This type is largely incomplete! If you need to track @@ -27,6 +30,10 @@ pub enum Event { /// An input method event InputMethod(input_method::Event), + + #[cfg(feature = "tray-icon")] + /// A tray icon event + TrayIcon(tray_icon::Event), } /// The status of an [`Event`] after being processed. diff --git a/core/src/lib.rs b/core/src/lib.rs index e75ef2a7e5..dcbcd4b269 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -32,6 +32,9 @@ pub mod touch; pub mod widget; pub mod window; +#[cfg(feature = "tray-icon")] +pub mod tray_icon; + mod angle; mod background; mod color; diff --git a/core/src/tray_icon.rs b/core/src/tray_icon.rs new file mode 100644 index 0000000000..69e76f6fdd --- /dev/null +++ b/core/src/tray_icon.rs @@ -0,0 +1,265 @@ +//! Tray icon + +mod errors; +mod event; +mod settings; + +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; + +pub use errors::Error; +pub use event::Event; +pub use settings::*; + +/// Wrapper type for tray_icon +#[derive(Clone)] +pub struct TrayIcon { + icon: tray_icon::TrayIcon, + /// Mapping of MenuItem id and tray_icon MenuId + id_map: HashMap, +} + +impl TrayIcon { + /// Create new TrayIcon from Settings + pub fn new(settings: Settings) -> Result { + let mut attrs = tray_icon::TrayIconAttributes::default(); + if let Some(title) = settings.title { + attrs.title = Some(title.clone()); + } + if let Some(icon) = settings.icon { + let icon = icon.try_into()?; + attrs.icon = Some(icon); + } + if let Some(tooltip) = settings.tooltip { + attrs.tooltip = Some(tooltip.clone()); + } + let id_map = if let Some(menu_items) = settings.menu_items { + let mut id_map = HashMap::with_capacity(menu_items.len()); + let menu = tray_icon::menu::Menu::new(); + for menu_item in menu_items { + Self::build_menu_item(&mut id_map, &menu, menu_item)?; + } + attrs.menu = Some(Box::new(menu)); + id_map + } else { + HashMap::new() + }; + let icon = tray_icon::TrayIcon::new(attrs).map_err(Error::from)?; + let this = Self { + icon: icon, + id_map: id_map, + }; + + Ok(this) + } + + fn build_menu_item( + id_map: &mut HashMap, + menu: &impl tray_icon::menu::ContextMenu, + menu_item: MenuItem, + ) -> Result<(), Error> { + let menu_id = menu_item.id(); + let add_to_menu = |item: &dyn tray_icon::menu::IsMenuItem, + id_map: &mut HashMap| + -> Result<(), Error> { + if let Some(menu) = menu.as_menu() { + let _ = id_map.insert(item.id().0.clone(), menu_id); + menu.append(item).map_err(Error::from) + } else if let Some(submenu) = menu.as_submenu() { + let _ = id_map.insert(item.id().0.clone(), menu_id); + submenu.append(item).map_err(Error::from) + } else { + Err(Error::MenuError( + tray_icon::menu::Error::NotAChildOfThisMenu, + )) + } + }; + match menu_item { + MenuItem::Submenu { + text, menu_items, .. + } => { + let submenu = tray_icon::menu::Submenu::new(text, true); + for sub_menu_item in menu_items { + Self::build_menu_item(id_map, &submenu, sub_menu_item)?; + } + add_to_menu(&submenu, id_map) + } + MenuItem::Predefined { + predefined_type, + alternate_text, + } => { + let p = match predefined_type { + PredefinedMenuItem::Separator => { + tray_icon::menu::PredefinedMenuItem::separator() + } + PredefinedMenuItem::Copy => { + tray_icon::menu::PredefinedMenuItem::copy( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Cut => { + tray_icon::menu::PredefinedMenuItem::cut( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Paste => { + tray_icon::menu::PredefinedMenuItem::paste( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::SelectAll => { + tray_icon::menu::PredefinedMenuItem::select_all( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Undo => { + tray_icon::menu::PredefinedMenuItem::undo( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Redo => { + tray_icon::menu::PredefinedMenuItem::redo( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Minimize => { + tray_icon::menu::PredefinedMenuItem::minimize( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Maximize => { + tray_icon::menu::PredefinedMenuItem::maximize( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Fullscreen => { + tray_icon::menu::PredefinedMenuItem::fullscreen( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Hide => { + tray_icon::menu::PredefinedMenuItem::hide( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::HideOthers => { + tray_icon::menu::PredefinedMenuItem::hide_others( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::ShowAll => { + tray_icon::menu::PredefinedMenuItem::show_all( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::CloseWindow => { + tray_icon::menu::PredefinedMenuItem::close_window( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::Quit => { + tray_icon::menu::PredefinedMenuItem::quit( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::About(about_metadata) => { + let a: Option = + if let Some(a) = about_metadata { + let about = a.try_into()?; + Some(about) + } else { + None + }; + tray_icon::menu::PredefinedMenuItem::about( + alternate_text.as_deref(), + a, + ) + } + PredefinedMenuItem::Services => { + tray_icon::menu::PredefinedMenuItem::services( + alternate_text.as_deref(), + ) + } + PredefinedMenuItem::BringAllToFront => { + tray_icon::menu::PredefinedMenuItem::bring_all_to_front( + alternate_text.as_deref(), + ) + } + }; + add_to_menu(&p, id_map) + } + MenuItem::Text { + text, + enabled, + accelerator, + .. + } => { + let a: Option = + if let Some(a) = accelerator { + let a = a.try_into()?; + Some(a) + } else { + None + }; + let t = tray_icon::menu::MenuItem::new(text, enabled, a); + add_to_menu(&t, id_map) + } + MenuItem::Check { + text, + enabled, + checked, + accelerator, + .. + } => { + let a: Option = + if let Some(a) = accelerator { + let a = a.try_into()?; + Some(a) + } else { + None + }; + let c = tray_icon::menu::CheckMenuItem::new( + text, enabled, checked, a, + ); + add_to_menu(&c, id_map) + } + MenuItem::Icon { + text, + enabled, + icon, + accelerator, + .. + } => { + let i = icon.try_into()?; + let a: Option = + if let Some(a) = accelerator { + let a = a.try_into()?; + Some(a) + } else { + None + }; + let c = tray_icon::menu::IconMenuItem::new( + text, + enabled, + Some(i), + a, + ); + add_to_menu(&c, id_map) + } + } + } + + /// Fetch MenuItem Id for tray_icon MenuId + pub fn id_map(&self) -> HashMap { + self.id_map.clone() + } +} + +impl Debug for TrayIcon { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TrayIcon") + .field("icon", &self.icon.id()) + .field("id_map", &self.id_map) + .finish() + } +} diff --git a/core/src/tray_icon/errors.rs b/core/src/tray_icon/errors.rs new file mode 100644 index 0000000000..2cd6f7e12b --- /dev/null +++ b/core/src/tray_icon/errors.rs @@ -0,0 +1,21 @@ +//! Tray Icon creation errors + +/// An error that occurred during Tray Icon creation +#[derive(Debug, thiserror::Error)] +pub enum Error { + ///Failed to create icon + #[error("icon could not be parsed")] + BadIcon(#[from] tray_icon::BadIcon), + ///Failed to create the tray icon + #[error("tray icon could not be created")] + CreationError(#[from] tray_icon::Error), + ///Failed to create the tray icon menu + #[error("tray icon menu could not be created")] + MenuError(#[from] tray_icon::menu::Error), + ///Failed to create menu icon + #[error("menu icon could not be parsed")] + BadMenuIcon(#[from] tray_icon::menu::BadIcon), + ///Failed to create menu icon + #[error("accelerator could not be parsed")] + BadAccelerator(#[from] tray_icon::menu::AcceleratorParseError), +} diff --git a/core/src/tray_icon/event.rs b/core/src/tray_icon/event.rs new file mode 100644 index 0000000000..14061fc3b9 --- /dev/null +++ b/core/src/tray_icon/event.rs @@ -0,0 +1,197 @@ +//! Tray icon events + +use crate::{Point, Rectangle, mouse::Button}; + +use tray_icon::{TrayIconEvent, menu::MenuEvent}; + +/// A tray icon interaction +#[derive(Debug, Clone, PartialEq)] +pub enum Event { + /// Tray icon click started + MouseButtonPressed { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + /// Mouse button which triggered this event [Left, Middle, Right] + button: Button, + }, + /// Tray icon click ended + MouseButtonReleased { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + /// Mouse button which triggered this event [Left, Middle, Right] + button: Button, + }, + /// Tray icon double clicked + DoubleClicked { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + /// Mouse button which triggered this event [Left, Middle, Right] + button: Button, + }, + /// Mouse entered tray icon + MouseEntered { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + }, + /// Mouse moved over tray icon + MouseMoved { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + }, + /// Mouse exited tray icon + MouseExited { + /// Id of the tray icon which triggered this event. + id: String, + /// Position of the mouse cursor + position: Point, + /// Bounding rectangle of the tray icon + rect: Rectangle, + }, + /// Tray icon menu item clicked + MenuItemClicked { + /// Id of the tray icon which triggered this event. + id: String, + }, +} + +impl From for Event { + fn from(value: MenuEvent) -> Self { + Self::MenuItemClicked { id: value.id.0 } + } +} + +impl From for Event { + fn from(value: TrayIconEvent) -> Self { + match value { + TrayIconEvent::Click { + id, + position, + rect, + button, + button_state, + } => match button_state { + tray_icon::MouseButtonState::Up => Self::MouseButtonPressed { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + button: match button { + tray_icon::MouseButton::Left => Button::Left, + tray_icon::MouseButton::Middle => Button::Middle, + tray_icon::MouseButton::Right => Button::Right, + }, + }, + tray_icon::MouseButtonState::Down => { + Self::MouseButtonReleased { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + button: match button { + tray_icon::MouseButton::Left => Button::Left, + tray_icon::MouseButton::Middle => Button::Middle, + tray_icon::MouseButton::Right => Button::Right, + }, + } + } + }, + TrayIconEvent::DoubleClick { + id, + position, + rect, + button, + } => Self::DoubleClicked { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + button: match button { + tray_icon::MouseButton::Left => Button::Left, + tray_icon::MouseButton::Middle => Button::Middle, + tray_icon::MouseButton::Right => Button::Right, + }, + }, + TrayIconEvent::Enter { id, position, rect } => Self::MouseEntered { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + }, + TrayIconEvent::Move { id, position, rect } => Self::MouseMoved { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + }, + TrayIconEvent::Leave { id, position, rect } => Self::MouseExited { + id: id.0, + position: Point { + x: position.x as f32, + y: position.y as f32, + }, + rect: Rectangle { + x: rect.position.x as f32, + y: rect.position.y as f32, + width: rect.size.width as f32, + height: rect.size.height as f32, + }, + }, + _ => todo!("Unknown TrayIconEvent"), + } + } +} diff --git a/core/src/tray_icon/settings.rs b/core/src/tray_icon/settings.rs new file mode 100644 index 0000000000..f84181f733 --- /dev/null +++ b/core/src/tray_icon/settings.rs @@ -0,0 +1,465 @@ +//! Tray icon settings + +use std::fmt::{self, Debug, Display}; + +use crate::tray_icon::Error; + +use crate::{ + Size, + keyboard::{Modifiers, key::Code}, +}; + +/// Tray icon settings +#[derive(Debug)] +pub struct Settings { + /// Title of the icon + pub title: Option, + /// Icon to show (not available on web) + pub icon: Option, + /// Tooltip to show on hover + pub tooltip: Option, + /// Menu items + pub menu_items: Option>, +} + +/// Displays a key shortcut next to menu item +/// Only triggers when menu is open +#[derive(Debug)] +pub struct Accelerator(pub Code, pub Modifiers); + +impl TryFrom for tray_icon::menu::accelerator::Accelerator { + type Error = Error; + + fn try_from(value: Accelerator) -> Result { + let code = value.0.into(); + let modifiers = if value.1.is_empty() { + None + } else { + let mut m = tray_icon::menu::accelerator::Modifiers::empty(); + if value.1.alt() { + m |= tray_icon::menu::accelerator::Modifiers::ALT; + } + if value.1.shift() { + m |= tray_icon::menu::accelerator::Modifiers::SHIFT; + } + if value.1.control() { + m |= tray_icon::menu::accelerator::Modifiers::CONTROL; + } + if value.1.logo() { + m |= tray_icon::menu::accelerator::Modifiers::SUPER; + } + Some(m) + }; + Ok(Self::new(modifiers, code)) + } +} + +/// About description of the application +#[derive(Debug)] +pub struct AboutMetadata { + /// Name of the application + pub name: Option, + /// Version identifier + pub version: Option, + /// Short version identifier + pub short_version: Option, + /// Authors list + pub authors: Option>, + /// Comments + pub comments: Option, + /// Copyright details + pub copyright: Option, + /// License name + pub license: Option, + /// Website address + pub website: Option, + /// Label fdor website + pub website_label: Option, + /// Credits + pub credits: Option, + /// Icon for about dialog + pub icon: Option, +} + +impl TryFrom for tray_icon::menu::AboutMetadata { + type Error = Error; + + fn try_from(value: AboutMetadata) -> Result { + let icon: Option = if let Some(icon) = value.icon + { + let icon = icon.try_into()?; + Some(icon) + } else { + None + }; + Ok(tray_icon::menu::AboutMetadata { + name: value.name, + version: value.version, + short_version: value.short_version, + authors: value.authors, + comments: value.comments, + copyright: value.copyright, + license: value.license, + website: value.website, + website_label: value.website_label, + credits: value.credits, + icon: icon, + }) + } +} + +/// Predefined Menu Item configs +#[derive(Debug)] +pub enum PredefinedMenuItem { + /// Separator between menu items + Separator, + /// Copy + Copy, + /// Cut + Cut, + /// Paste + Paste, + /// Select all + SelectAll, + /// Undo + Undo, + /// Redo + Redo, + /// Minimize + Minimize, + /// Maximize + Maximize, + /// Fullscreen + Fullscreen, + /// Hide + Hide, + /// Hide others + HideOthers, + /// ShowAll + ShowAll, + /// CloseWindow + CloseWindow, + /// Quit + Quit, + /// About + About(Option), + /// Services + Services, + /// Bring all to front + BringAllToFront, +} + +impl Display for PredefinedMenuItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(self, f) + } +} + +/// Tray Icon Menu types +#[derive(Debug)] +pub enum MenuItem { + /// Define a new submenu + Submenu { + /// Id of the Menu Item + id: String, + /// Text to show for menu item + text: String, + /// Menu items in submenu + menu_items: Vec, + }, + /// Define a predefined menu item + Predefined { + /// The menu item type + predefined_type: PredefinedMenuItem, + /// Override the text of the menu item + alternate_text: Option, + }, + /// Define a menu item with text + Text { + /// Id of the Menu Item + id: String, + /// Text to show for menu item + text: String, + /// Is menu item enabled + enabled: bool, + /// Key shortcut + accelerator: Option, + }, + /// Define a menu item with a checkbox + Check { + /// Id of the Menu Item + id: String, + /// Text to show for menu item + text: String, + /// Is menu item enabled + enabled: bool, + /// Is the checkbox checked + checked: bool, + /// Key shortcut + accelerator: Option, + }, + /// Define a menu item with an icon + Icon { + /// Id of the Menu Item + id: String, + /// Text to show for menu item + text: String, + /// Is menu item enabled + enabled: bool, + /// Icon for the menu item + icon: Icon, + /// Key shortcut + accelerator: Option, + }, +} + +impl MenuItem { + /// Get the Id of the MenuItem + pub fn id(&self) -> String { + match self { + Self::Submenu { id, .. } => id.clone(), + Self::Text { id, .. } => id.clone(), + Self::Check { id, .. } => id.clone(), + Self::Icon { id, .. } => id.clone(), + Self::Predefined { + predefined_type, .. + } => predefined_type.to_string(), + } + } +} + +/// Icon data +#[derive(Debug)] +pub struct Icon { + /// RGBA byte data of icon image + pub rgba: Vec, + /// Size of icon image + pub size: Size, +} + +impl TryFrom for tray_icon::Icon { + type Error = Error; + fn try_from(value: Icon) -> Result { + Self::from_rgba(value.rgba, value.size.width, value.size.height) + .map_err(Self::Error::from) + } +} + +impl TryFrom for tray_icon::menu::Icon { + type Error = Error; + fn try_from(value: Icon) -> Result { + Self::from_rgba(value.rgba, value.size.width, value.size.height) + .map_err(Self::Error::from) + } +} + +/// Macro rule for easing enum conversion boilerplate even just a little +macro_rules! convert_enum { + ($src: ident, $dst: path, [$($src_variant: ident, $dst_variant: ident,)*], $($variant: ident,)*) => { + impl From<$src> for $dst { + fn from(src: $src) -> Self { + match src { + $($src::$src_variant => Self::$dst_variant,)* + $($src::$variant => Self::$variant,)* + } + } + } + }; +} + +convert_enum!( + Code, + tray_icon::menu::accelerator::Code, + [Meta, Super, SuperLeft, MetaLeft, SuperRight, MetaRight,], + Backquote, + Backslash, + BracketLeft, + BracketRight, + Comma, + Digit0, + Digit1, + Digit2, + Digit3, + Digit4, + Digit5, + Digit6, + Digit7, + Digit8, + Digit9, + Equal, + IntlBackslash, + IntlRo, + IntlYen, + KeyA, + KeyB, + KeyC, + KeyD, + KeyE, + KeyF, + KeyG, + KeyH, + KeyI, + KeyJ, + KeyK, + KeyL, + KeyM, + KeyN, + KeyO, + KeyP, + KeyQ, + KeyR, + KeyS, + KeyT, + KeyU, + KeyV, + KeyW, + KeyX, + KeyY, + KeyZ, + Minus, + Period, + Quote, + Semicolon, + Slash, + AltLeft, + AltRight, + Backspace, + CapsLock, + ContextMenu, + ControlLeft, + ControlRight, + Enter, + ShiftLeft, + ShiftRight, + Space, + Tab, + Convert, + KanaMode, + Lang1, + Lang2, + Lang3, + Lang4, + Lang5, + NonConvert, + Delete, + End, + Help, + Home, + Insert, + PageDown, + PageUp, + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + NumLock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + NumpadAdd, + NumpadBackspace, + NumpadClear, + NumpadClearEntry, + NumpadComma, + NumpadDecimal, + NumpadDivide, + NumpadEnter, + NumpadEqual, + NumpadHash, + NumpadMemoryAdd, + NumpadMemoryClear, + NumpadMemoryRecall, + NumpadMemoryStore, + NumpadMemorySubtract, + NumpadMultiply, + NumpadParenLeft, + NumpadParenRight, + NumpadStar, + NumpadSubtract, + Escape, + Fn, + FnLock, + PrintScreen, + ScrollLock, + Pause, + BrowserBack, + BrowserFavorites, + BrowserForward, + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + Eject, + LaunchApp1, + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + Hyper, + Turbo, + Abort, + Resume, + Suspend, + Again, + Copy, + Cut, + Find, + Open, + Paste, + Props, + Select, + Undo, + Hiragana, + Katakana, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + F26, + F27, + F28, + F29, + F30, + F31, + F32, + F33, + F34, + F35, +); diff --git a/core/src/window.rs b/core/src/window.rs index d0e741d86e..54864350a3 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -15,6 +15,7 @@ mod user_attention; pub use direction::Direction; pub use event::Event; pub use icon::Icon; +pub use id::GLOBAL; pub use id::Id; pub use level::Level; pub use mode::Mode; diff --git a/core/src/window/id.rs b/core/src/window/id.rs index ee0a4c5958..c5853b18f0 100644 --- a/core/src/window/id.rs +++ b/core/src/window/id.rs @@ -7,6 +7,9 @@ use std::sync::atomic::{self, AtomicU64}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Id(u64); +/// Id of the "Global" window. Will never match a window Id, +/// which means any message sent to it will automatically be marked as Status::Ignored +pub const GLOBAL: Id = Id(0); static COUNT: AtomicU64 = AtomicU64::new(1); impl Id { diff --git a/examples/tray_icon/Cargo.toml b/examples/tray_icon/Cargo.toml new file mode 100644 index 0000000000..12fe241b95 --- /dev/null +++ b/examples/tray_icon/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tray_icon" +version = "0.1.0" +authors = ["nate-trojian"] +edition = "2024" +publish = false + +[dependencies] +iced.workspace = true +iced.features = ["tray-icon"] + +image.workspace = true +image.features = ["png"] \ No newline at end of file diff --git a/examples/tray_icon/logo.png b/examples/tray_icon/logo.png new file mode 100644 index 0000000000..05299ba5eb Binary files /dev/null and b/examples/tray_icon/logo.png differ diff --git a/examples/tray_icon/src/main.rs b/examples/tray_icon/src/main.rs new file mode 100644 index 0000000000..3bd27a6e1f --- /dev/null +++ b/examples/tray_icon/src/main.rs @@ -0,0 +1,128 @@ +use std::path::Path; + +use iced::{ + Alignment::Center, + Element, Event, + Length::{Fill, Fixed}, + Subscription, Task, Theme, border, event, keyboard, tray_icon, + widget::{center, column, container, scrollable, text}, +}; + +pub fn main() -> iced::Result { + iced::application(App::default, App::update, App::view) + .subscription(App::subscription) + .tray_icon(tray_icon::Settings { + title: Some("Iced".into()), + icon: Some(load_icon(Path::new(&format!( + "{}/logo.png", + env!("CARGO_MANIFEST_DIR") + )))), + tooltip: Some("Iced".to_string()), + menu_items: Some(vec![ + tray_icon::MenuItem::Text { + id: "Hello".into(), + text: "Hello".into(), + enabled: true, + accelerator: Some(tray_icon::Accelerator( + keyboard::key::Code::KeyC, + keyboard::Modifiers::SHIFT, + )), + }, + tray_icon::MenuItem::Check { + id: "Checkbox".into(), + text: "Checked".into(), + enabled: true, + checked: true, + accelerator: None, + }, + tray_icon::MenuItem::Predefined { + predefined_type: tray_icon::PredefinedMenuItem::CloseWindow, + alternate_text: None, + }, + ]), + }) + .run() +} + +#[derive(Debug, Clone)] +enum Message { + Event(Event), +} + +struct App { + menu_events: Vec, +} + +impl App { + fn new() -> Self { + Self { + menu_events: Vec::new(), + } + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::Event(Event::TrayIcon(event)) => match event { + tray_icon::Event::MenuItemClicked { id } => { + self.menu_events.push(format!("Menu Item Clicked: {}", id)); + } + tray_icon::Event::MouseEntered { .. } => { + self.menu_events.push("Mouse Entered".into()); + } + tray_icon::Event::MouseExited { .. } => { + self.menu_events.push("Mouse Exited".into()); + } + _ => {} + }, + _ => {} + } + Task::none() + } + + fn view(&self) -> Element<'_, Message> { + let events = + container(column(self.menu_events.iter().map(|e| text(e).into()))) + .style(|theme: &Theme| { + let palette = theme.extended_palette(); + + container::Style::default().border( + border::color(palette.background.strong.color).width(4), + ) + }) + .padding(4); + let content = column![ + text("Tray Icon Events"), + scrollable(events).height(Fill).width(Fixed(400.0)) + ] + .width(Fill) + .align_x(Center) + .spacing(10); + center(content).into() + } + + fn subscription(&self) -> Subscription { + event::listen().map(Message::Event) + } +} + +impl Default for App { + fn default() -> Self { + Self::new() + } +} + +fn load_icon(path: &Path) -> iced::tray_icon::Icon { + println!("{:?}", path); + let (icon_rgba, icon_size) = { + let image = image::open(path) + .expect("Failed to open icon path") + .into_rgba8(); + let (width, height) = image.dimensions(); + let rgba = image.into_raw(); + (rgba, (width, height)) + }; + iced::tray_icon::Icon { + rgba: icon_rgba, + size: icon_size.into(), + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 2dc6047463..5cb1e00311 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -13,6 +13,9 @@ keywords.workspace = true [lints] workspace = true +[features] +tray-icon = [] + [dependencies] bytes.workspace = true iced_core.workspace = true diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 457f723cf0..60321b7dde 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -18,6 +18,9 @@ pub mod task; pub mod user_interface; pub mod window; +#[cfg(feature = "tray-icon")] +pub mod tray_icon; + pub use iced_core as core; pub use iced_debug as debug; pub use iced_futures as futures; @@ -56,6 +59,10 @@ pub enum Action { /// Run a system action. System(system::Action), + #[cfg(feature = "tray-icon")] + /// Pass through a Tray Icon Event + TrayIcon(tray_icon::Event), + /// Recreate all user interfaces and redraw all windows. Reload, @@ -82,6 +89,8 @@ impl Action { Action::Clipboard(action) => Err(Action::Clipboard(action)), Action::Window(action) => Err(Action::Window(action)), Action::System(action) => Err(Action::System(action)), + #[cfg(feature = "tray-icon")] + Action::TrayIcon(action) => Err(Action::TrayIcon(action)), Action::Reload => Err(Action::Reload), Action::Exit => Err(Action::Exit), } @@ -106,6 +115,10 @@ where } Action::Window(_) => write!(f, "Action::Window"), Action::System(action) => write!(f, "Action::System({action:?})"), + #[cfg(feature = "tray-icon")] + Action::TrayIcon(action) => { + write!(f, "Action::TrayIcon({action:?})") + } Action::Reload => write!(f, "Action::Reload"), Action::Exit => write!(f, "Action::Exit"), } diff --git a/runtime/src/tray_icon.rs b/runtime/src/tray_icon.rs new file mode 100644 index 0000000000..afe5a4d2f9 --- /dev/null +++ b/runtime/src/tray_icon.rs @@ -0,0 +1,2 @@ +//! Track tray_icon events. +pub use iced_core::tray_icon::*; diff --git a/src/application.rs b/src/application.rs index e735218b53..c009163ff6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -38,6 +38,9 @@ use crate::{ Element, Executor, Font, Result, Settings, Size, Subscription, Task, }; +#[cfg(feature = "tray-icon")] +use crate::tray_icon; + use iced_debug as debug; use std::borrow::Cow; @@ -152,6 +155,8 @@ where }, settings: Settings::default(), window: window::Settings::default(), + #[cfg(feature = "tray-icon")] + tray_icon: None, } } @@ -167,6 +172,8 @@ pub struct Application { raw: P, settings: Settings, window: window::Settings, + #[cfg(feature = "tray-icon")] + tray_icon: Option, } impl Application

{ @@ -189,7 +196,13 @@ impl Application

{ #[cfg(any(not(feature = "debug"), target_arch = "wasm32"))] let program = self.raw; - Ok(shell::run(program, self.settings, Some(self.window))?) + Ok(shell::run( + program, + self.settings, + Some(self.window), + #[cfg(feature = "tray-icon")] + self.tray_icon, + )?) } /// Sets the [`Settings`] that will be used to run the [`Application`]. @@ -320,6 +333,17 @@ impl Application

{ } } + #[cfg(feature = "tray-icon")] + /// Sets the [`tray_icon::Settings`] of the [`Application`]. + /// + /// Overwrites any previous [`tray_icon::Settings`]. + pub fn tray_icon(self, tray_icon_settings: tray_icon::Settings) -> Self { + Self { + tray_icon: Some(tray_icon_settings), + ..self + } + } + /// Sets the [`Title`] of the [`Application`]. pub fn title( self, @@ -333,6 +357,8 @@ impl Application

{ }), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -349,6 +375,8 @@ impl Application

{ }), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -365,6 +393,8 @@ impl Application

{ }), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -381,6 +411,8 @@ impl Application

{ }), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -397,6 +429,8 @@ impl Application

{ }), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -413,6 +447,8 @@ impl Application

{ raw: program::with_executor::(self.raw), settings: self.settings, window: self.window, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } } diff --git a/src/application/timed.rs b/src/application/timed.rs index 3d158874ac..9c78f47d1e 100644 --- a/src/application/timed.rs +++ b/src/application/timed.rs @@ -143,6 +143,8 @@ where }, settings: Settings::default(), window: window::Settings::default(), + #[cfg(feature = "tray-icon")] + tray_icon: None, } } diff --git a/src/daemon.rs b/src/daemon.rs index 2074cf7157..2777ce4443 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -6,6 +6,9 @@ use crate::theme; use crate::window; use crate::{Element, Executor, Font, Result, Settings, Subscription, Task}; +#[cfg(feature = "tray-icon")] +use crate::tray_icon; + use iced_debug as debug; use std::borrow::Cow; @@ -97,6 +100,8 @@ where _renderer: PhantomData, }, settings: Settings::default(), + #[cfg(feature = "tray-icon")] + tray_icon: None, } } @@ -111,6 +116,8 @@ where pub struct Daemon { raw: P, settings: Settings, + #[cfg(feature = "tray-icon")] + tray_icon: Option, } impl Daemon

{ @@ -133,7 +140,13 @@ impl Daemon

{ #[cfg(any(not(feature = "debug"), target_arch = "wasm32"))] let program = self.raw; - Ok(shell::run(program, self.settings, None)?) + Ok(shell::run( + program, + self.settings, + None, + #[cfg(feature = "tray-icon")] + self.tray_icon, + )?) } /// Sets the [`Settings`] that will be used to run the [`Daemon`]. @@ -169,6 +182,17 @@ impl Daemon

{ self } + #[cfg(feature = "tray-icon")] + /// Sets the [`tray_icon::Settings`] of the [`Daemon`]. + /// + /// Overwrites any previous [`tray_icon::Settings`]. + pub fn tray_icon(self, tray_icon_settings: tray_icon::Settings) -> Self { + Self { + tray_icon: Some(tray_icon_settings), + ..self + } + } + /// Sets the [`Title`] of the [`Daemon`]. pub fn title( self, @@ -181,6 +205,8 @@ impl Daemon

{ debug::hot(|| title.title(state, window)) }), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -196,6 +222,8 @@ impl Daemon

{ debug::hot(|| f(state)) }), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -211,6 +239,8 @@ impl Daemon

{ debug::hot(|| f(state, window)) }), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -226,6 +256,8 @@ impl Daemon

{ debug::hot(|| f(state, theme)) }), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -241,6 +273,8 @@ impl Daemon

{ debug::hot(|| f(state, window)) }), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } @@ -256,6 +290,8 @@ impl Daemon

{ Daemon { raw: program::with_executor::(self.raw), settings: self.settings, + #[cfg(feature = "tray-icon")] + tray_icon: self.tray_icon, } } } diff --git a/src/error.rs b/src/error.rs index 111bedf245..f83a6e044b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,9 @@ use crate::futures; use crate::graphics; use crate::shell; +#[cfg(feature = "tray-icon")] +use crate::core::tray_icon; + /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -16,6 +19,11 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] GraphicsCreationFailed(graphics::Error), + + #[cfg(feature = "tray-icon")] + /// The application tray icon could not be created + #[error("the application tray icon could not be created")] + TrayIconCreationFailed(tray_icon::Error), } impl From for Error { @@ -30,6 +38,10 @@ impl From for Error { shell::Error::GraphicsCreationFailed(error) => { Error::GraphicsCreationFailed(error) } + #[cfg(feature = "tray-icon")] + shell::Error::TrayIconCreationFailed(error) => { + Error::TrayIconCreationFailed(error) + } } } } diff --git a/src/lib.rs b/src/lib.rs index a933d21ffa..258ef1f4e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -510,6 +510,9 @@ pub mod daemon; pub mod time; pub mod window; +#[cfg(feature = "tray-icon")] +pub mod tray_icon; + #[cfg(feature = "advanced")] pub mod advanced; diff --git a/src/tray_icon.rs b/src/tray_icon.rs new file mode 100644 index 0000000000..3ddfdbe257 --- /dev/null +++ b/src/tray_icon.rs @@ -0,0 +1,9 @@ +//! Configure the tray icon for your application +pub use crate::core::tray_icon::AboutMetadata; +pub use crate::core::tray_icon::Accelerator; +pub use crate::core::tray_icon::Error; +pub use crate::core::tray_icon::Event; +pub use crate::core::tray_icon::Icon; +pub use crate::core::tray_icon::MenuItem; +pub use crate::core::tray_icon::PredefinedMenuItem; +pub use crate::core::tray_icon::Settings; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index f215797818..4fb3ba1d74 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -23,6 +23,7 @@ wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] unconditional-rendering = [] +tray-icon = ["dep:tray-icon"] [dependencies] iced_debug.workspace = true @@ -38,6 +39,9 @@ winit.workspace = true sysinfo.workspace = true sysinfo.optional = true +tray-icon.workspace = true +tray-icon.optional = true + [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys.workspace = true web-sys.features = ["Document", "Window", "HtmlCanvasElement"] diff --git a/winit/src/error.rs b/winit/src/error.rs index 2b7f4344eb..9f1083d6f9 100644 --- a/winit/src/error.rs +++ b/winit/src/error.rs @@ -1,6 +1,9 @@ use crate::futures::futures; use crate::graphics; +#[cfg(feature = "tray-icon")] +use crate::runtime::tray_icon; + /// An error that occurred while running an application. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -15,6 +18,11 @@ pub enum Error { /// The application graphics context could not be created. #[error("the application graphics context could not be created")] GraphicsCreationFailed(graphics::Error), + + #[cfg(feature = "tray-icon")] + /// The application tray icon could not be created + #[error("the application tray icon could not be created")] + TrayIconCreationFailed(#[from] tray_icon::Error), } impl From for Error { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index c4e6286319..78acc1d98c 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -69,6 +69,9 @@ pub fn run

( program: P, settings: Settings, window_settings: Option, + #[cfg(feature = "tray-icon")] tray_icon_settings: Option< + runtime::tray_icon::Settings, + >, ) -> Result<(), Error> where P: Program + 'static, @@ -83,6 +86,36 @@ where .build() .expect("Create event loop"); + #[cfg(feature = "tray-icon")] + // Icon must exist outside of the anonymous scope in order to not be dropped + let icon = build_tray_icon(tray_icon_settings)?; + + #[cfg(feature = "tray-icon")] + { + use tray_icon::{TrayIconEvent, menu::MenuEvent}; + + let icon = icon.clone(); + + if let Some(icon) = icon { + let id_map = icon.id_map(); + + let event_loop_proxy = event_loop.create_proxy(); + TrayIconEvent::set_event_handler(Some(move |e| { + let _ = event_loop_proxy.send_event(Action::TrayIcon( + runtime::tray_icon::Event::from(e), + )); + })); + let event_loop_proxy = event_loop.create_proxy(); + MenuEvent::set_event_handler(Some(move |e: MenuEvent| { + let _ = event_loop_proxy.send_event(Action::TrayIcon( + runtime::tray_icon::Event::MenuItemClicked { + id: id_map.get(&e.id.0).unwrap().clone(), + }, + )); + })); + } + } + let (proxy, worker) = Proxy::new(event_loop.create_proxy()); #[cfg(feature = "debug")] @@ -1041,6 +1074,20 @@ async fn run_instance

( let _ = ManuallyDrop::into_inner(user_interfaces); } +#[cfg(feature = "tray-icon")] +/// Build the tray icon for if it exists +fn build_tray_icon( + settings: Option, +) -> Result, Error> { + match settings { + Some(settings) => { + let icon = runtime::tray_icon::TrayIcon::new(settings)?; + Ok(Some(icon)) + } + None => Ok(None), + } +} + /// Builds a window's [`UserInterface`] for the [`Program`]. fn build_user_interface<'a, P: Program>( program: &'a program::Instance

, @@ -1108,6 +1155,9 @@ fn run_action<'a, P, C>( use crate::runtime::system; use crate::runtime::window; + #[cfg(feature = "tray-icon")] + use crate::core::window::GLOBAL; + match action { Action::Output(message) => { messages.push(message); @@ -1419,6 +1469,10 @@ fn run_action<'a, P, C>( } } }, + #[cfg(feature = "tray-icon")] + Action::TrayIcon(event) => { + events.push((GLOBAL, core::Event::TrayIcon(event))) + } Action::Widget(operation) => { let mut current_operation = Some(operation);