diff --git a/Cargo.lock b/Cargo.lock index 1bb893cd..5a8ece17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "async-broadcast" @@ -28,6 +28,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-broadcast" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bbd92a9bd0e9c1298118ecf8a2f825e86b12c3ec9e411573e34aaf3a0c03cdd" +dependencies = [ + "easy-parallel", + "event-listener", + "futures-core", + "parking_lot 0.11.2", +] + [[package]] name = "async-channel" version = "1.6.1" @@ -55,9 +67,9 @@ dependencies = [ [[package]] name = "async-io" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ "concurrent-queue", "futures-lite", @@ -109,15 +121,26 @@ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -143,10 +166,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] -name = "bumpalo" -version = "3.9.1" +name = "block-buffer" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte_string" @@ -174,9 +206,9 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cairo-rs" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129e928d3eda625f53ce257589efbe5143416875fd01bddd08c8c6feb8b9962b" +checksum = "62be3562254e90c1c6050a72aa638f6315593e98c5cdaba9017cedbabf0a5dee" dependencies = [ "bitflags", "cairo-sys-rs", @@ -198,12 +230,15 @@ dependencies = [ [[package]] name = "calloop" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +checksum = "84221146483fe8a40de9af92111abd5a71ab8cf0e3c673df2b209f891a82e02e" dependencies = [ "log", - "nix 0.22.3", + "nix 0.24.1", + "slotmap", + "thiserror", + "vec_map", ] [[package]] @@ -220,9 +255,9 @@ checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-expr" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e068cb2806bbc15b439846dc16c5f89f8599f2c3e4d73d4449d38f9b2f0b6c5" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" dependencies = [ "smallvec", ] @@ -255,6 +290,31 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "cosmic-app-list" +version = "0.1.0" +dependencies = [ + "anyhow", + "cascade", + "cosmic-panel-config", + "futures", + "futures-util", + "gio", + "gsk4", + "gtk4", + "i18n-embed", + "i18n-embed-fl", + "libcosmic", + "once_cell", + "pretty_env_logger", + "relm4-macros 0.4.4", + "rust-embed", + "serde", + "serde_json", + "tokio", + "xdg", +] + [[package]] name = "cosmic-applet-audio" version = "0.1.0" @@ -311,7 +371,7 @@ dependencies = [ "futures-util", "gtk4", "logind-zbus", - "nix 0.23.1", + "nix 0.24.1", "once_cell", "relm4-macros 0.4.4", "tokio", @@ -346,10 +406,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "cosmic-panel-button" +version = "0.1.0" +dependencies = [ + "anyhow", + "cascade", + "cosmic-panel-config", + "gio", + "gtk4", + "i18n-embed", + "i18n-embed-fl", + "once_cell", + "pretty_env_logger", + "rust-embed", +] + [[package]] name = "cosmic-panel-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#3022429a50e200b5055015de6ccddb39a4cfb597" +source = "git+https://github.com/pop-os/cosmic-panel#d53d6a4d24347dd50068ac174861b1ee37395b10" dependencies = [ "anyhow", "gtk4", @@ -361,6 +437,15 @@ dependencies = [ "xdg", ] +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-channel" version = "0.5.4" @@ -383,9 +468,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.12.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ "darling_core", "darling_macro", @@ -393,9 +478,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.12.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" dependencies = [ "fnv", "ident_case", @@ -407,15 +492,27 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.12.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ "darling_core", "quote", "syn", ] +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "parking_lot_core 0.9.3", +] + [[package]] name = "derivative" version = "2.2.0" @@ -429,18 +526,18 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d918e7dabe374a51dae0f29d818fece3b218b8b4eabec3bc4d42c537e7ed8f" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f712c2d4e52d5fcae53584e461dcb92fb2202e144ebf83ab0ba4360d18b767c7" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" dependencies = [ "darling", "proc-macro2", @@ -450,14 +547,23 @@ dependencies = [ [[package]] name = "derive_builder_macro" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2ac71b4a9a590dde6cee3ca4687aca5e7ce06f4ee297c5a959de5f1e42b2e" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" dependencies = [ "derive_builder_core", "syn", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "dirs" version = "3.0.2" @@ -516,9 +622,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", "serde", @@ -535,6 +641,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -572,6 +691,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + [[package]] name = "flume" version = "0.10.12" @@ -734,9 +906,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678516f1baef591d270ca10587c01a12542a731a7879cc62391a18191a470831" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" dependencies = [ "bitflags", "gdk-pixbuf-sys", @@ -760,9 +932,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a2fc0bd03d59383fc10b71a8cb731a1fac2998732a36a0c03e9b1de1513218" +checksum = "4fabb7cf843c26b085a5d68abb95d0c0bf27a9ae2eeff9c4adb503a1eb580876" dependencies = [ "bitflags", "cairo-rs", @@ -776,9 +948,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48a39e34abe35ee2cf54a1e29dd983accecd113ad30bdead5050418fa92f2a1b" +checksum = "efe7dcb44f5c00aeabff3f69abfc5673de46559070f89bd3fbb7b66485d9cef2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -793,9 +965,9 @@ dependencies = [ [[package]] name = "gdk4-wayland" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3604742a9967cf74756cef18e56fbd6481e7bb90f3b931211abb18c0a6a4aa" +checksum = "cf81f00824c5f9862764c18ef061efe12b9c4f10614f74d3eaf1f18852c335e2" dependencies = [ "gdk4", "gdk4-wayland-sys", @@ -807,9 +979,9 @@ dependencies = [ [[package]] name = "gdk4-wayland-sys" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cbf7fa3fc7714c72902d82229677f9291f7cceb33855c5cef868f177356c30" +checksum = "41f2375ec73e2ec6815cdf1da330ff2e020b46fab9057d1e06f44909f1789898" dependencies = [ "glib-sys", "libc", @@ -818,9 +990,9 @@ dependencies = [ [[package]] name = "gdk4-x11" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a54a4f3405461afa18ddc2b5fbeaecc2558fcd8b132ed0c9c7c4ffa2f9ae22" +checksum = "be84e388c6b74cce3f9232904ce87ae1857ee3a41a20d9d8a16ae8792799b27c" dependencies = [ "gdk4", "gdk4-x11-sys", @@ -832,9 +1004,9 @@ dependencies = [ [[package]] name = "gdk4-x11-sys" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eb40aebb4f15b270df2ac2c463bf7f6d82211d9c5df1d13b84541a63a3139d7" +checksum = "3f85f9dabcc847c0733246822bebb476dcbb93f5a964d561b46b69f00fdbbf44" dependencies = [ "gdk4-sys", "glib-sys", @@ -842,6 +1014,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.6" @@ -877,9 +1059,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cd21a7a674ea811749661012512b0ba5237ba404ccbcab2850db5537549b64" +checksum = "0f132be35e05d9662b9fa0fee3f349c6621f7782e0105917f4cc73c1bf47eceb" dependencies = [ "bitflags", "futures-channel", @@ -907,9 +1089,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a826fad715b57834920839d7a594c3b5e416358c7d790bdaba847a40d7c1d96d" +checksum = "bd124026a2fa8c33a3d17a3fe59c103f2d9fa5bd92c19e029e037736729abeab" dependencies = [ "bitflags", "futures-channel", @@ -927,9 +1109,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac4d47c544af67747652ab1865ace0ffa1155709723ac4f32e97587dd4735b2" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" dependencies = [ "anyhow", "heck", @@ -986,9 +1168,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d5a47a78c682bb67496b562495ed84972c0512ba0654888c4dc92b80a85bd3" +checksum = "05e9020d333280b3aa38d496495bfa9b50712eebf1ad63f0ec5bcddb5eb61be4" dependencies = [ "bitflags", "cairo-rs", @@ -1002,9 +1184,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31d21d7ce02ba261bb24c50c4ab238a10b41a2c97c32afffae29471b7cca69b" +checksum = "7add39ccf60078508c838643a2dcc91f045c46ed63b5ea6ab701b2e25bda3fea" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1018,9 +1200,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5d40303dabe4608fc260de2bd7563da6f85bc90af956323f0cd8ae0abcfe03" +checksum = "c64f0c2a3d80e899dc3febddad5bac193ffcf74a0fd7e31037f30dd34d6f7396" dependencies = [ "bitflags", "cairo-rs", @@ -1041,9 +1223,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3c4aa605fb3d78205c7aef0eeaa6db61d8cc4dd05a465dc6ffdfdaee84f825" +checksum = "fafbcc920af4eb677d7d164853e7040b9de5a22379c596f570190c675d45f7a7" dependencies = [ "anyhow", "proc-macro-crate", @@ -1056,9 +1238,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47c075e8f795c38f6e9a47b51a73eab77b325f83c0154979ed4d4245c36490d" +checksum = "5bc8006eea634b7c72da3ff79e24606e45f21b3b832a3c5a1f543f5f97eb0f63" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1073,6 +1255,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + [[package]] name = "heck" version = "0.4.0" @@ -1094,6 +1282,84 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "i18n-config" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62affcd43abfb51f3cbd8736f9407908dc5b44fc558a9be07460bbfd104d983" +dependencies = [ + "log", + "serde", + "serde_derive", + "thiserror", + "toml", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f21ed76e44de8ac3dfa36bb37ab2e6480be0dc75c612474949be1f3cb2c253" +dependencies = [ + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "locale_config", + "log", + "parking_lot 0.12.1", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420a9718ef9d0ab727840a398e25408ea0daff9ba3c681707ba05485face98e" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "strsim", + "syn", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db2330e035808eb064afb67e6743ddce353763af3e0f2bdfc2476e00ce76136" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1129,6 +1395,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "intl-memoizer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" +dependencies = [ + "tinystr", + "unic-langid", +] + [[package]] name = "itertools" version = "0.10.3" @@ -1139,10 +1425,16 @@ dependencies = [ ] [[package]] -name = "js-sys" -version = "0.3.56" +name = "itoa" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -1175,14 +1467,14 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#b606ed6a9b07d4b2cccf6858f2411969120dc7cb" +source = "git+https://github.com/pop-os/libcosmic#d004d686bd83e55f8f5058700941284ca84dc579" dependencies = [ "cascade", "derivative", @@ -1192,6 +1484,7 @@ dependencies = [ "gio", "gobject-sys", "gtk4", + "libcosmic-widgets", "once_cell", "wayland-client", "wayland-protocols", @@ -1201,7 +1494,7 @@ dependencies = [ [[package]] name = "libcosmic-widgets" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?branch=lucy/widgets#d004d686bd83e55f8f5058700941284ca84dc579" +source = "git+https://github.com/pop-os/libcosmic#d004d686bd83e55f8f5058700941284ca84dc579" dependencies = [ "relm4", "relm4-macros 0.4.1", @@ -1270,9 +1563,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -1299,9 +1592,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" @@ -1329,25 +1622,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1397,6 +1679,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.1" @@ -1425,15 +1719,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1447,9 +1732,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1457,9 +1742,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -1476,9 +1761,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] @@ -1518,6 +1803,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "ordered-stream" version = "0.0.1" @@ -1561,19 +1852,44 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.3", ] [[package]] name = "parking_lot_core" -version = "0.9.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", @@ -1613,9 +1929,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1673,6 +1989,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -1709,11 +2035,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1738,6 +2064,12 @@ dependencies = [ "libpulse-binding", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-xml" version = "0.22.0" @@ -1749,9 +2081,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -1808,9 +2140,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -1819,16 +2151,16 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "relm4" version = "0.4.1" source = "git+https://github.com/AaronErhardt/relm4?rev=7404ad64ca8763f6629cadcd743947cd29e1538a#7404ad64ca8763f6629cadcd743947cd29e1538a" dependencies = [ - "async-broadcast", + "async-broadcast 0.3.4", "async-oneshot", "flume", "fragile", @@ -1860,6 +2192,15 @@ dependencies = [ "syn", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ron" version = "0.7.0" @@ -1871,6 +2212,46 @@ dependencies = [ "serde", ] +[[package]] +name = "rust-embed" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a17e5ac65b318f397182ae94e532da0ba56b88dd1200b774715d36c4943b1c3" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756feca3afcbb1487a1d01f4ecd94cf8ec98ea074c55a69e7136d29fb6166029" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.3.3" @@ -1880,6 +2261,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + [[package]] name = "same-file" version = "1.0.6" @@ -1901,6 +2288,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + [[package]] name = "semver" version = "0.11.0" @@ -1921,18 +2314,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -1940,10 +2333,21 @@ dependencies = [ ] [[package]] -name = "serde_repr" -version = "0.1.7" +name = "serde_json" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", @@ -1965,6 +2369,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2004,7 +2421,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay-client-toolkit" version = "0.15.4" -source = "git+https://github.com/smithay/client-toolkit.git#e9eeb05e997d80259d085a94acd88d91704050b6" +source = "git+https://github.com/wash2/client-toolkit.git#ebcf9b3ed6454b050621e021f4bf4136fc810965" dependencies = [ "bitflags", "calloop", @@ -2031,9 +2448,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" dependencies = [ "lock_api", ] @@ -2052,13 +2469,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.90" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2081,19 +2498,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" [[package]] -name = "thiserror" -version = "1.0.30" +name = "tempfile" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2121,10 +2561,16 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.17.0" +name = "tinystr" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + +[[package]] +name = "tokio" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95eec79ea28c00a365f539f1961e9278fbcaf81c0ff6aaf0e93c181352446948" dependencies = [ "bytes", "libc", @@ -2132,7 +2578,7 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2142,9 +2588,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -2153,9 +2599,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -2180,6 +2626,21 @@ dependencies = [ "syn", ] +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "ucd-trie" version = "0.1.3" @@ -2187,10 +2648,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "uds_windows" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" @@ -2235,9 +2731,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2245,9 +2741,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -2260,9 +2756,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2270,9 +2766,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -2283,9 +2779,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wayland-client" @@ -2401,9 +2897,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -2414,33 +2910,33 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "x11" @@ -2478,11 +2974,11 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" [[package]] name = "zbus" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb86f3d4592e26a48b2719742aec94f8ae6238ebde20d98183ee185d1275e9a" +checksum = "53819092b9db813b2c6168b097b4b13ad284d81c9f2b0165a0a1b190e505a1f3" dependencies = [ - "async-broadcast", + "async-broadcast 0.4.0", "async-channel", "async-executor", "async-io", @@ -2507,6 +3003,7 @@ dependencies = [ "serde_repr", "sha1", "static_assertions", + "uds_windows", "winapi", "zbus_macros", "zbus_names", @@ -2515,9 +3012,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36823cc10fddc3c6b19f048903262dacaf8274170e9a255784bdd8b4570a8040" +checksum = "c7174ebe6722c280d6d132d694bb5664ce50a788cb70eeb518e7fc1ca095a114" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2539,9 +3036,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.1.2" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ea5dc38b2058fae6a5b79009388143dadce1e91c26a67f984a0fc0381c8033" +checksum = "cbd1abd8bc2c855412b9c8af9fc11c0d695c73c732ad5a1a1be10f3fd4bf19b2" dependencies = [ "byteorder", "enumflags2", @@ -2553,9 +3050,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.1.2" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2cecc5a61c2a053f7f653a24cd15b3b0195d7f7ddb5042c837fb32e161fb7a" +checksum = "abebd57382dfacf3e7bbdd7b7c3d162d6ed0687a78f046263ddef4ddabc275ae" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index f67e1075..cfa413b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,11 @@ [workspace] -members = ["applets/cosmic-applet-audio", "applets/cosmic-applet-graphics", "applets/cosmic-applet-network", "applets/cosmic-applet-power", "applets/cosmic-applet-status-area", "old-panel"] +members = [ + "applets/cosmic-applet-audio", + "applets/cosmic-applet-graphics", + "applets/cosmic-applet-network", + "applets/cosmic-applet-power", + "applets/cosmic-applet-status-area", + "applets/cosmic-app-list", + "applets/cosmic-panel-button", + "old-panel", + ] diff --git a/applets/cosmic-app-list/Cargo.toml b/applets/cosmic-app-list/Cargo.toml new file mode 100644 index 00000000..1b5dace7 --- /dev/null +++ b/applets/cosmic-app-list/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "cosmic-app-list" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +cosmic-panel-config = { git = "https://github.com/pop-os/cosmic-panel/", features = ["gtk4"] } +cascade = "1.0.0" +gtk4 = { version = "0.4.5", features = ["v4_4"] } +gio = "0.15.3" +libcosmic = { git = "https://github.com/pop-os/libcosmic" } +relm4-macros = "0.4.4" +serde = "1.0.136" +serde_json = "1.0.78" +tokio = { version = "1.16.1", features = ["sync"] } +futures = "0.3.19" +futures-util = "0.3.19" +once_cell = "1.9.0" +xdg = "2.4.0" +gsk4 = "0.4.6" +pretty_env_logger = "0.4" +anyhow = "1.0.50" +i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.6.4" +rust-embed = "6.3.0" + +[build-dependencies] +gio = "0.15.10" diff --git a/applets/cosmic-app-list/build.rs b/applets/cosmic-app-list/build.rs new file mode 100644 index 00000000..7c42fb5e --- /dev/null +++ b/applets/cosmic-app-list/build.rs @@ -0,0 +1,7 @@ +fn main() { + gio::compile_resources( + "data/resources", + "data/resources/resources.gresource.xml", + "compiled.gresource", + ); +} diff --git a/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop b/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop new file mode 100644 index 00000000..369c6c69 --- /dev/null +++ b/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Cosmic Dock App List +Comment=Write a GTK + Rust application +Type=Application +Exec=cosmic-app-list +Terminal=false +Categories=GNOME;GTK; +Keywords=Gnome;GTK; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=com.system76.CosmicAppList.svg +StartupNotify=true +NoDisplay=true diff --git a/applets/cosmic-app-list/data/com.system76.CosmicAppList.gschema.xml b/applets/cosmic-app-list/data/com.system76.CosmicAppList.gschema.xml new file mode 100644 index 00000000..848380ea --- /dev/null +++ b/applets/cosmic-app-list/data/com.system76.CosmicAppList.gschema.xml @@ -0,0 +1,20 @@ + + + + + 600 + Default window width + Default window width + + + 400 + Default window height + Default window height + + + false + Default window maximized behaviour + + + + diff --git a/applets/cosmic-app-list/data/com.system76.CosmicAppList.metainfo.xml b/applets/cosmic-app-list/data/com.system76.CosmicAppList.metainfo.xml new file mode 100644 index 00000000..bdd8a5cb --- /dev/null +++ b/applets/cosmic-app-list/data/com.system76.CosmicAppList.metainfo.xml @@ -0,0 +1,35 @@ + + + + "com.system76.CosmicAppList" + CC0 + MPL + Cosmic Dock App List + Write a GTK + Rust application + + A boilerplate template for GTK + Rust. It uses Meson as a build system and has flatpak support by default. + + + + https://gitlab.gnome.org/bilelmoussaoui/cosmic-app-list/raw/master/data/resources/screenshots/screenshot1.png + Main window + + + https://gitlab.gnome.org/bilelmoussaoui/cosmic-app-list + https://gitlab.gnome.org/bilelmoussaoui/cosmic-app-list/issues + + + + + + + ModernToolkit + HiDpiIcon + + Ashley Wulber + ashley@system76.com + "com.System76.CosmicAppList".desktop + diff --git a/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList-symbolic.svg b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList-symbolic.svg new file mode 100644 index 00000000..fc4d934e --- /dev/null +++ b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList-symbolic.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.Devel.svg b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.Devel.svg new file mode 100644 index 00000000..92533ae1 --- /dev/null +++ b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.Devel.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.svg b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.svg new file mode 100644 index 00000000..c2bd5b1b --- /dev/null +++ b/applets/cosmic-app-list/data/icons/com.system76.CosmicAppList.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/cosmic-app-list/data/resources/resources.gresource.xml b/applets/cosmic-app-list/data/resources/resources.gresource.xml new file mode 100644 index 00000000..cf813c6c --- /dev/null +++ b/applets/cosmic-app-list/data/resources/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/applets/cosmic-app-list/i18n.toml b/applets/cosmic-app-list/i18n.toml new file mode 100644 index 00000000..05c50ba2 --- /dev/null +++ b/applets/cosmic-app-list/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" \ No newline at end of file diff --git a/applets/cosmic-app-list/i18n/en/cosmic_app_list.ftl b/applets/cosmic-app-list/i18n/en/cosmic_app_list.ftl new file mode 100644 index 00000000..2d5ea1e4 --- /dev/null +++ b/applets/cosmic-app-list/i18n/en/cosmic_app_list.ftl @@ -0,0 +1 @@ +cosmic-app-list = Cosmic Dock App List diff --git a/applets/cosmic-app-list/src/apps_container/imp.rs b/applets/cosmic-app-list/src/apps_container/imp.rs new file mode 100644 index 00000000..58093e15 --- /dev/null +++ b/applets/cosmic-app-list/src/apps_container/imp.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0-only +use gtk4::glib; +use gtk4::subclass::prelude::*; +use once_cell::sync::OnceCell; + +use crate::dock_list::DockList; + +#[derive(Default)] +pub struct AppsContainer { + pub saved_list: OnceCell, + pub active_list: OnceCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for AppsContainer { + // `NAME` needs to match `class` attribute of template + const NAME: &'static str = "AppsContainer"; + type Type = super::AppsContainer; + type ParentType = gtk4::Box; +} + +impl ObjectImpl for AppsContainer {} + +impl WidgetImpl for AppsContainer {} + +impl BoxImpl for AppsContainer {} diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs new file mode 100644 index 00000000..621de069 --- /dev/null +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -0,0 +1,102 @@ +use std::env; + +// SPDX-License-Identifier: MPL-2.0-only +use crate::dock_list::DockList; +use crate::dock_list::DockListType; +use crate::utils::Event; +use cascade::cascade; +use cosmic_panel_config::config::Anchor; +use cosmic_panel_config::config::CosmicPanelConfig; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use gtk4::Orientation; +use gtk4::{gio, glib}; +use tokio::sync::mpsc::Sender; + +mod imp; + +glib::wrapper! { + pub struct AppsContainer(ObjectSubclass) + @extends gtk4::Widget, gtk4::Box, + @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +impl AppsContainer { + pub fn new(tx: Sender) -> Self { + let self_: Self = glib::Object::new(&[]).expect("Failed to create AppsContainer"); + let imp = imp::AppsContainer::from_instance(&self_); + + cascade! { + &self_; + ..set_orientation(Orientation::Horizontal); + ..add_css_class("transparent"); + // ..add_css_class("dock_container"); + }; + + let config = CosmicPanelConfig::load_from_env().unwrap_or_default(); + + let saved_app_list_view = DockList::new(DockListType::Saved, tx.clone(), config.clone()); + self_.append(&saved_app_list_view); + + // let separator_container = cascade! { + // gtk4::Box::new(Orientation::Vertical, 0); + // ..set_margin_top(8); + // ..set_margin_bottom(8); + // ..set_vexpand(true); + // }; + // self_.append(&separator_container); + // let separator = cascade! { + // Separator::new(Orientation::Vertical); + // ..set_margin_start(8); + // ..set_margin_end(8); + // ..set_vexpand(true); + // ..add_css_class("dock_separator"); + // }; + // separator_container.append(&separator); + let active_app_list_view = DockList::new(DockListType::Active, tx, config.clone()); + self_.append(&active_app_list_view); + // self_.connect_orientation_notify(glib::clone!(@weak separator => move |c| { + // dbg!(c.orientation()); + // separator.set_orientation(match c.orientation() { + // Orientation::Horizontal => Orientation::Vertical, + // _ => Orientation::Horizontal, + // }); + // })); + + imp.saved_list.set(saved_app_list_view).unwrap(); + imp.active_list.set(active_app_list_view).unwrap(); + // Setup + self_.setup_callbacks(); + self_.set_position(config.anchor); + + + Self::setup_callbacks(&self_); + + self_ + } + + pub fn model(&self, type_: DockListType) -> &gio::ListStore { + // Get state + let imp = imp::AppsContainer::from_instance(self); + match type_ { + DockListType::Active => imp.active_list.get().unwrap().model(), + DockListType::Saved => imp.saved_list.get().unwrap().model(), + } + } + + pub fn set_position(&self, position: Anchor) { + self.set_orientation(position.into()); + let imp = imp::AppsContainer::from_instance(self); + imp.saved_list.get().unwrap().set_position(position); + imp.active_list.get().unwrap().set_position(position); + } + + fn setup_callbacks(&self) { + // Get state + let imp = imp::AppsContainer::from_instance(self); + let drop_controller = imp.saved_list.get().unwrap().drop_controller(); + + // hack to prevent hiding window when dnd from other apps + drop_controller.connect_enter(move |_self, _x, _y| gtk4::gdk::DragAction::COPY); + } +} diff --git a/applets/cosmic-app-list/src/apps_window/imp.rs b/applets/cosmic-app-list/src/apps_window/imp.rs new file mode 100644 index 00000000..5aed55f1 --- /dev/null +++ b/applets/cosmic-app-list/src/apps_window/imp.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::apps_container::AppsContainer; +use gtk4::{glib, subclass::prelude::*}; +use once_cell::sync::OnceCell; +// Object holding the state +#[derive(Default)] + +pub struct CosmicAppListWindow { + pub(super) inner: OnceCell, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for CosmicAppListWindow { + // `NAME` needs to match `class` attribute of template + const NAME: &'static str = "CosmicAppListWindow"; + type Type = super::CosmicAppListWindow; + type ParentType = gtk4::ApplicationWindow; +} + +// Trait shared by all GObjects +impl ObjectImpl for CosmicAppListWindow {} + +// Trait shared by all widgets +impl WidgetImpl for CosmicAppListWindow {} + +// Trait shared by all windows +impl WindowImpl for CosmicAppListWindow {} + +// Trait shared by all application +impl ApplicationWindowImpl for CosmicAppListWindow {} diff --git a/applets/cosmic-app-list/src/apps_window/mod.rs b/applets/cosmic-app-list/src/apps_window/mod.rs new file mode 100644 index 00000000..852c97ed --- /dev/null +++ b/applets/cosmic-app-list/src/apps_window/mod.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::{apps_container::AppsContainer, fl, Event}; +use cascade::cascade; +use gtk4::{ + gio, + glib::{self, Object}, + prelude::*, + subclass::prelude::*, +}; +use tokio::sync::mpsc; + +mod imp; + +glib::wrapper! { + pub struct CosmicAppListWindow(ObjectSubclass) + @extends gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget, + @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, + gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; +} + +impl CosmicAppListWindow { + pub fn new(app: >k4::Application, tx: mpsc::Sender) -> Self { + let self_: Self = Object::new(&[("application", app)]) + .expect("Failed to create `CosmicAppListWindow`."); + let imp = imp::CosmicAppListWindow::from_instance(&self_); + + cascade! { + &self_; + ..set_width_request(1); + ..set_height_request(1); + ..set_decorated(false); + ..set_resizable(false); + ..set_title(Some(&fl!("cosmic-app-list"))); + ..add_css_class("transparent"); + }; + let app_list = AppsContainer::new(tx); + self_.set_child(Some(&app_list)); + imp.inner.set(app_list).unwrap(); + + self_.setup_shortcuts(); + + self_ + } + + fn setup_shortcuts(&self) { + let window = self.clone().upcast::(); + let action_quit = gio::SimpleAction::new("quit", None); + action_quit.connect_activate(glib::clone!(@weak window => move |_, _| { + window.close(); + if let Some(a) = window.application() { a.quit() } + std::process::exit(0); + })); + self.add_action(&action_quit); + } +} diff --git a/applets/cosmic-app-list/src/dock_item/imp.rs b/applets/cosmic-app-list/src/dock_item/imp.rs new file mode 100644 index 00000000..cc8a7991 --- /dev/null +++ b/applets/cosmic-app-list/src/dock_item/imp.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use glib::subclass::Signal; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use tokio::sync::mpsc::Sender; + +use crate::dock_popover::DockPopover; +use crate::utils::Event; + +#[derive(Debug, Default)] +pub struct DockItem { + pub image: Rc>>, + pub dots: Rc>, + pub item_box: Rc>, + pub popover: Rc>, + pub popover_menu: Rc>>, + pub tx: OnceCell>, + pub icon_size: Rc>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DockItem { + const NAME: &'static str = "DockItem"; + type Type = super::DockItem; + type ParentType = gtk4::Button; +} + +impl ObjectImpl for DockItem { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder( + // Signal name + "popover-closed", + // Types of the values which will be sent to the signal handler + &[], + // Type of the value the signal handler sends back + <()>::static_type().into(), + ) + .build()] + }); + SIGNALS.as_ref() + } +} + +impl WidgetImpl for DockItem {} + +impl ButtonImpl for DockItem {} diff --git a/applets/cosmic-app-list/src/dock_item/mod.rs b/applets/cosmic-app-list/src/dock_item/mod.rs new file mode 100644 index 00000000..768ec365 --- /dev/null +++ b/applets/cosmic-app-list/src/dock_item/mod.rs @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::dock_object::DockObject; +use crate::dock_popover::DockPopover; +use crate::utils::BoxedWindowList; +use crate::utils::Event; +use cascade::cascade; +use cosmic_panel_config::config::Anchor; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use gtk4::Box; +use gtk4::Image; +use gtk4::Orientation; +use gtk4::Popover; +use gtk4::{Align, PositionType}; +use tokio::sync::mpsc::Sender; + +mod imp; + +glib::wrapper! { + pub struct DockItem(ObjectSubclass) + @extends gtk4::Button, gtk4::Widget, + @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget; +} + +impl DockItem { + pub fn new(tx: Sender, icon_size: u32) -> Self { + let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem"); + + let item_box = Box::new(Orientation::Vertical, 0); + item_box.add_css_class("transparent"); + cascade! { + &self_; + ..set_child(Some(&item_box)); + ..add_css_class("dock_item"); + }; + + let image = cascade! { + Image::new(); + ..set_hexpand(true); + ..set_halign(Align::Center); + ..set_pixel_size(icon_size.try_into().unwrap()); + ..add_css_class("dock"); + }; + let dots = cascade! { + Box::new(Orientation::Horizontal, 4); + ..set_hexpand(true); + ..set_halign(Align::Center); + ..set_valign(Align::Center); + ..add_css_class("transparent"); + }; + // TODO dots inverse color of parent with gsk blend modes? + item_box.append(&image); + item_box.append(&dots); + let popover = cascade! { + Popover::new(); + ..set_autohide(true); + ..add_css_class("dock"); + ..set_has_arrow(false); + }; + item_box.append(&popover); + let self_clone = self_.clone(); + popover.connect_closed(move |_| { + let _ = self_clone.emit_by_name::<()>("popover-closed", &[]); + }); + + let popover_menu = cascade! { + DockPopover::new(tx.clone()); + ..add_css_class("popover_menu"); + }; + popover.set_child(Some(&popover_menu)); + popover_menu.connect_local( + "menu-hide", + false, + glib::clone!(@weak popover, @weak popover_menu => @default-return None, move |_| { + popover.popdown(); + popover_menu.reset_menu(); + None + }), + ); + + let imp = imp::DockItem::from_instance(&self_); + imp.icon_size.set(icon_size); + imp.image.replace(Some(image)); + imp.dots.replace(dots); + imp.item_box.replace(item_box); + imp.popover.replace(popover); + imp.popover_menu.replace(Some(popover_menu)); + imp.tx.set(tx).unwrap(); + self_ + } + + // refactor to emit event for removing the item? + pub fn set_dock_object(&self, dock_object: &DockObject) { + let imp = imp::DockItem::from_instance(self); + let image = cascade! { + dock_object.get_image(); + ..set_hexpand(true); + ..set_halign(Align::Center); + ..set_pixel_size(imp.icon_size.get().try_into().unwrap()); + ..set_tooltip_text(dock_object.get_name().as_deref()); + }; + let old_image = imp.image.replace(None); + if let Some(old_image) = old_image { + imp.item_box.borrow().remove(&old_image); + imp.item_box.borrow().prepend(&image); + imp.image.replace(Some(image)); + } + let active = dock_object.property::("active"); + let dots = imp.dots.borrow(); + while let Some(c) = dots.first_child() { + dots.remove(&c); + } + for _ in active.0 { + dots.append(&cascade! { + Box::new(Orientation::Horizontal, 0); + ..set_halign(Align::Center); + ..set_valign(Align::Center); + ..add_css_class("dock_dots"); + }); + } + + let popover = dock_object.property::("popover"); + // dbg!(popover); + // dbg!(dock_object); + if popover { + self.add_popover(dock_object); + } else { + self.clear_popover(); + } + } + + pub fn set_position(&self, position: Anchor) { + let imp = imp::DockItem::from_instance(self); + let item_box = imp.item_box.borrow(); + let dots = imp.dots.borrow(); + if let Some(image) = imp.image.borrow().as_ref() { + match position { + Anchor::Left => { + item_box.set_orientation(Orientation::Horizontal); + dots.set_orientation(Orientation::Vertical); + dots.set_margin_bottom(4); + dots.set_margin_top(4); + item_box.reorder_child_after(&image.clone(), Some(&dots.clone())); + } + Anchor::Right => { + item_box.set_orientation(Orientation::Horizontal); + dots.set_orientation(Orientation::Vertical); + dots.set_margin_bottom(4); + dots.set_margin_top(4); + item_box.reorder_child_after(&dots.clone(), Some(&image.clone())); + } + Anchor::Top => { + item_box.set_orientation(Orientation::Vertical); + dots.set_orientation(Orientation::Horizontal); + dots.set_margin_start(4); + dots.set_margin_end(4); + item_box.reorder_child_after(&image.clone(), Some(&dots.clone())); + } + Anchor::Bottom => { + item_box.set_orientation(Orientation::Vertical); + dots.set_orientation(Orientation::Horizontal); + dots.set_margin_start(4); + dots.set_margin_end(4); + item_box.reorder_child_after(&dots.clone(), Some(&image.clone())); + } + }; + } + let popover = imp.popover.borrow(); + popover.set_position(match position { + Anchor::Left => PositionType::Right, + Anchor::Right => PositionType::Left, + Anchor::Top => PositionType::Bottom, + Anchor::Bottom => PositionType::Top, + }); + + } + + pub fn add_popover(&self, obj: &DockObject) { + let imp = imp::DockItem::from_instance(self); + let popover = imp.popover.borrow(); + if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { + popover_menu.set_dock_object(obj, true); + popover.popup(); + } + } + + pub fn clear_popover(&self) { + let imp = imp::DockItem::from_instance(self); + let popover = imp.popover.borrow(); + if let Some(popover_menu) = imp.popover_menu.borrow().as_ref() { + popover.popdown(); + popover_menu.reset_menu(); + } + } +} diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs new file mode 100644 index 00000000..6355c4bf --- /dev/null +++ b/applets/cosmic-app-list/src/dock_list/imp.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use cosmic_panel_config::config::{Anchor, CosmicPanelConfig}; +use glib::SignalHandlerId; +use gtk4::subclass::prelude::*; +use gtk4::{gio, glib}; +use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; +use once_cell::sync::OnceCell; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use tokio::sync::mpsc; + +use crate::utils::Event; + +#[derive(Debug, Default)] +pub struct DockList { + pub list_view: OnceCell, + pub type_: OnceCell, + pub model: OnceCell, + pub click_controller: OnceCell, + pub drop_controller: OnceCell, + pub drag_source: OnceCell, + pub drag_end_signal: Rc>>, + pub drag_cancel_signal: Rc>>, + pub popover_menu_index: Rc>>, + pub position: Rc>, + pub tx: OnceCell>, + pub config: OnceCell +} + +#[glib::object_subclass] +impl ObjectSubclass for DockList { + const NAME: &'static str = "DockList"; + type Type = super::DockList; + type ParentType = Box; +} + +impl ObjectImpl for DockList {} + +impl WidgetImpl for DockList {} + +impl BoxImpl for DockList {} diff --git a/applets/cosmic-app-list/src/dock_list/mod.rs b/applets/cosmic-app-list/src/dock_list/mod.rs new file mode 100644 index 00000000..a6d86a52 --- /dev/null +++ b/applets/cosmic-app-list/src/dock_list/mod.rs @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::dock_item::DockItem; +use crate::dock_object::DockObject; +use crate::utils::data_path; +use crate::utils::{BoxedWindowList, Event, Item}; +use cascade::cascade; +use cosmic_panel_config::config::{Anchor, CosmicPanelConfig}; +use gio::DesktopAppInfo; +use gio::Icon; +use glib::Object; +use glib::Type; +use gtk4::gdk; +use gtk4::gdk::ContentProvider; +use gtk4::gdk::Display; +use gtk4::gdk::ModifierType; +use gtk4::glib; +use gtk4::prelude::ListModelExt; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use gtk4::DropTarget; +use gtk4::IconTheme; +use gtk4::ListView; +use gtk4::Orientation; +use gtk4::SignalListItemFactory; +use gtk4::{DragSource, GestureClick}; +use std::fs::File; +use std::path::Path; +use tokio::sync::mpsc::Sender; + +mod imp; + +glib::wrapper! { + pub struct DockList(ObjectSubclass) + @extends gtk4::Widget, gtk4::Box, + @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum DockListType { + Saved, + Active, +} + +impl Default for DockListType { + fn default() -> Self { + DockListType::Active + } +} + +impl DockList { + pub fn new(type_: DockListType, tx: Sender, config: CosmicPanelConfig) -> Self { + let self_: DockList = glib::Object::new(&[]).expect("Failed to create DockList"); + let imp = imp::DockList::from_instance(&self_); + imp.type_.set(type_).unwrap(); + imp.tx.set(tx).unwrap(); + imp.config.set(config).unwrap(); + self_.layout(); + //dnd behavior is different for each type, as well as the data in the model + self_.setup_model(); + self_.setup_click_controller(); + self_.setup_drag(); + self_.setup_drop_target(); + self_.setup_factory(); + self_ + } + + pub fn model(&self) -> &gio::ListStore { + // Get state + let imp = imp::DockList::from_instance(self); + imp.model.get().expect("Could not get model") + } + + pub fn drop_controller(&self) -> &DropTarget { + // Get state + let imp = imp::DockList::from_instance(self); + imp.drop_controller.get().expect("Could not get model") + } + + pub fn popover_index(&self) -> Option { + // Get state + let imp = imp::DockList::from_instance(self); + imp.popover_menu_index.get() + } + + fn restore_data(&self) { + if let Ok(file) = File::open(data_path()) { + if let Ok(data) = serde_json::from_reader::<_, Vec>(file) { + // dbg!(&data); + let dock_objects: Vec = data + .into_iter() + .filter_map(|d| { + DockObject::from_app_info_path(&d) + .map(|dockobject| dockobject.upcast::()) + }) + .collect(); + // dbg!(&dock_objects); + + let model = self.model(); + model.splice(model.n_items(), 0, &dock_objects); + } + } else { + eprintln!("Error loading saved apps!"); + let model = &self.model(); + xdg::BaseDirectories::new() + .expect("could not access XDG Base directory") + .get_data_dirs() + .iter_mut() + .for_each(|xdg_data_path| { + let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; + xdg_data_path.push("applications"); + // dbg!(&xdg_data_path); + if let Ok(dir_iter) = std::fs::read_dir(xdg_data_path) { + dir_iter.for_each(|dir_entry| { + if let Ok(dir_entry) = dir_entry { + if let Some(path) = dir_entry.path().file_name() { + if let Some(path) = path.to_str() { + if let Some(app_info) = gio::DesktopAppInfo::new(path) { + if app_info.should_show() + && defaults.contains(&app_info.name().as_str()) + { + model.append(&DockObject::new(app_info)); + } else { + // println!("Ignoring {}", path); + } + } else { + // println!("error loading {}", path); + } + } + } + } + }) + } + }); + } + } + + fn store_data(model: &gio::ListStore) { + // Store todo data in vector + let mut backup_data = Vec::new(); + let mut i = 0; + while let Some(item) = model.item(i) { + // Get `AppGroup` from `glib::Object` + let dock_object = item + .downcast_ref::() + .expect("The object needs to be of type `AppGroupData`."); + // Add todo data to vector and increase position + if let Some(app_info) = dock_object.property::>("appinfo") { + if let Some(f) = app_info.filename() { + backup_data.push(f); + } + } + i += 1; + } + // dbg!(&backup_data); + // Save state in file + let file = File::create(data_path()).expect("Could not create json file."); + serde_json::to_writer_pretty(file, &backup_data) + .expect("Could not write data to json file"); + // TODO save plugins here for now examples are hardcoded and don't need to be saved + } + + fn layout(&self) { + let imp = imp::DockList::from_instance(self); + let list_view = cascade! { + ListView::default(); + ..set_orientation(Orientation::Horizontal); + ..add_css_class("transparent"); + }; + if imp.type_.get().unwrap() == &DockListType::Saved { + list_view.set_width_request(64); + } + self.append(&list_view); + imp.list_view.set(list_view).unwrap(); + } + + pub fn set_position(&self, position: Anchor) { + let imp = imp::DockList::from_instance(self); + let model = imp.model.get().unwrap(); + imp.position.replace(position); + model.items_changed(0, model.n_items(), model.n_items()); + + let imp = imp::DockList::from_instance(self); + imp.list_view + .get() + .unwrap() + .set_orientation(position.into()); + } + + fn setup_model(&self) { + let imp = imp::DockList::from_instance(self); + let model = gio::ListStore::new(DockObject::static_type()); + + let selection_model = gtk4::NoSelection::new(Some(&model)); + + // Wrap model with selection and pass it to the list view + let list_view = imp.list_view.get().unwrap(); + list_view.set_model(Some(&selection_model)); + imp.model.set(model).expect("Could not set model"); + + if imp.type_.get().unwrap() == &DockListType::Saved { + let model = self.model(); + self.restore_data(); + model.connect_items_changed(|model, _, _removed, _added| { + Self::store_data(model); + }); + } + } + + fn setup_click_controller(&self) { + let imp = imp::DockList::from_instance(self); + let controller = GestureClick::builder() + .button(0) + .propagation_limit(gtk4::PropagationLimit::None) + .propagation_phase(gtk4::PropagationPhase::Capture) + .build(); + self.add_controller(&controller); + + let model = self.model(); + let list_view = &imp.list_view.get().unwrap(); + let popover_menu_index = &imp.popover_menu_index; + let tx = imp.tx.get().unwrap().clone(); + controller.connect_released(glib::clone!(@weak model, @weak list_view, @weak popover_menu_index => move |self_, _, x, y| { + let max_x = list_view.allocated_width(); + let max_y = list_view.allocated_height(); + let (indexing_dim, indexing_length, other_dim, other_length) = match list_view.orientation() { + Orientation::Horizontal => (x, max_x, y, max_y), + Orientation::Vertical => (y, max_y, x, max_x), + _ => return, + }; + // dbg!(max_y); + // dbg!(y); + let n_buckets = model.n_items(); + let index = (indexing_dim * n_buckets as f64 / (indexing_length as f64 + 0.1)) as u32; + // dbg!(self_.current_button()); + // dbg!(self_.last_event(self_.current_sequence().as_ref())); + let click_modifier = self_.last_event(self_.current_sequence().as_ref()).map(|event| event.modifier_state()); + // dbg!(click_modifier); + // Launch the application when an item of the list is activated + + let tx = tx.clone(); + let focus_window = move |first_focused_item: &Item| { + let entity = first_focused_item.entity; + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.clone().send(Event::Activate(entity)).await; + }); + }; + let old_index = popover_menu_index.get(); + if let Some(old_index) = old_index { + if let Some(old_item) = model.item(old_index) { + if let Ok(old_dock_object) = old_item.downcast::() { + old_dock_object.set_popover(false); + popover_menu_index.replace(None); + model.items_changed(old_index, 0, 0); + //TODO signal dock to check if it should hide + } + } + return; + } + if other_dim > f64::from(other_length) || y < 0.0 || indexing_dim > f64::from(indexing_length) || indexing_dim < 0.0 { + // println!("out of bounds click..."); + return; + } + + if let Some(item) = model.item(index) { + if let Ok(dock_object) = item.downcast::() { + let active = dock_object.property::("active"); + let app_info = dock_object.property::>("appinfo"); + match (self_.current_button(), click_modifier, active.0.get(0), app_info) { + (click, Some(click_modifier), Some(first_focused_item), _) if click == 1 && !click_modifier.contains(ModifierType::CONTROL_MASK) => focus_window(first_focused_item), + (click, None, Some(first_focused_item), _) if click == 1 => focus_window(first_focused_item), + (click, _, _, Some(app_info)) | (click, _, None, Some(app_info)) if click != 3 => { + let context = gdk::Display::default().unwrap().app_launch_context(); + if let Err(err) = app_info.launch(&[], Some(&context)) { + dbg!(err); + } + + } + (click, _, _, _) if click == 3 => { + // println!("handling right click"); + if let Some(old_index) = popover_menu_index.get() { + if let Some(item) = model.item(old_index) { + if let Ok(dock_object) = item.downcast::() { + dock_object.set_popover(false); + popover_menu_index.replace(Some(index)); + model.items_changed(old_index, 0, 0); + } + } + } + dock_object.set_popover(true); + popover_menu_index.replace(Some(index)); + model.items_changed(index, 0, 0); + } + _ => eprintln!("Failed to process click.") + } + } + } + })); + imp.click_controller.set(controller).unwrap(); + } + + fn setup_drop_target(&self) { + let imp = imp::DockList::from_instance(self); + if imp.type_.get().unwrap() != &DockListType::Saved { + return; + } + + let drop_target_widget = &imp.list_view.get().unwrap(); + let mut drop_actions = gdk::DragAction::COPY; + drop_actions.insert(gdk::DragAction::MOVE); + let drop_format = gdk::ContentFormats::for_type(Type::STRING); + let drop_format = drop_format.union(&gdk::ContentFormats::for_type(Type::U32)); + let drop_controller = DropTarget::builder() + .preload(true) + .actions(drop_actions) + .formats(&drop_format) + .build(); + drop_target_widget.add_controller(&drop_controller); + + let model = self.model(); + let list_view = &imp.list_view.get().unwrap(); + let drag_end = &imp.drag_end_signal; + let drag_source = &imp.drag_source.get().unwrap(); + let tx = imp.tx.get().unwrap().clone(); + drop_controller.connect_drop( + glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_source => @default-return true, move |_self, drop_value, x, y| { + //calculate insertion location + let max_x = list_view.allocated_width(); + let max_y = list_view.allocated_height(); + let n_buckets = model.n_items() * 2; + + let (indexing_dim, indexing_length, _other_dim, _other_length) = match list_view.orientation() { + Orientation::Horizontal => (x, max_x, y, max_y), + Orientation::Vertical => (y, max_y, x, max_x), + _ => (x, max_x, y, max_y), + }; + + let drop_bucket = (indexing_dim * n_buckets as f64 / (indexing_length as f64 + 0.1)) as u32; + let index = if drop_bucket == 0 { + 0 + } else if drop_bucket == n_buckets - 1 { + model.n_items() + } else { + (drop_bucket + 1) / 2 + }; + + if let Ok(Some(path_str)) = drop_value.get::>() { + let desktop_path = &Path::new(&path_str); + if let Some(pathbase) = desktop_path.file_name() { + if let Some(app_info) = gio::DesktopAppInfo::new(&pathbase.to_string_lossy()) { + // remove item if already exists + let mut i: u32 = 0; + let mut index_of_existing_app: Option = None; + while let Some(item) = model.item(i) { + if let Ok(cur_app_info) = item.downcast::() { + if let Some(cur_app_info) = cur_app_info.property::>("appinfo") { + if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { + index_of_existing_app = Some(i); + } + } + } + i += 1; + } + if let Some(index_of_existing_app) = index_of_existing_app { + // remove existing entry + model.remove(index_of_existing_app); + if let Some(old_handle) = drag_end.replace(None) { + glib::signal_handler_disconnect(&drag_source, old_handle); + } + } + model.insert(index, &DockObject::new(app_info)); + } + } + } + else if let Ok(old_index) = drop_value.get::() { + if let Some(item) = model.item(old_index) { + if let Ok(dock_object) = item.downcast::() { + model.remove(old_index); + model.insert(index, &dock_object); + if let Some(old_handle) = drag_end.replace(None) { + glib::signal_handler_disconnect(&drag_source, old_handle); + } + } + } + } + else { + // dbg!("rejecting drop"); + _self.reject(); + } + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.send(Event::RefreshFromCache).await; + }); + true + }), + ); + + imp.drop_controller + .set(drop_controller) + .expect("Could not set dock dnd drop controller"); + } + + fn setup_drag(&self) { + let imp = imp::DockList::from_instance(self); + let type_ = imp.type_.get().unwrap(); + + let actions = match *type_ { + DockListType::Saved => gdk::DragAction::MOVE, + DockListType::Active => gdk::DragAction::COPY, + }; + let drag_source = DragSource::builder() + .name("dock drag source") + .actions(actions) + .build(); + + let model = self.model(); + let list_view = imp.list_view.get().unwrap(); + let drag_end = &imp.drag_end_signal; + let drag_cancel = &imp.drag_cancel_signal; + let type_ = *type_; + let tx = imp.tx.get().unwrap().clone(); + list_view.add_controller(&drag_source); + drag_source.connect_prepare(glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { + let max_x = list_view.allocated_width(); + // dbg!(max_x); + // dbg!(max_y); + let n_buckets = model.n_items(); + + let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + if let Some(item) = model.item(index) { + if type_ == DockListType::Saved { + let tx1 = tx.clone(); + if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( + glib::clone!(@weak model => move |_self, _drag, _delete_data| { + if _delete_data { + model.remove(index); + let tx = tx1.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.send(Event::RefreshFromCache).await; + }); + }; + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + + let tx = tx.clone(); + if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( + glib::clone!(@weak model => @default-return false, move |_self, _drag, cancel_reason| { + if cancel_reason != gdk::DragCancelReason::UserCancelled { + model.remove(index); + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.send(Event::RefreshFromCache).await; + }); + true + } else { + false + } + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + } + if let Ok(dock_object) = item.downcast::() { + if let Some(app_info) = dock_object.property::>("appinfo") { + let icon = app_info + .icon() + .unwrap_or_else(|| Icon::for_string("image-missing").expect("Failed to set default icon")); + + if let Some(default_display) = &Display::default() { + let icon_theme = IconTheme::for_display(default_display); + let paintable_icon = icon_theme.lookup_by_gicon( + &icon, + 64, + 1, + gtk4::TextDirection::None, + gtk4::IconLookupFlags::empty(), + ); + self_.set_icon(Some(&paintable_icon), 32, 32); + } + + // saved app list provides index + return match type_ { + DockListType::Saved => Some(ContentProvider::for_value(&index.to_value())), + DockListType::Active => app_info.filename().map(|file| ContentProvider::for_value(&file.to_string_lossy().to_value())) + } + } + } + } + None + })); + + // TODO investigate why drop does not finish when dropping on some surfaces + // for now this is a fix that will cancel the drop after 100 ms and not completing. + drag_source.connect_drag_begin(|_self, drag| { + drag.connect_drop_performed(|_self| { + glib::timeout_add_local_once( + std::time::Duration::from_millis(100), + glib::clone!(@weak _self => move || { + _self.drop_done(false); + }), + ); + }); + }); + + imp.drag_source + .set(drag_source) + .expect("Could not set saved drag source"); + } + + fn setup_factory(&self) { + let imp = imp::DockList::from_instance(self); + let popover_menu_index = &imp.popover_menu_index; + let factory = SignalListItemFactory::new(); + let model = imp.model.get().expect("Failed to get saved app model."); + let tx = imp.tx.get().unwrap().clone(); + let icon_size = imp.config.get().unwrap().get_applet_icon_size(); + factory.connect_setup( + glib::clone!(@weak popover_menu_index, @weak model => move |_, list_item| { + let dock_item = DockItem::new(tx.clone(), icon_size); + dock_item + .connect_local("popover-closed", false, move |_| { + if let Some(old_index) = popover_menu_index.replace(None) { + if let Some(item) = model.item(old_index) { + if let Ok(dock_object) = item.downcast::() { + dock_object.set_popover(false); + model.items_changed(old_index, 0, 0); + } + } + } + + None + }); + list_item.set_child(Some(&dock_item)); + }), + ); + factory.connect_bind( + glib::clone!(@weak imp.position as position => move |_, list_item| { + let dock_object = list_item + .item() + .expect("The item has to exist.") + .downcast::() + .expect("The item has to be a `DockObject`"); + let dock_item = list_item + .child() + .expect("The list item child needs to exist.") + .downcast::() + .expect("The list item type needs to be `DockItem`"); + dock_item.set_dock_object(&dock_object); + dock_item.set_position(position.get()); + }), + ); + // Set the factory of the list view + imp.list_view.get().unwrap().set_factory(Some(&factory)); + } +} diff --git a/applets/cosmic-app-list/src/dock_object/imp.rs b/applets/cosmic-app-list/src/dock_object/imp.rs new file mode 100644 index 00000000..7c13076c --- /dev/null +++ b/applets/cosmic-app-list/src/dock_object/imp.rs @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::cell::Cell; +use std::cell::RefCell; + +use crate::utils::BoxedWindowList; +use gio::DesktopAppInfo; +use glib::{ParamFlags, ParamSpec, Value}; +use gtk4::gdk::glib::ParamSpecBoolean; +use gtk4::gdk::glib::ParamSpecBoxed; +use gtk4::gdk::glib::ParamSpecObject; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use once_cell::sync::Lazy; + +// Object holding the state +#[derive(Default)] +pub struct DockObject { + pub(super) appinfo: RefCell>, + pub(super) active: RefCell, + pub(super) saved: Cell, + pub(super) popover: Cell, +} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for DockObject { + const NAME: &'static str = "DockObject"; + type Type = super::DockObject; + type ParentType = glib::Object; +} + +// Trait shared by all GObjects +impl ObjectImpl for DockObject { + fn properties() -> &'static [ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpecObject::new( + // Name + "appinfo", + // Nickname + "appinfo", + // Short description + "app info", + DesktopAppInfo::static_type(), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpecBoxed::new( + // Name + "active", + // Nickname + "active", + // Short description + "active", + BoxedWindowList::static_type(), + // The property can be read and written to + ParamFlags::READWRITE, + ), + ParamSpecBoolean::new( + "saved", + "saved", + "Indicates whether app is saved to the dock", + false, + ParamFlags::READWRITE, + ), + ParamSpecBoolean::new( + "popover", + "popover", + "Indicates whether there is a popover menu displayed for this object", + false, + ParamFlags::READWRITE, + ), + ] + }); + PROPERTIES.as_ref() + } + + fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { + match pspec.name() { + "appinfo" => { + let appinfo = value + .get() + .expect("Value needs to be Option"); + self.appinfo.replace(appinfo); + } + "active" => { + let active = value.get().expect("Value needs to be BoxedWindowList"); + self.active.replace(active); + } + "saved" => { + self.saved + .replace(value.get().expect("Value needs to be a boolean")); + } + "popover" => { + self.popover + .replace(value.get().expect("Value needs to be a boolean")); + } + _ => unimplemented!(), + } + } + + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { + match pspec.name() { + "appinfo" => self.appinfo.borrow().to_value(), + "active" => self.active.borrow().to_value(), + "saved" => self.saved.get().to_value(), + "popover" => self.popover.get().to_value(), + _ => unimplemented!(), + } + } +} diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs new file mode 100644 index 00000000..b7a7d26a --- /dev/null +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::utils::BoxedWindowList; +use gio::{DesktopAppInfo, Icon}; +use gtk4::gdk::glib::Object; +use gtk4::gdk::subclass::prelude::ObjectSubclassExt; +use gtk4::prelude::*; +use gtk4::{glib, Image}; +use std::path::Path; + +mod imp; + +glib::wrapper! { + pub struct DockObject(ObjectSubclass); +} + +impl DockObject { + pub fn new(appinfo: DesktopAppInfo) -> Self { + Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)]) + .expect("Failed to create `DockObject`.") + } + + pub fn from_app_info_path(path: &str) -> Option { + if let Some(path) = Path::new(path).file_name() { + if let Some(path) = path.to_str() { + if let Some(appinfo) = gio::DesktopAppInfo::new(path) { + if appinfo.should_show() { + return Some( + Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)]) + .expect("Failed to create `DockObject`."), + ); + } + } + } + } + None + } + + pub fn get_path(&self) -> Option { + let imp = imp::DockObject::from_instance(self); + if let Some(app_info) = imp.appinfo.borrow().as_ref() { + app_info + .filename() + .map(|name| name.to_string_lossy().into()) + } else { + None + } + } + + pub fn get_name(&self) -> Option { + let imp = imp::DockObject::from_instance(self); + imp.appinfo.borrow().as_ref().map(|app_info| app_info.name().to_string()) + } + + pub fn get_image(&self) -> gtk4::Image { + let imp = imp::DockObject::from_instance(self); + if let Some(app_info) = imp.appinfo.borrow().as_ref() { + let image = Image::new(); + let icon = app_info + .icon() + .unwrap_or_else(|| Icon::for_string("image-missing").expect("Failed to set default icon")); + image.set_from_gicon(&icon); + image.set_tooltip_text(None); + image + } else { + eprintln!("failed to load image"); + let image = Image::new(); + image.set_tooltip_text(None); + image + } + } + + pub fn set_saved(&self, is_saved: bool) { + let imp = imp::DockObject::from_instance(self); + imp.saved.replace(is_saved); + } + + pub fn from_search_results(results: BoxedWindowList) -> Self { + let appinfo = if let Some(first) = results.0.get(0) { + xdg::BaseDirectories::new() + .expect("could not access XDG Base directory") + .get_data_dirs() + .iter_mut() + .filter_map(|xdg_data_path| { + xdg_data_path.push("applications"); + std::fs::read_dir(xdg_data_path).ok() + }) + .flatten() + .filter_map(|dir_entry| { + if let Ok(dir_entry) = dir_entry { + if let Some(path) = dir_entry.path().file_name() { + if let Some(path) = path.to_str() { + if let Some(app_info) = gio::DesktopAppInfo::new(path) { + if app_info.should_show() + && first.description.as_str() == app_info.name().as_str() + { + return Some(app_info); + } + } + } + } + } + None + }) + .next() + } else { + None + }; + // dbg!(&appinfo); + Object::new(&[("appinfo", &appinfo), ("active", &results)]) + .expect("Failed to create `DockObject`.") + } + + pub fn set_popover(&self, b: bool) { + let imp = imp::DockObject::from_instance(self); + imp.popover.replace(b); + } +} + +#[derive(Clone, Debug, Default, glib::Boxed)] +#[boxed_type(name = "BoxedDockObject")] +pub struct BoxedDockObject(pub Option); diff --git a/applets/cosmic-app-list/src/dock_popover/imp.rs b/applets/cosmic-app-list/src/dock_popover/imp.rs new file mode 100644 index 00000000..08ee90f3 --- /dev/null +++ b/applets/cosmic-app-list/src/dock_popover/imp.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::cell::RefCell; +use std::rc::Rc; + +use glib::subclass::Signal; +use gtk4::glib; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use gtk4::{Box, Button, ListBox, Revealer}; +use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; +use tokio::sync::mpsc::Sender; + +use crate::dock_object::DockObject; +use crate::utils::Event; + +#[derive(Debug, Default)] +pub struct DockPopover { + pub menu_handle: Rc>, + pub all_windows_item_revealer: Rc>, + pub all_windows_item_header: Rc>, + pub window_list: Rc>, + pub launch_new_item: Rc>, + pub favorite_item: Rc>, + pub quit_all_item: Rc>, + //TODO figure out how to use lifetimes with glib::wrapper! macro + pub dock_object: Rc>>, + pub tx: OnceCell>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DockPopover { + const NAME: &'static str = "DockPopover"; + type Type = super::DockPopover; + type ParentType = Box; +} + +impl ObjectImpl for DockPopover { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder( + // Signal name + "menu-hide", + // Types of the values which will be sent to the signal handler + &[], + // Type of the value the signal handler sends back + <()>::static_type().into(), + ) + .build()] + }); + SIGNALS.as_ref() + } +} + +impl WidgetImpl for DockPopover {} + +impl BoxImpl for DockPopover {} diff --git a/applets/cosmic-app-list/src/dock_popover/mod.rs b/applets/cosmic-app-list/src/dock_popover/mod.rs new file mode 100644 index 00000000..a10d51fa --- /dev/null +++ b/applets/cosmic-app-list/src/dock_popover/mod.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use cascade::cascade; +use gio::DesktopAppInfo; +use gtk4::gdk::pango::EllipsizeMode; +use gtk4::subclass::prelude::*; +use gtk4::{gdk, gio, glib}; +use gtk4::{prelude::*, Label}; +use gtk4::{Box, Button, Image, ListBox, Orientation}; +use tokio::sync::mpsc::Sender; + +use crate::dock_object::DockObject; +use crate::utils::BoxedWindowList; +use crate::utils::Event; + +mod imp; + +glib::wrapper! { + pub struct DockPopover(ObjectSubclass) + @extends gtk4::Widget, gtk4::Box, + @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +impl DockPopover { + pub fn new(tx: Sender) -> Self { + let self_: DockPopover = glib::Object::new(&[]).expect("Failed to create DockList"); + let imp = imp::DockPopover::from_instance(&self_); + imp.tx.set(tx).unwrap(); + self_.layout(); + //dnd behavior is different for each type, as well as the data in the model + self_ + } + + pub fn set_dock_object(&self, dock_object: &DockObject, update_layout: bool) { + let imp = imp::DockPopover::from_instance(self); + imp.dock_object.replace(Some(dock_object.clone())); + if update_layout { + self.update_layout(); + } + } + + pub fn update_layout(&self) { + self.reset_menu(); + cascade! { + &self; + ..set_orientation(Orientation::Vertical); + ..set_hexpand(true); + }; + + // build menu + let imp = imp::DockPopover::from_instance(self); + let dock_object = imp.dock_object.borrow(); + let menu_handle = imp.menu_handle.borrow(); + if let Some(dock_object) = dock_object.as_ref() { + let all_windows_item_container = cascade! { + Box::new(Orientation::Vertical, 4); + }; + menu_handle.append(&all_windows_item_container); + let window_list = dock_object.property::("active"); + if window_list.0.is_empty() { + all_windows_item_container.hide(); + } else { + let window_listbox = cascade! { + ListBox::new(); + ..set_activate_on_single_click(true); + ..add_css_class("popover_menu"); + }; + all_windows_item_container.append(&window_listbox); + for w in window_list.0 { + let window_box = cascade! { + Box::new(Orientation::Vertical, 4); + ..add_css_class("dock_item"); + }; + window_listbox.append(&window_box); + + let window_title = cascade! { + Label::new(Some(w.name.as_str())); + ..set_margin_start(4); + ..set_margin_end(4); + ..set_margin_top(4); + ..set_margin_bottom(4); + ..set_wrap(true); + ..set_max_width_chars(20); + ..set_ellipsize(EllipsizeMode::End); + ..add_css_class("title-4"); + ..add_css_class("dock_popover_title"); + }; + + let window_image = cascade! { + //TODO fill with image of window + Image::from_pixbuf(None); + }; + window_box.append(&window_image); + window_box.append(&window_title); + } + // imp.all_windows_item_revealer.replace(window_list_revealer); + imp.window_list.replace(window_listbox); + } + + let launch_item_container = cascade! { + Box::new(Orientation::Vertical, 4); + ..set_hexpand(true); + ..add_css_class("popover_menu"); + }; + menu_handle.append(&launch_item_container); + + let launch_new_item = cascade! { + Button::with_label("New Window"); + ..add_css_class("popover_menu"); + }; + launch_item_container.append(&launch_new_item); + imp.launch_new_item.replace(launch_new_item); + + let favorite_item = cascade! { + Button::with_label(if dock_object.property::("saved") {"Remove from Favorites"} else {"Add to Favorites"}); + ..add_css_class("popover_menu"); + }; + menu_handle.append(&favorite_item); + imp.favorite_item.replace(favorite_item); + + let window_list = dock_object.property::("active"); + + if window_list.0.len() > 1 { + let quit_all_item = cascade! { + Button::with_label(format!("Quit {} Windows", window_list.0.len()).as_str()); + ..add_css_class("popover_menu"); + }; + menu_handle.append(&quit_all_item); + imp.quit_all_item.replace(quit_all_item); + } else { + let quit_all_item = cascade! { + Button::with_label("Quit"); + ..add_css_class("popover_menu"); + }; + menu_handle.append(&quit_all_item); + if window_list.0.is_empty() { + quit_all_item.hide(); + } + imp.quit_all_item.replace(quit_all_item); + } + + self.setup_handlers(); + } + } + + fn layout(&self) { + let imp = imp::DockPopover::from_instance(self); + let menu_handle = cascade! { + Box::new(Orientation::Vertical, 4); + ..add_css_class("popover_menu"); + }; + self.append(&menu_handle); + imp.menu_handle.replace(menu_handle); + } + + fn emit_hide(&self) { + self.emit_by_name::<()>("menu-hide", &[]); + } + + pub fn reset_menu(&self) { + // reset menu + let menu_handle = cascade! { + Box::new(Orientation::Vertical, 4); + }; + self.append(&menu_handle); + + let imp = imp::DockPopover::from_instance(self); + let old_menu_handle = imp.menu_handle.replace(menu_handle); + self.remove(&old_menu_handle); + } + + fn setup_handlers(&self) { + let imp = imp::DockPopover::from_instance(self); + let dock_object = imp.dock_object.borrow(); + let launch_new_item = imp.launch_new_item.borrow(); + let favorite_item = imp.favorite_item.borrow(); + let quit_all_item = imp.quit_all_item.borrow(); + let window_listbox = imp.window_list.borrow(); + // let all_windows_header = imp.all_windows_item_header.borrow(); + // let revealer = &imp.all_windows_item_revealer; + + if let Some(dock_object) = dock_object.as_ref() { + // println!("setting up popover menu handlers"); + let self_ = self.clone(); + launch_new_item.connect_clicked(glib::clone!(@weak dock_object, => move |_| { + let app_info = dock_object.property::>("appinfo").expect("Failed to convert value to DesktopAppInfo"); + + let context = gdk::Display::default().unwrap().app_launch_context(); + if let Err(err) = app_info.launch(&[], Some(&context)) { + eprintln!("{}", err); + } + self_.emit_hide(); + })); + + let tx = imp.tx.get().unwrap().clone(); + let self_ = self.clone(); + quit_all_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { + let active = dock_object.property::("active").0; + for w in active { + let entity = w.entity; + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.clone().send(Event::Close(entity)).await; + }); + } + self_.emit_hide(); + })); + + let tx = imp.tx.get().unwrap().clone(); + let self_ = self.clone(); + favorite_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { + let saved = dock_object.property::("saved"); + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + if let Some(name) = dock_object.get_name() { + let _ = tx.clone().send(Event::Favorite((name, !saved))).await; + } + }); + self_.emit_hide(); + })); + + // all_windows_header.connect_clicked( + // glib::clone!(@weak dock_object, @weak revealer => move |self_| { + // // dbg!(dock_object); + // let revealer = revealer.borrow(); + // revealer.set_reveal_child(!revealer.reveals_child()) + // }), + // ); + + let tx = imp.tx.get().unwrap().clone(); + let self_ = self.clone(); + window_listbox.connect_row_activated( + glib::clone!(@weak dock_object => move |_, item| { + let active = dock_object.property::("active").0; + let entity = active[usize::try_from(item.index()).unwrap()].entity; + let tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.send(Event::Activate(entity)).await; + }); + self_.emit_hide(); + }), + ); + } + } +} diff --git a/applets/cosmic-app-list/src/localize.rs b/applets/cosmic-app-list/src/localize.rs new file mode 100644 index 00000000..efb92aa4 --- /dev/null +++ b/applets/cosmic-app-list/src/localize.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + DefaultLocalizer, LanguageLoader, Localizer, +}; +use once_cell::sync::Lazy; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + + loader +}); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs new file mode 100644 index 00000000..3a0798a6 --- /dev/null +++ b/applets/cosmic-app-list/src/main.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use apps_window::CosmicAppListWindow; +use dock_list::DockListType; +use dock_object::DockObject; +use gio::{ApplicationFlags, DesktopAppInfo}; +use gtk4::gdk::Display; +use gtk4::{glib, prelude::*, CssProvider, StyleContext}; +use once_cell::sync::OnceCell; +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tokio::sync::mpsc; +use utils::{block_on, BoxedWindowList, Event, Item, DEST, PATH}; + +mod apps_container; +mod apps_window; +mod dock_item; +mod dock_list; +mod dock_object; +mod dock_popover; +mod localize; +mod utils; + +const ID: &str = "com.system76.CosmicAppList"; +static TX: OnceCell> = OnceCell::new(); + +pub fn localize() { + let localizer = crate::localize::localizer(); + let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); + + if let Err(error) = localizer.select(&requested_languages) { + eprintln!("Error while loading language for App List {}", error); + } +} + +fn load_css() { + let provider = CssProvider::new(); + provider.load_from_data(include_bytes!("style.css")); + + StyleContext::add_provider_for_display( + &Display::default().unwrap(), + &provider, + gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +fn main() { + // Initialize logger + pretty_env_logger::init(); + glib::set_application_name("Cosmic Dock App List"); + + localize(); + + gio::resources_register_include!("compiled.gresource").unwrap(); + let app = gtk4::Application::new(Some(ID), ApplicationFlags::default()); + + app.connect_activate(|app| { + load_css(); + let (tx, mut rx) = mpsc::channel(100); + + let window = CosmicAppListWindow::new(app, tx.clone()); + + let apps_container = apps_container::AppsContainer::new(tx.clone()); + let cached_results = Arc::new(Mutex::new(Vec::new())); + // let zbus_conn = spawn_zbus(tx.clone(), Arc::clone(&cached_results)); + TX.set(tx.clone()).unwrap(); + + let _ = glib::MainContext::default().spawn_local(async move { + while let Some(event) = rx.recv().await { + match event { + Event::Activate(_) => { + // let _activate_window = zbus_conn + // .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) + // .await + // .expect("Failed to focus selected window"); + } + Event::Close(_) => { + // let _activate_window = zbus_conn + // .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,))) + // .await + // .expect("Failed to close selected window"); + } + Event::Favorite((name, should_favorite)) => { + dbg!(&name); + dbg!(should_favorite); + let saved_app_model = apps_container.model(DockListType::Saved); + let active_app_model = apps_container.model(DockListType::Active); + if should_favorite { + let mut cur: u32 = 0; + let mut index: Option = None; + while let Some(item) = active_app_model.item(cur) { + if let Ok(cur_dock_object) = item.downcast::() { + if cur_dock_object.get_path() == Some(name.clone()) { + cur_dock_object.set_saved(true); + index = Some(cur); + } + } + cur += 1; + } + if let Some(index) = index { + let object = active_app_model.item(index).unwrap(); + active_app_model.remove(index); + saved_app_model.append(&object); + } + } else { + let mut cur: u32 = 0; + let mut index: Option = None; + while let Some(item) = saved_app_model.item(cur) { + if let Ok(cur_dock_object) = item.downcast::() { + if cur_dock_object.get_path() == Some(name.clone()) { + cur_dock_object.set_saved(false); + index = Some(cur); + } + } + cur += 1; + } + if let Some(index) = index { + let object = saved_app_model.item(index).unwrap(); + saved_app_model.remove(index); + active_app_model.append(&object); + } + } + let _ = tx.send(Event::RefreshFromCache).await; + } + Event::RefreshFromCache => { + // println!("refreshing model from cache"); + let cached_results = cached_results.as_ref().lock().unwrap(); + let stack_active = cached_results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem:&Item| { + if let Some(v) = acc.get_mut(&elem.description) { + v.0.push(elem.clone()); + } else { + acc.insert( + elem.description.clone(), + BoxedWindowList(vec![elem.clone()]), + ); + } + acc + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); + + // update active app stacks for saved apps into the saved app model + // then put the rest in the active app model (which doesn't include saved apps) + let saved_app_model = apps_container.model(DockListType::Saved); + + let mut saved_i: u32 = 0; + while let Some(item) = saved_app_model.item(saved_i) { + if let Ok(dock_obj) = item.downcast::() { + if let Some(cur_app_info) = + dock_obj.property::>("appinfo") + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].description == cur_app_info.name()) + { + // println!( + // "found active saved app {} at {}", + // _s.0[0].name, i + // ); + let active = stack_active.remove(i); + dock_obj.set_property("active", active.to_value()); + saved_app_model.items_changed( + saved_i, + 0, + 0, + ); + } else if cached_results + .iter() + .any(|s| s.description == cur_app_info.name()) + { + dock_obj.set_property( + "active", + BoxedWindowList(Vec::new()).to_value(), + ); + saved_app_model.items_changed( + saved_i, + 0, + 0, + ); + } + } + } + saved_i += 1; + } + + let active_app_model = apps_container.model(DockListType::Active); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); + } + Event::WindowList => { + // sort to make comparison with cache easier + let results = cached_results.as_ref().lock().unwrap(); + + // build active app stacks for each app + let stack_active = results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem| { + if let Some(v) = acc.get_mut(&elem.description) { + v.0.push(elem.clone()); + } else { + acc.insert( + elem.description.clone(), + BoxedWindowList(vec![elem.clone()]), + ); + } + acc + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); + + // update active app stacks for saved apps into the saved app model + // then put the rest in the active app model (which doesn't include saved apps) + let saved_app_model = apps_container.model(DockListType::Saved); + + let mut saved_i: u32 = 0; + while let Some(item) = saved_app_model.item(saved_i) { + if let Ok(dock_obj) = item.downcast::() { + if let Some(cur_app_info) = + dock_obj.property::>("appinfo") + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].description == cur_app_info.name()) + { + // println!("found active saved app {} at {}", s.0[0].name, i); + let active = stack_active.remove(i); + dock_obj.set_property("active", active.to_value()); + saved_app_model.items_changed( + saved_i, + 0, + 0, + ); + } else if results + .iter() + .any(|s| s.description == cur_app_info.name()) + { + dock_obj.set_property( + "active", + BoxedWindowList(Vec::new()).to_value(), + ); + saved_app_model.items_changed( + saved_i, + 0, + 0, + ); + } + } + } + saved_i += 1; + } + + let active_app_model = apps_container.model(DockListType::Active); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); + } + } + } + }); + window.show(); + }); + app.run(); +} diff --git a/applets/cosmic-app-list/src/style.css b/applets/cosmic-app-list/src/style.css new file mode 100644 index 00000000..e4a9c1a7 --- /dev/null +++ b/applets/cosmic-app-list/src/style.css @@ -0,0 +1,64 @@ +listview { + border-color: transparent; + background: transparent; + outline-color: transparent; +} + +listview row { + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + background: transparent; + border-color: transparent; + outline-color: transparent; +} + +listview row:hover { + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + background: transparent; + border-color: transparent; + outline-color: transparent; +} + +list.popover_menu { + background: transparent; +} + +box.apps { + background: transparent; +} + +image { + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; +} + +button.dock_item { + border-radius: 12px; + transition: 100ms; + padding: 4px; + border-color: transparent; + background: transparent; + outline-color: transparent; +} + +button.dock_item:hover { + border-radius: 12px; + transition: 100ms; + padding: 4px; + border-color: rgba(255, 255, 255, 0.1); + outline-color: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.1); +} + +*.transparent { + border-color: transparent; + background: transparent; + outline-color: transparent; +} diff --git a/applets/cosmic-app-list/src/utils.rs b/applets/cosmic-app-list/src/utils.rs new file mode 100644 index 00000000..868ccc37 --- /dev/null +++ b/applets/cosmic-app-list/src/utils.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::path::PathBuf; + +use gtk4::glib; +use serde::{Deserialize, Serialize}; +use std::future::Future; + +pub const DEST: &str = "com.System76.PopShell"; +pub const PATH: &str = "/com/System76/PopShell"; + +#[derive(Debug)] +pub enum Event { + WindowList, + Activate((u32, u32)), + Close((u32, u32)), + Favorite((String, bool)), + RefreshFromCache, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Item { + pub(crate) entity: (u32, u32), + pub(crate) name: String, + pub(crate) description: String, + pub(crate) desktop_entry: String, +} + +#[derive(Clone, Debug, Default, glib::Boxed)] +#[boxed_type(name = "BoxedWindowList")] +pub struct BoxedWindowList(pub Vec); + +pub fn data_path() -> PathBuf { + let mut path = glib::user_data_dir(); + path.push(crate::ID); + std::fs::create_dir_all(&path).expect("Could not create directory."); + path.push("data.json"); + path +} + +pub fn thread_context() -> glib::MainContext { + glib::MainContext::thread_default().unwrap_or_else(|| { + let ctx = glib::MainContext::new(); + ctx + }) +} + +pub fn block_on(future: F) -> F::Output +where + F: Future, +{ + let ctx = thread_context(); + ctx.with_thread_default(|| ctx.block_on(future)).unwrap() +} diff --git a/applets/cosmic-applet-audio/Cargo.toml b/applets/cosmic-applet-audio/Cargo.toml index dada7516..5b85e326 100644 --- a/applets/cosmic-applet-audio/Cargo.toml +++ b/applets/cosmic-applet-audio/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0-or-later" [dependencies] futures-util = "0.3.21" -libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic", branch = "lucy/widgets" } +libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic" } libpulse-binding = "2.26.0" pulsectl-rs = "0.3.2" tracker = "0.1.1" diff --git a/applets/cosmic-applet-graphics/Cargo.toml b/applets/cosmic-applet-graphics/Cargo.toml index ca9193e1..0fdb1f42 100644 --- a/applets/cosmic-applet-graphics/Cargo.toml +++ b/applets/cosmic-applet-graphics/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] gtk4 = { version = "0.4.6", features = ["v4_2"] } once_cell = "1.9.0" -relm4-macros = "0.4.2" +relm4-macros = "0.4.4" tokio = { version = "1.16.1", features = ["full"] } zbus = "2.1.1" cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", features = ["gtk4"]} diff --git a/applets/cosmic-applet-network/Cargo.toml b/applets/cosmic-applet-network/Cargo.toml index 4de986fe..e56c8dce 100644 --- a/applets/cosmic-applet-network/Cargo.toml +++ b/applets/cosmic-applet-network/Cargo.toml @@ -10,9 +10,9 @@ futures-util = "0.3.21" gtk4 = "0.4.6" itertools = "0.10.3" once_cell = "1.9.0" -relm4-macros = "0.4.3" +relm4-macros = "0.4.4" slotmap = "1.0.6" tokio = { version = "1.15.0", features = ["full"] } zbus = "2.0.1" -libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic", branch = "lucy/widgets" } +libcosmic-widgets = { git = "https://github.com/pop-os/libcosmic" } cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", features = ["gtk4"]} \ No newline at end of file diff --git a/applets/cosmic-applet-power/Cargo.toml b/applets/cosmic-applet-power/Cargo.toml index 7c0b62c2..636bcf8a 100644 --- a/applets/cosmic-applet-power/Cargo.toml +++ b/applets/cosmic-applet-power/Cargo.toml @@ -8,9 +8,9 @@ license = "GPL-3.0-or-later" futures-util = "0.3.21" gtk4 = "0.4.6" logind-zbus = "3.0.1" -nix = "0.23.1" +nix = "0.24.1" once_cell = "1.9.0" -relm4-macros = "0.4.1" +relm4-macros = "0.4.4" tokio = { version = "1.15.0", features = ["full"] } zbus = "2.0.1" cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", features = ["gtk4"]} diff --git a/applets/cosmic-panel-app-button/data/com.system76.CosmicPanelAppButton.desktop b/applets/cosmic-panel-app-button/data/com.system76.CosmicPanelAppButton.desktop new file mode 100644 index 00000000..b7bb665a --- /dev/null +++ b/applets/cosmic-panel-app-button/data/com.system76.CosmicPanelAppButton.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Cosmic Panel App Library Button +Comment=Write a GTK + Rust application +Type=Application +Exec=cosmic-panel-button --id com.system76.CosmicAppLibrary +Terminal=false +Categories=GNOME;GTK; +Keywords=Gnome;GTK; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=com.system76.CosmicPanelAppButton.svg +StartupNotify=true +NoDisplay=true diff --git a/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton-symbolic.svg b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton-symbolic.svg new file mode 100644 index 00000000..a4337c2d --- /dev/null +++ b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton-symbolic.svg @@ -0,0 +1,99 @@ + + + + + + image/svg+xml + + Pop_icon + + + + + + Pop_icon + + + + + + + + + + + + diff --git a/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.Devel.svg b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.Devel.svg new file mode 100644 index 00000000..a4337c2d --- /dev/null +++ b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.Devel.svg @@ -0,0 +1,99 @@ + + + + + + image/svg+xml + + Pop_icon + + + + + + Pop_icon + + + + + + + + + + + + diff --git a/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.svg b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.svg new file mode 100644 index 00000000..a4337c2d --- /dev/null +++ b/applets/cosmic-panel-app-button/data/icons/com.system76.CosmicPanelAppButton.svg @@ -0,0 +1,99 @@ + + + + + + image/svg+xml + + Pop_icon + + + + + + Pop_icon + + + + + + + + + + + + diff --git a/applets/cosmic-panel-app-button/data/resources/resources.gresource.xml b/applets/cosmic-panel-app-button/data/resources/resources.gresource.xml new file mode 100644 index 00000000..3e8c922d --- /dev/null +++ b/applets/cosmic-panel-app-button/data/resources/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/applets/cosmic-panel-button/Cargo.toml b/applets/cosmic-panel-button/Cargo.toml new file mode 100644 index 00000000..99cc6880 --- /dev/null +++ b/applets/cosmic-panel-button/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cosmic-panel-button" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +cosmic-panel-config = { git="https://github.com/pop-os/cosmic-panel/", features = ["gtk4"] } +cascade = "1.0.0" +gtk4 = { version = "0.4.5", features = ["v4_4"] } +once_cell = "1.9.0" +pretty_env_logger = "0.4" +anyhow = "1.0.50" +i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } +i18n-embed-fl = "0.6.4" +rust-embed = "6.3.0" + +[build-dependencies] +gio = "0.15.10" diff --git a/applets/cosmic-panel-button/build.rs b/applets/cosmic-panel-button/build.rs new file mode 100644 index 00000000..7c42fb5e --- /dev/null +++ b/applets/cosmic-panel-button/build.rs @@ -0,0 +1,7 @@ +fn main() { + gio::compile_resources( + "data/resources", + "data/resources/resources.gresource.xml", + "compiled.gresource", + ); +} diff --git a/applets/cosmic-panel-button/data/resources/resources.gresource.xml b/applets/cosmic-panel-button/data/resources/resources.gresource.xml new file mode 100644 index 00000000..3e8c922d --- /dev/null +++ b/applets/cosmic-panel-button/data/resources/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/applets/cosmic-panel-button/i18n.toml b/applets/cosmic-panel-button/i18n.toml new file mode 100644 index 00000000..05c50ba2 --- /dev/null +++ b/applets/cosmic-panel-button/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en" + +[fluent] +assets_dir = "i18n" \ No newline at end of file diff --git a/applets/cosmic-panel-button/i18n/en/cosmic_panel_button.ftl b/applets/cosmic-panel-button/i18n/en/cosmic_panel_button.ftl new file mode 100644 index 00000000..78cecf6b --- /dev/null +++ b/applets/cosmic-panel-button/i18n/en/cosmic_panel_button.ftl @@ -0,0 +1 @@ +cosmic-panel-button = Cosmic Panel Button \ No newline at end of file diff --git a/applets/cosmic-panel-button/src/apps_window/imp.rs b/applets/cosmic-panel-button/src/apps_window/imp.rs new file mode 100644 index 00000000..39e74b24 --- /dev/null +++ b/applets/cosmic-panel-button/src/apps_window/imp.rs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use gtk4::{glib, subclass::prelude::*}; +// Object holding the state +#[derive(Default)] + +pub struct CosmicPanelAppButtonWindow {} + +// The central trait for subclassing a GObject +#[glib::object_subclass] +impl ObjectSubclass for CosmicPanelAppButtonWindow { + // `NAME` needs to match `class` attribute of template + const NAME: &'static str = "CosmicPanelAppButtonWindow"; + type Type = super::CosmicPanelAppButtonWindow; + type ParentType = gtk4::ApplicationWindow; +} + +// Trait shared by all GObjects +impl ObjectImpl for CosmicPanelAppButtonWindow {} + +// Trait shared by all widgets +impl WidgetImpl for CosmicPanelAppButtonWindow {} + +// Trait shared by all windows +impl WindowImpl for CosmicPanelAppButtonWindow {} + +// Trait shared by all application +impl ApplicationWindowImpl for CosmicPanelAppButtonWindow {} diff --git a/applets/cosmic-panel-button/src/apps_window/mod.rs b/applets/cosmic-panel-button/src/apps_window/mod.rs new file mode 100644 index 00000000..53aeacf1 --- /dev/null +++ b/applets/cosmic-panel-button/src/apps_window/mod.rs @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use crate::fl; +use cascade::cascade; +use cosmic_panel_config::config::CosmicPanelConfig; +use gtk4::{ + gio::{self, DesktopAppInfo, Icon}, + glib::{self, Object}, + prelude::*, + Align, Application, Button, Orientation, +}; +use std::process::Command; + +mod imp; + +glib::wrapper! { + pub struct CosmicPanelAppButtonWindow(ObjectSubclass) + @extends gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget, + @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable, + gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager; +} + +impl CosmicPanelAppButtonWindow { + pub fn new(app: &Application, app_desktop_file_name: &str) -> Self { + let self_: Self = Object::new(&[("application", app)]) + .expect("Failed to create `CosmicPanelButtonWindow`."); + cascade! { + &self_; + ..set_width_request(1); + ..set_height_request(1); + ..set_decorated(false); + ..set_resizable(false); + ..set_title(Some(app_desktop_file_name)); + ..add_css_class("root_window"); + }; + + if let Some(apps_desktop_info) = DesktopAppInfo::new(&format!("{}.desktop", app_desktop_file_name)) { + let app_button = cascade! { + Button::new(); + ..add_css_class("apps"); + }; + let config = CosmicPanelConfig::load_from_env().unwrap_or_default(); + let icon = apps_desktop_info.icon().unwrap_or_else(|| { + Icon::for_string("image-missing").expect("Failed to set default icon") + }); + let container = gtk4::Box::new(Orientation::Horizontal, 0); + let image = cascade! { + gtk4::Image::from_gicon(&icon); + ..set_hexpand(true); + ..set_halign(Align::Center); + ..set_pixel_size(config.get_applet_icon_size().try_into().unwrap()); + ..set_tooltip_text(Some(&apps_desktop_info.name())); + }; + container.append(&image); + + app_button.set_child(Some(&container)); + dbg!(apps_desktop_info.string("Exec").unwrap().as_str()); + app_button.connect_clicked(move |_| { + let _ = Command::new("xdg-shell-wrapper") + .env_remove("WAYLAND_SOCKET") + .arg(apps_desktop_info.string("Exec").unwrap().as_str()) + .spawn(); + }); + self_.set_child(Some(&app_button)); + } else { + panic!("Requested application is not installed"); + } + + self_ + } +} diff --git a/applets/cosmic-panel-button/src/localize.rs b/applets/cosmic-panel-button/src/localize.rs new file mode 100644 index 00000000..efb92aa4 --- /dev/null +++ b/applets/cosmic-panel-button/src/localize.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use i18n_embed::{ + fluent::{fluent_language_loader, FluentLanguageLoader}, + DefaultLocalizer, LanguageLoader, Localizer, +}; +use once_cell::sync::Lazy; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "i18n/"] +struct Localizations; + +pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| { + let loader: FluentLanguageLoader = fluent_language_loader!(); + + loader + .load_fallback_language(&Localizations) + .expect("Error while loading fallback language"); + + loader +}); + +#[macro_export] +macro_rules! fl { + ($message_id:literal) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id) + }}; + + ($message_id:literal, $($args:expr),*) => {{ + i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *) + }}; +} + +// Get the `Localizer` to be used for localizing this library. +pub fn localizer() -> Box { + Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations)) +} diff --git a/applets/cosmic-panel-button/src/main.rs b/applets/cosmic-panel-button/src/main.rs new file mode 100644 index 00000000..ecbcbf37 --- /dev/null +++ b/applets/cosmic-panel-button/src/main.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use apps_window::CosmicPanelAppButtonWindow; +use gtk4::gdk::Display; +use gtk4::{ + gio::{self, ApplicationFlags}, + glib, + prelude::*, + CssProvider, StyleContext, +}; +use once_cell::sync::OnceCell; + +mod apps_window; +mod localize; +mod utils; + +static ID: OnceCell = OnceCell::new(); + +pub fn localize() { + let localizer = crate::localize::localizer(); + let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); + + if let Err(error) = localizer.select(&requested_languages) { + eprintln!("Error while loading language for App List {}", error); + } +} + +fn load_css() { + let provider = CssProvider::new(); + provider.load_from_data(include_bytes!("style.css")); + + StyleContext::add_provider_for_display( + &Display::default().unwrap(), + &provider, + gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +fn main() { + // Initialize logger + pretty_env_logger::init(); + glib::set_application_name("Cosmic Panel App Button"); + + localize(); + gio::resources_register_include!("compiled.gresource").unwrap(); + let app = gtk4::Application::new(None, ApplicationFlags::default()); + app.add_main_option("id", glib::Char::from(b'i'), glib::OptionFlags::NONE, glib::OptionArg::String, "id of the launched application", None); + app.connect_handle_local_options(|_app, args| { + if let Ok(Some(id)) = args.lookup::("id") { + ID.set(id).unwrap(); + -1 + } else { + 1 + } + }); + app.connect_activate(|app| { + load_css(); + let id = ID.get().unwrap().clone(); + let window = CosmicPanelAppButtonWindow::new(app, &id); + + window.show(); + }); + app.run(); +} diff --git a/applets/cosmic-panel-button/src/style.css b/applets/cosmic-panel-button/src/style.css new file mode 100644 index 00000000..794da45a --- /dev/null +++ b/applets/cosmic-panel-button/src/style.css @@ -0,0 +1,28 @@ +button { + border-radius: 12px; + transition: 100ms; + padding: 4px; + border-color: transparent; + background: transparent; + outline-color: transparent; +} + +button:hover { + border-radius: 12px; + transition: 100ms; + padding: 4px; + border-color: rgba(255, 255, 255, 0.1); + outline-color: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.1); +} + +image { + border-color: transparent; + background: transparent; + outline-color: transparent; + padding: 0px; +} + +window.root_window { + background: transparent; +} diff --git a/applets/cosmic-panel-button/src/utils.rs b/applets/cosmic-panel-button/src/utils.rs new file mode 100644 index 00000000..c66833d0 --- /dev/null +++ b/applets/cosmic-panel-button/src/utils.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0-only + +use std::path::PathBuf; + +use gtk4::glib; +use std::future::Future; + +pub fn data_path(id: &str) -> PathBuf { + let mut path = glib::user_data_dir(); + path.push(id); + std::fs::create_dir_all(&path).expect("Could not create directory."); + path.push("data.json"); + path +} + +pub fn thread_context() -> glib::MainContext { + glib::MainContext::thread_default().unwrap_or_else(|| glib::MainContext::new()) +} + +pub fn block_on(future: F) -> F::Output +where + F: Future, +{ + let ctx = thread_context(); + ctx.with_thread_default(|| ctx.block_on(future)).unwrap() +} diff --git a/applets/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop b/applets/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop new file mode 100644 index 00000000..74a56cfb --- /dev/null +++ b/applets/cosmic-panel-workspaces-button/data/com.system76.CosmicPanelWorkspacesButton.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Cosmic Panel Workspaces Button +Comment=Write a GTK + Rust application +Type=Application +Exec=cosmic-panel-button -id com.system76.CosmicWorkspaces +Terminal=false +Categories=GNOME;GTK; +Keywords=Gnome;GTK; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=com.system76.CosmicPanelWorkspacesButton.svg +StartupNotify=true +NoDisplay=true diff --git a/applets/cosmic-panel-workspaces-button/data/icons/com.system76.CosmicPanelWorkspacesButton.svg b/applets/cosmic-panel-workspaces-button/data/icons/com.system76.CosmicPanelWorkspacesButton.svg new file mode 100644 index 00000000..a4337c2d --- /dev/null +++ b/applets/cosmic-panel-workspaces-button/data/icons/com.system76.CosmicPanelWorkspacesButton.svg @@ -0,0 +1,99 @@ + + + + + + image/svg+xml + + Pop_icon + + + + + + Pop_icon + + + + + + + + + + + + diff --git a/applets/cosmic-panel-workspaces-button/data/resources/resources.gresource.xml b/applets/cosmic-panel-workspaces-button/data/resources/resources.gresource.xml new file mode 100644 index 00000000..3e8c922d --- /dev/null +++ b/applets/cosmic-panel-workspaces-button/data/resources/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/justfile b/justfile index b5bd750a..7028a576 100644 --- a/justfile +++ b/justfile @@ -13,17 +13,27 @@ sharedir := rootdir + prefix + '/share' iconsdir := sharedir + '/icons/hicolor/scalable/apps' bindir := rootdir + prefix + '/bin' +app_list_id := 'com.system76.CosmicAppList' audio_id := 'com.system76.CosmicAppletAudio' graphics_id := 'com.system76.CosmicAppletGraphics' network_id := 'com.system76.CosmicAppletNetwork' power_id := 'com.system76.CosmicAppletPower' status_area_id := 'com.system76.CosmicAppletStatusArea' +app_button_id := 'com.system76.CosmicPanelAppButton' +workspaces_button_id := 'com.system76.CosmicPanelWorkspacesButton' all: _extract_vendor cargo build {{cargo_args}} # Installs files into the system install: + # app list + install -Dm0644 applets/cosmic-app-list/data/icons/{{app_list_id}}-symbolic.svg {{iconsdir}}/{{app_list_id}}-symbolic.svg + install -Dm0644 applets/cosmic-app-list/data/icons/{{app_list_id}}.Devel.svg {{iconsdir}}/{{app_list_id}}.Devel.svg + install -Dm0644 applets/cosmic-app-list/data/icons/{{app_list_id}}.svg {{iconsdir}}/{{app_list_id}}.svg + install -Dm0644 applets/cosmic-app-list/data/{{app_list_id}}.desktop {{sharedir}}/applications/{{app_list_id}}.desktop + install -Dm04755 target/release/cosmic-app-list {{bindir}}/cosmic-app-list + # audio install -Dm0644 applets/cosmic-applet-audio/data/icons/{{audio_id}}.svg {{iconsdir}}/{{audio_id}}.svg install -Dm0644 applets/cosmic-applet-audio/data/{{audio_id}}.desktop {{sharedir}}/applications/{{audio_id}}.desktop @@ -49,6 +59,17 @@ install: install -Dm0644 applets/cosmic-applet-status-area/data/{{status_area_id}}.desktop {{sharedir}}/applications/{{status_area_id}}.desktop install -Dm04755 target/release/cosmic-applet-status-area {{bindir}}/cosmic-applet-status-area + # app library button + install -Dm0644 applets/cosmic-panel-app-button/data/icons/{{app_button_id}}.svg {{iconsdir}}/{{app_button_id}}.svg + install -Dm0644 applets/cosmic-panel-app-button/data/{{app_button_id}}.desktop {{sharedir}}/applications/{{app_button_id}}.desktop + + # workspaces button + install -Dm0644 applets/cosmic-panel-workspaces-button/data/icons/{{workspaces_button_id}}.svg {{iconsdir}}/{{workspaces_button_id}}.svg + install -Dm0644 applets/cosmic-panel-workspaces-button/data/{{workspaces_button_id}}.desktop {{sharedir}}/applications/{{workspaces_button_id}}.desktop + + # panel button + install -Dm04755 target/release/cosmic-panel-button {{bindir}}/cosmic-panel-button + # Extracts vendored dependencies if vendor=1 _extract_vendor: #!/usr/bin/env sh diff --git a/rust-toolchain b/rust-toolchain index 43c989b5..4213d88d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.56.1 +1.61
A boilerplate template for GTK + Rust. It uses Meson as a build system and has flatpak support by default.