diff --git a/.gitignore b/.gitignore index 08bf5da..37e070b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /.vscode /_build /target +/repo diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3e8c0b..d2a5acd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - lint .flatpak: - image: 'quay.io/gnome_infrastructure/gnome-runtime-images:gnome-45' + image: 'quay.io/gnome_infrastructure/gnome-runtime-images:gnome-50' variables: FLATPAK_BUILD_DIR: _build MANIFEST_PATH: build-aux/org.pipewire.Helvum.json @@ -12,7 +12,7 @@ stages: - flatpak --version - flatpak info org.gnome.Platform - flatpak info org.gnome.Sdk - - flatpak info org.freedesktop.Sdk.Extension.llvm16 + - flatpak info org.freedesktop.Sdk.Extension.llvm21 - flatpak info org.freedesktop.Sdk.Extension.rust-stable - flatpak-builder --version @@ -50,4 +50,3 @@ rustdoc: - >- flatpak-builder --run ${FLATPAK_BUILD_DIR} ${MANIFEST_PATH} env RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps - diff --git a/Cargo.lock b/Cargo.lock index 4876527..c209b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,79 +1,86 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] -name = "anyhow" -version = "1.0.75" +name = "annotate-snippets" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bindgen" -version = "0.66.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.4.0", + "annotate-snippets", + "bitflags", "cexpr", "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", + "itertools", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn", ] [[package]] name = "bitflags" -version = "1.3.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "cairo-rs" -version = "0.18.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c0466dfa8c0ee78deef390c274ad756801e0a6dbb86c5ef0924a298c5761c4d" +checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" dependencies = [ - "bitflags 2.4.0", + "bitflags", "cairo-sys-rs", "glib", "libc", - "once_cell", - "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.18.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" dependencies = [ "glib-sys", "libc", @@ -82,11 +89,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ - "libc", + "find-msvc-tools", + "shlex", ] [[package]] @@ -95,14 +103,14 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] name = "cfg-expr" -version = "0.15.5" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -110,15 +118,21 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -127,24 +141,30 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" dependencies = [ "unicode-segmentation", ] [[package]] name = "cookie-factory" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "field-offset" @@ -152,30 +172,52 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset 0.9.0", + "memoffset", "rustc_version", ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -184,59 +226,67 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn", ] [[package]] -name = "futures-task" -version = "0.3.28" +name = "futures-sink" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "gdk-pixbuf" -version = "0.18.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc9c2ed73a81d556b65d08879ba4ee58808a6b1927ce915262185d6d547c6f3" +checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", - "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.18.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" dependencies = [ "gio-sys", "glib-sys", @@ -247,9 +297,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.7.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6" +checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -262,9 +312,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.7.2" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0" +checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -279,9 +329,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.18.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57052f84e8e5999b258e8adf8f5f2af0ac69033864936b8b6838321db2f759b1" +checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6" dependencies = [ "futures-channel", "futures-core", @@ -290,32 +340,30 @@ dependencies = [ "gio-sys", "glib", "libc", - "once_cell", "pin-project-lite", "smallvec", - "thiserror", ] [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "winapi", + "windows-sys", ] [[package]] name = "glib" -version = "0.18.2" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19" +checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" dependencies = [ - "bitflags 2.4.0", + "bitflags", "futures-channel", "futures-core", "futures-executor", @@ -328,30 +376,26 @@ dependencies = [ "libc", "log", "memchr", - "once_cell", "smallvec", - "thiserror", ] [[package]] name = "glib-macros" -version = "0.18.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8da903822b136d42360518653fcf154455defc437d3e7a81475bf9a95ff1e47" +checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" dependencies = [ "heck", - "proc-macro-crate", - "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.37", + "syn", ] [[package]] name = "glib-sys" -version = "0.18.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445" dependencies = [ "libc", "system-deps", @@ -359,15 +403,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gobject-sys" -version = "0.18.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" dependencies = [ "glib-sys", "libc", @@ -376,9 +420,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.18.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401" +checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" dependencies = [ "glib", "graphene-sys", @@ -387,9 +431,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.18.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59" +checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" dependencies = [ "glib-sys", "libc", @@ -399,9 +443,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.7.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e" +checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62" dependencies = [ "cairo-rs", "gdk4", @@ -414,9 +458,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.7.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55" +checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -430,9 +474,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.7.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842" +checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2" dependencies = [ "cairo-rs", "field-offset", @@ -451,23 +495,21 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.7.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f" +checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" dependencies = [ - "anyhow", "proc-macro-crate", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "gtk4-sys" -version = "0.7.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8" +checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -484,57 +526,54 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helvum" version = "0.5.1" dependencies = [ + "futures", "glib", + "gtk4", "libadwaita", "libc", "log", - "once_cell", "pipewire", ] [[package]] name = "indexmap" -version = "2.0.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] [[package]] name = "libadwaita" -version = "0.5.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c" +checksum = "bc0da4e27b20d3e71f830e5b0f0188d22c257986bf421c02cfde777fe07932a4" dependencies = [ - "gdk-pixbuf", "gdk4", "gio", "glib", @@ -546,9 +585,9 @@ dependencies = [ [[package]] name = "libadwaita-sys" -version = "0.5.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a" +checksum = "aaee067051c5d3c058d050d167688b80b67de1950cfca77730549aa761fc5d7d" dependencies = [ "gdk4-sys", "gio-sys", @@ -562,42 +601,42 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.148" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "winapi", + "windows-link", ] [[package]] name = "libspa" -version = "0.7.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4" +checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" dependencies = [ - "bitflags 2.4.0", + "bitflags", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", "nix", - "nom", + "nom 8.0.0", "system-deps", ] [[package]] name = "libspa-sys" -version = "0.7.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345" +checksum = "901049455d2eb6decf9058235d745237952f4804bc584c5fcb41412e6adcc6e0" dependencies = [ "bindgen", "cc", @@ -606,30 +645,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.6.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -642,15 +672,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nix" -version = "0.26.4" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.7.1", - "pin-utils", ] [[package]] @@ -664,29 +693,37 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.18.0" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "pango" -version = "0.18.0" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a9e54b831d033206160096b825f2070cf5fda7e35167b1c01e9e774f9202d1" +checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438" dependencies = [ "gio", "glib", "libc", - "once_cell", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.18.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" dependencies = [ "glib-sys", "gobject-sys", @@ -694,32 +731,20 @@ dependencies = [ "system-deps", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pipewire" -version = "0.7.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9" +checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" dependencies = [ "anyhow", - "bitflags 2.4.0", + "bitflags", "libc", "libspa", "libspa-sys", @@ -731,9 +756,9 @@ dependencies = [ [[package]] name = "pipewire-sys" -version = "0.7.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64" +checksum = "cb028afee0d6ca17020b090e3b8fa2d7de23305aef975c7e5192a5050246ea36" dependencies = [ "bindgen", "libspa-sys", @@ -742,67 +767,42 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "once_cell", "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -812,9 +812,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -823,97 +823,83 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "semver" -version = "1.0.19" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "serde" -version = "1.0.188" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.11.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -922,9 +908,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.1.1" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck", @@ -935,115 +921,125 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "thiserror" -version = "1.0.49" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn", ] [[package]] name = "toml" -version = "0.7.8" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "version-compare" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] -name = "version_check" -version = "0.9.4" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-link", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "winnow" -version = "0.5.15" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index e72f4fd..9d7c57e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "helvum" version = "0.5.1" -authors = ["Tom Wagner "] -edition = "2021" -rust-version = "1.70" +authors = [ + "Tom Wagner ", + "Jaŭhien Lavonćjeŭ " +] +edition = "2024" license = "GPL-3.0-only" description = "A GTK patchbay for pipewire" repository = "https://gitlab.freedesktop.org/pipewire/helvum" @@ -14,12 +16,10 @@ categories = ["gui", "multimedia"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pipewire = "0.7.1" -adw = { version = "0.5", package = "libadwaita", features = ["v1_4"] } -glib = { version = "0.18", features = ["log"] } - -log = "0.4.11" - -once_cell = "1.7.2" - +pipewire = "0.9.2" +adw = { version = "0.9.1", package = "libadwaita", features = ["v1_9"] } +gtk = { package = "gtk4", version = "0.11.2", features = ["gnome_50"] } +glib = { version = "0.22.7", features = ["log"] } +futures = "0.3.32" +log = "0.4.29" libc = "0.2" diff --git a/build-aux/org.pipewire.Helvum.json b/build-aux/org.pipewire.Helvum.json index 8716d17..36e7d05 100644 --- a/build-aux/org.pipewire.Helvum.json +++ b/build-aux/org.pipewire.Helvum.json @@ -1,11 +1,11 @@ { "id": "org.pipewire.Helvum", "runtime": "org.gnome.Platform", - "runtime-version": "45", + "runtime-version": "50", "sdk": "org.gnome.Sdk", "sdk-extensions": [ "org.freedesktop.Sdk.Extension.rust-stable", - "org.freedesktop.Sdk.Extension.llvm16" + "org.freedesktop.Sdk.Extension.llvm21" ], "command": "helvum", "finish-args": [ @@ -16,8 +16,8 @@ "--filesystem=xdg-run/pipewire-0" ], "build-options": { - "append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm16/bin", - "prepend-ld-library-path": "/usr/lib/sdk/llvm16/lib", + "append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm21/bin", + "prepend-ld-library-path": "/usr/lib/sdk/llvm21/lib", "build-args": [ "--share=network" ] diff --git a/data/meson.build b/data/meson.build index 305c8f4..0326eb4 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,11 +1,22 @@ -subdir('icons') +blueprints = custom_target('blueprints', + input: files( + 'ui/window.blp', + 'ui/node.blp', + 'ui/port.blp', + 'ui/zoomentry.blp', + ), + output: '.', + command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],) +# Desktop file desktop_conf = configuration_data() desktop_conf.set('icon', base_id) desktop_file = configure_file( input: '@0@.desktop.in'.format(base_id), output: '@BASENAME@', - configuration: desktop_conf + configuration: desktop_conf, + install: true, + install_dir: join_paths(datadir, 'applications') ) if desktop_file_validate.found() @@ -18,18 +29,15 @@ if desktop_file_validate.found() ) endif -install_data( - desktop_file, - install_dir: datadir / 'applications' -) - - +# Appdata appdata_conf = configuration_data() appdata_conf.set('app-id', base_id) appdata_file = configure_file( input: '@0@.metainfo.xml.in'.format(base_id), output: '@BASENAME@', - configuration: appdata_conf + configuration: appdata_conf, + install: true, + install_dir: join_paths(datadir, 'metainfo') ) # Validate Appdata @@ -43,7 +51,42 @@ if appstream_util.found() ) endif -install_data( - appdata_file, - install_dir: datadir / 'metainfo' +# Resources +conf = configuration_data() +conf.set('PATH_ID', path_id) +configure_file( + input: '@0@.gresource.xml.in'.format(base_id), + output: '@0@.gresource.xml'.format(base_id), + configuration: conf, ) + +gnome.compile_resources( + base_id, + join_paths(meson.project_build_root(), 'data', '@0@.gresource.xml'.format(base_id)), + gresource_bundle: true, + source_dir: join_paths(meson.project_build_root(), 'data'), + install: true, + install_dir: join_paths(datadir, meson.project_name()), + dependencies: [appdata_file, blueprints], +) + +gnome.post_install( + gtk_update_icon_cache: true, + glib_compile_schemas: false, + update_desktop_database: true, +) + +# D-Bus service file +conf = configuration_data() +conf.set('APP_ID', base_id) +conf.set('PKGNAME', meson.project_name()) +conf.set('BINDIR', bindir) +configure_file( + input: '@0@.service.in'.format(base_id), + output: '@0@.service'.format(base_id), + configuration: conf, + install: true, + install_dir: join_paths(datadir,'dbus-1', 'services') +) + +subdir('icons') diff --git a/data/org.pipewire.Helvum.gresource.xml.in b/data/org.pipewire.Helvum.gresource.xml.in new file mode 100644 index 0000000..8700ca1 --- /dev/null +++ b/data/org.pipewire.Helvum.gresource.xml.in @@ -0,0 +1,11 @@ + + + + ui/window.ui + ui/node.ui + ui/port.ui + ui/zoomentry.ui + + ui/style.css + + \ No newline at end of file diff --git a/data/org.pipewire.Helvum.service.in b/data/org.pipewire.Helvum.service.in new file mode 100644 index 0000000..b76ba84 --- /dev/null +++ b/data/org.pipewire.Helvum.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=@APP_ID@ +Exec=@BINDIR@/@PKGNAME@ --gapplication-service \ No newline at end of file diff --git a/data/ui/node.blp b/data/ui/node.blp new file mode 100644 index 0000000..50ea8fb --- /dev/null +++ b/data/ui/node.blp @@ -0,0 +1,50 @@ +using Gtk 4.0; + +template $HelvumNode: Widget { + styles [ + "card", + ] + + Box { + orientation: vertical; + + Box { + styles [ + "node-title", + ] + + orientation: vertical; + spacing: 1; + + Label node_name { + styles [ + "heading", + ] + + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + + Label media_name { + styles [ + "dim-label", + "caption", + ] + + visible: false; + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + } + + Separator separator { + visible: false; + } + + Grid port_grid {} + } +} diff --git a/data/ui/port.blp b/data/ui/port.blp new file mode 100644 index 0000000..34eebc9 --- /dev/null +++ b/data/ui/port.blp @@ -0,0 +1,14 @@ +using Gtk 4.0; + +template $HelvumPort: Widget { + hexpand: true; + + Label label { + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + + $HelvumPortHandle handle {} +} diff --git a/src/style.css b/data/ui/style.css similarity index 73% rename from src/style.css rename to data/ui/style.css index 397fedb..d0aebdc 100644 --- a/src/style.css +++ b/data/ui/style.css @@ -1,5 +1,6 @@ /* Copyright 2021 Tom A. Wagner - + Copyright 2026 Jaŭhien Lavonćjeŭ + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. @@ -15,30 +16,25 @@ SPDX-License-Identifier: GPL-3.0-only */ -@define-color media-type-audio rgb( 50, 100, 240); -@define-color media-type-video rgb(200, 200, 0); -@define-color media-type-midi rgb(200, 0, 50); -@define-color media-type-unknown rgb(128, 128, 128); - .audio { - background: @media-type-audio; - color: black; + background: var(--accent-blue); + color: var(--accent-blue); } .video { - background: @media-type-video; - color: black; + background: var(--accent-yellow); + color: var(--accent-yellow); } .midi { - background: @media-type-midi; - color: black; + background: var(--accent-red); + color: var(--accent-red); } node { /* Compared to the default card color, this is not transparent in dark-mode and provides a better contrast to the background in light mode */ - background-color: @headerbar_bg_color; + background-color: var(--headerbar-bg-color); } node .node-title { @@ -51,7 +47,8 @@ port label { port-handle { border-radius: 50%; - background-color: @media-type-unknown; + background-color: var(--accent-slate); + color: var(--accent-slate); } button.rounded { diff --git a/data/ui/window.blp b/data/ui/window.blp new file mode 100644 index 0000000..323dfa3 --- /dev/null +++ b/data/ui/window.blp @@ -0,0 +1,55 @@ +using Gtk 4.0; +using Adw 1; + +menu primary_menu { + section { + item { + label: "_About Helvum"; + action: "app.about"; + } + } +} + +template $HelvumWindow: Adw.ApplicationWindow { + default-width: 1280; + default-height: 720; + title: _("Helvum - Pipewire Patchbay"); + + Adw.ToolbarView { + [top] + Adw.HeaderBar header_bar { + [end] + MenuButton { + icon-name: "open-menu-symbolic"; + menu-model: primary_menu; + } + } + + content: Box { + orientation: vertical; + + Adw.Banner connection_banner { + title: _("Disconnected"); + revealed: false; + } + + Overlay { + ScrolledWindow { + $HelvumGraphView graph { + hexpand: "true"; + vexpand: "true"; + } + } + + [overlay] + $HelvumZoomEntry { + zoomed-widget: "graph"; + halign: "end"; + valign: "end"; + margin-end: "24"; + margin-bottom: "24"; + } + } + }; + } +} diff --git a/data/ui/zoomentry.blp b/data/ui/zoomentry.blp new file mode 100644 index 0000000..caec64a --- /dev/null +++ b/data/ui/zoomentry.blp @@ -0,0 +1,36 @@ +using Gtk 4.0; + +template $HelvumZoomEntry: Box { + spacing: 12; + + Entry entry { + secondary-icon-name: "go-down-symbolic"; + input-purpose: digits; + max-width-chars: 5; + + styles [ + "osd", + "rounded", + ] + } + + Button zoom_out_button { + icon-name: "zoom-out-symbolic"; + tooltip-text: _("Zoom out"); + + styles [ + "osd", + "rounded", + ] + } + + Button zoom_in_button { + icon-name: "zoom-in-symbolic"; + tooltip-text: _("Zoom in"); + + styles [ + "osd", + "rounded", + ] + } +} diff --git a/meson.build b/meson.build index 34bf0fa..8dd52d8 100644 --- a/meson.build +++ b/meson.build @@ -11,9 +11,10 @@ gnome = import('gnome') base_id = 'org.pipewire.Helvum' dependency('glib-2.0', version: '>= 2.66') -dependency('gtk4', version: '>= 4.4.0') -dependency('libadwaita-1', version: '>= 1.4') +dependency('gtk4', version: '>= 4.22.0') +dependency('libadwaita-1', version: '>= 1.9') dependency('libpipewire-0.3') +dependency('blueprint-compiler') desktop_file_validate = find_program('desktop-file-validate', required: false) appstream_util = find_program('appstream-util', required: false) @@ -23,6 +24,7 @@ prefix = get_option('prefix') bindir = prefix / get_option('bindir') datadir = prefix / get_option('datadir') iconsdir = datadir / 'icons' +path_id = '/org/pipewire/Helvum' meson.add_dist_script( 'build-aux/dist-vendor.sh', @@ -32,8 +34,3 @@ meson.add_dist_script( subdir('src') subdir('data') - -gnome.post_install( - gtk_update_icon_cache: true, - update_desktop_database: true, -) diff --git a/src/application.rs b/src/application.rs index 0e4dbc0..2b44a4d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,28 +15,28 @@ // // SPDX-License-Identifier: GPL-3.0-only +use futures::channel::mpsc::UnboundedReceiver; +use pipewire::channel::Sender; +use std::cell::OnceCell; + use adw::{ gio, - glib::{self, clone, Receiver}, + glib::{self, clone}, gtk, prelude::*, subclass::prelude::*, }; -use pipewire::channel::Sender; -use crate::{graph_manager::GraphManager, ui, GtkMessage, PipewireMessage}; +use crate::{GtkMessage, PipewireMessage, graph_manager::GraphManager, ui}; -static STYLE: &str = include_str!("style.css"); -static APP_ID: &str = "org.pipewire.Helvum"; -static VERSION: &str = env!("CARGO_PKG_VERSION"); static AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); +static APP_ID: Option<&str> = option_env!("APP_ID"); +static PATH_ID: Option<&str> = option_env!("PATH_ID"); +static VERSION: Option<&str> = option_env!("VERSION"); mod imp { use super::*; - use adw::subclass::prelude::AdwApplicationImpl; - use once_cell::unsync::OnceCell; - #[derive(Default)] pub struct Application { pub(super) window: ui::Window, @@ -60,31 +61,21 @@ mod imp { let zoom_set_action = gio::SimpleAction::new("set-zoom", Some(&f64::static_variant_type())); - zoom_set_action.connect_activate(clone!(@weak graphview => move|_, param| { - let zoom_factor = param.unwrap().get::().unwrap(); - graphview.set_zoom_factor(zoom_factor, None) - })); + zoom_set_action.connect_activate(clone!( + #[weak] + graphview, + move |_, param| { + let zoom_factor = param.unwrap().get::().unwrap(); + graphview.set_zoom_factor(zoom_factor, None) + } + )); self.window.add_action(&zoom_set_action); - self.window.show(); + self.window.present(); } fn startup(&self) { self.parent_startup(); - - self.obj() - .style_manager() - .set_color_scheme(adw::ColorScheme::PreferDark); - - // Load CSS from the STYLE variable. - let provider = gtk::CssProvider::new(); - provider.load_from_data(STYLE); - gtk::style_context_add_provider_for_display( - >k::gdk::Display::default().expect("Error initializing gtk css provider."), - &provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - self.setup_actions(); } } @@ -97,9 +88,13 @@ mod imp { // Add shortcut for quitting the application. let quit = gtk::gio::SimpleAction::new("quit", None); - quit.connect_activate(clone!(@weak obj => move |_, _| { - obj.quit(); - })); + quit.connect_activate(clone!( + #[weak] + obj, + move |_, _| { + obj.quit(); + } + )); obj.set_accels_for_action("app.quit", &["Q"]); obj.add_action(&quit); @@ -116,19 +111,18 @@ mod imp { let window = obj.active_window().unwrap(); let authors: Vec<&str> = AUTHORS.split(':').collect(); - let about_window = adw::AboutWindow::builder() - .transient_for(&window) - .application_icon(APP_ID) + let about_dialog = adw::AboutDialog::builder() + .application_icon(APP_ID.unwrap()) .application_name("Helvum") .developer_name("Tom Wagner") .developers(authors) - .version(VERSION) + .version(VERSION.unwrap()) .website("https://gitlab.freedesktop.org/pipewire/helvum") .issue_url("https://gitlab.freedesktop.org/pipewire/helvum/-/issues") .license_type(gtk::License::Gpl30Only) .build(); - about_window.present(); + about_dialog.present(Some(&window)); } } } @@ -143,11 +137,12 @@ impl Application { /// Create the view. /// This will set up the entire user interface and prepare it for being run. pub(super) fn new( - gtk_receiver: Receiver, + gtk_receiver: UnboundedReceiver, pw_sender: Sender, ) -> Self { let app: Application = glib::Object::builder() .property("application-id", APP_ID) + .property("resource-base-path", PATH_ID) .build(); let imp = app.imp(); diff --git a/src/graph_manager.rs b/src/graph_manager.rs index 34b2868..7e37263 100644 --- a/src/graph_manager.rs +++ b/src/graph_manager.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,31 +15,34 @@ // // SPDX-License-Identifier: GPL-3.0-only -use adw::{glib, prelude::*, subclass::prelude::*}; +use futures::{channel::mpsc::UnboundedReceiver, prelude::*}; +use std::{ + cell::{OnceCell, RefCell}, + collections::HashMap, +}; -use pipewire::channel::Sender as PwSender; +use adw::{glib::clone, prelude::*, subclass::prelude::*}; -use crate::{ui::graph::GraphView, GtkMessage, PipewireMessage}; +use pipewire::{ + channel::Sender, + spa::{param::format::MediaType, utils::Direction}, +}; + +use crate::{GtkMessage, NodeType, PipewireMessage, ui::graph, ui::graph::GraphView}; mod imp { use super::*; - use std::{cell::RefCell, collections::HashMap}; - - use once_cell::unsync::OnceCell; - - use crate::{ui::graph, MediaType, NodeType}; - #[derive(Default, glib::Properties)] #[properties(wrapper_type = super::GraphManager)] pub struct GraphManager { #[property(get, set, construct_only)] - pub graph: OnceCell, + pub graph: OnceCell, #[property(get, set, construct_only)] pub connection_banner: OnceCell, - pub pw_sender: OnceCell>, + pub pw_sender: OnceCell>, pub items: RefCell>, } @@ -53,34 +57,61 @@ mod imp { impl ObjectImpl for GraphManager {} impl GraphManager { - pub fn attach_receiver(&self, receiver: glib::Receiver) { - receiver.attach(None, glib::clone!( - @weak self as imp => @default-return glib::ControlFlow::Continue, - move |msg| { - match msg { - PipewireMessage::NodeAdded { id, name, node_type } => imp.add_node(id, name.as_str(), node_type), - PipewireMessage::NodeNameChanged { id, name, media_name } => imp.node_name_changed(id, &name, &media_name), - PipewireMessage::PortAdded { id, node_id, name, direction } => imp.add_port(id, name.as_str(), node_id, direction), - PipewireMessage::PortFormatChanged { id, media_type } => imp.port_media_type_changed(id, media_type), - PipewireMessage::LinkAdded { - id, port_from, port_to, active, media_type - } => imp.add_link(id, port_from, port_to, active, media_type), - PipewireMessage::LinkStateChanged { id, active } => imp.link_state_changed(id, active), - PipewireMessage::LinkFormatChanged { id, media_type } => imp.link_format_changed(id, media_type), - PipewireMessage::NodeRemoved { id } => imp.remove_node(id), - PipewireMessage::PortRemoved { id, node_id } => imp.remove_port(id, node_id), - PipewireMessage::LinkRemoved { id } => imp.remove_link(id), - PipewireMessage::Connecting => { - imp.obj().connection_banner().set_revealed(true); - } - PipewireMessage::Connected => { - imp.obj().connection_banner().set_revealed(false); - }, - PipewireMessage::Disconnected => { - imp.clear(); - }, - }; - glib::ControlFlow::Continue + pub fn attach_receiver(&self, mut receiver: UnboundedReceiver) { + glib::MainContext::default().spawn_local(clone!( + #[weak(rename_to = imp)] + self, + async move { + while let Some(msg) = receiver.next().await { + match msg { + PipewireMessage::NodeAdded { + id, + name, + node_type, + } => imp.add_node(id, name.as_str(), node_type), + PipewireMessage::NodeNameChanged { + id, + name, + media_name, + } => imp.node_name_changed(id, &name, &media_name), + PipewireMessage::PortAdded { + id, + node_id, + name, + direction, + } => imp.add_port(id, name.as_str(), node_id, direction), + PipewireMessage::PortFormatChanged { id, media_type } => { + imp.port_media_type_changed(id, media_type) + } + PipewireMessage::LinkAdded { + id, + port_from, + port_to, + active, + media_type, + } => imp.add_link(id, port_from, port_to, active, media_type), + PipewireMessage::LinkStateChanged { id, active } => { + imp.link_state_changed(id, active) + } + PipewireMessage::LinkFormatChanged { id, media_type } => { + imp.link_format_changed(id, media_type) + } + PipewireMessage::NodeRemoved { id } => imp.remove_node(id), + PipewireMessage::PortRemoved { id, node_id } => { + imp.remove_port(id, node_id) + } + PipewireMessage::LinkRemoved { id } => imp.remove_link(id), + PipewireMessage::Connecting => { + imp.obj().connection_banner().set_revealed(true); + } + PipewireMessage::Connected => { + imp.obj().connection_banner().set_revealed(false); + } + PipewireMessage::Disconnected => { + imp.clear(); + } + }; + } } )); } @@ -130,7 +161,7 @@ mod imp { } /// Add a new port to the view. - fn add_port(&self, id: u32, name: &str, node_id: u32, direction: pipewire::spa::Direction) { + fn add_port(&self, id: u32, name: &str, node_id: u32, direction: Direction) { log::info!("Adding port to graph: id {}", id); let mut items = self.items.borrow_mut(); @@ -150,15 +181,21 @@ mod imp { port.connect_local( "port_toggled", false, - glib::clone!(@weak self as app => @default-return None, move |args| { - // Args always look like this: &[widget, id_port_from, id_port_to] - let port_from = args[1].get::().unwrap(); - let port_to = args[2].get::().unwrap(); + glib::clone!( + #[weak(rename_to = app)] + self, + #[upgrade_or] + None, + move |args| { + // Args always look like this: &[widget, id_port_from, id_port_to] + let port_from = args[1].get::().unwrap(); + let port_to = args[2].get::().unwrap(); - app.toggle_link(port_from, port_to); + app.toggle_link(port_from, port_to); - None - }), + None + } + ), ); items.insert(id, port.clone().upcast()); @@ -222,7 +259,9 @@ mod imp { let mut items = self.items.borrow_mut(); let Some(output_port) = items.get(&output_port_id) else { - log::warn!("Output port (id: {output_port_id}) for link (id: {id}) not found in graph manager"); + log::warn!( + "Output port (id: {output_port_id}) for link (id: {id}) not found in graph manager" + ); return; }; let Ok(output_port) = output_port.clone().dynamic_cast::() else { @@ -230,7 +269,9 @@ mod imp { return; }; let Some(input_port) = items.get(&input_port_id) else { - log::warn!("Output port (id: {input_port_id}) for link (id: {id}) not found in graph manager"); + log::warn!( + "Output port (id: {input_port_id}) for link (id: {id}) not found in graph manager" + ); return; }; let Ok(input_port) = input_port.clone().dynamic_cast::() else { @@ -273,7 +314,7 @@ mod imp { link.set_active(active); } - fn link_format_changed(&self, id: u32, media_type: pipewire::spa::format::MediaType) { + fn link_format_changed(&self, id: u32, media_type: MediaType) { let items = self.items.borrow(); let Some(link) = items.get(&id) else { @@ -326,8 +367,8 @@ impl GraphManager { pub fn new( graph: &GraphView, connection_banner: &adw::Banner, - sender: PwSender, - receiver: glib::Receiver, + sender: Sender, + receiver: UnboundedReceiver, ) -> Self { let res: Self = glib::Object::builder() .property("graph", graph) diff --git a/src/main.rs b/src/main.rs index 610c175..e2eaec3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -19,8 +20,14 @@ mod graph_manager; mod pipewire_connection; mod ui; -use adw::{gtk, prelude::*}; -use pipewire::spa::{format::MediaType, Direction}; +use futures::channel::mpsc::unbounded; +use pipewire::spa::{param::format::MediaType, utils::Direction}; + +use adw::{gio, gtk, prelude::*}; + +static APP_ID: Option<&str> = option_env!("APP_ID"); +static PKGNAME: Option<&str> = option_env!("PKGNAME"); +static DATADIR: Option<&str> = option_env!("DATADIR"); /// Messages sent by the GTK thread to notify the pipewire thread. #[derive(Debug, Clone)] @@ -112,21 +119,21 @@ fn init_glib_logger() { fn main() -> Result<(), Box> { init_glib_logger(); - gtk::init()?; - // Aquire main context so that we can attach the gtk channel later. - let ctx = glib::MainContext::default(); - let _guard = ctx.acquire().unwrap(); + let path = std::path::PathBuf::from(DATADIR.unwrap()) + .join(PKGNAME.unwrap()) + .join(format!("{}.gresource", APP_ID.unwrap())); + let resources = gio::Resource::load(path.to_str().unwrap()).expect("Could not load resources"); + gio::resources_register(&resources); // Start the pipewire thread with channels in both directions. - - let (gtk_sender, gtk_receiver) = glib::MainContext::channel(glib::Priority::DEFAULT); + let (gtk_sender, gtk_receiver) = unbounded::(); let (pw_sender, pw_receiver) = pipewire::channel::channel(); let pw_thread = std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver)); + gtk::init()?; let app = application::Application::new(gtk_receiver, pw_sender.clone()); - app.run(); pw_sender diff --git a/src/meson.build b/src/meson.build index 1df179f..4542106 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,13 @@ +cargo_env = [ + 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home', + 'PKGNAME=' + meson.project_name(), + 'VERSION=' + '@0@'.format(meson.project_version()), + 'APP_ID=' + base_id, + 'PATH_ID=' + path_id, + 'PREFIX=' + prefix, + 'DATADIR=' + datadir +] + cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ] cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ] @@ -10,8 +20,6 @@ else message('Building in debug mode') endif -cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ] - custom_target( 'cargo-build', build_by_default: true, diff --git a/src/pipewire_connection/mod.rs b/src/pipewire_connection/mod.rs index 768f737..0dde94f 100644 --- a/src/pipewire_connection/mod.rs +++ b/src/pipewire_connection/mod.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -23,22 +24,25 @@ use std::{ time::Duration, }; -use adw::glib::{self, clone}; +use futures::channel::mpsc::UnboundedSender; +use glib::clone; use log::{debug, error, info, warn}; use pipewire::{ + channel::Receiver, + context::ContextRc, + core::{CoreRc, PW_ID_CORE}, keys, - link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState}, - node::{Node, NodeInfo, NodeListener}, - port::{Port, PortChangeMask, PortInfo, PortListener}, - prelude::*, - properties, - registry::{GlobalObject, Registry}, + link::{Link, LinkChangeMask, LinkInfoRef, LinkListener, LinkState}, + main_loop::MainLoopRc, + node::{Node, NodeInfoRef, NodeListener}, + port::{Port, PortChangeMask, PortInfoRef, PortListener}, + properties::properties, + registry::{GlobalObject, RegistryRc}, spa::{ param::{ParamInfoFlags, ParamType}, - ForeignDict, SpaResult, + utils::{dict::DictRef, result::SpaResult}, }, types::ObjectType, - Context, Core, MainLoop, }; use crate::{GtkMessage, MediaType, NodeType, PipewireMessage}; @@ -61,124 +65,181 @@ enum ProxyItem { /// The "main" function of the pipewire thread. pub(super) fn thread_main( - gtk_sender: glib::Sender, - mut pw_receiver: pipewire::channel::Receiver, + gtk_sender: UnboundedSender, + mut pw_receiver: Receiver, ) { - let mainloop = MainLoop::new().expect("Failed to create mainloop"); - let context = Rc::new(Context::new(&mainloop).expect("Failed to create context")); + let mainloop = MainLoopRc::new(None).expect("Failed to create mainloop"); + let context = ContextRc::new(&mainloop, None).expect("Failed to create context"); let is_stopped = Rc::new(Cell::new(false)); let mut is_connecting = false; while !is_stopped.get() { // Try to connect - let core = match context.connect(Some(properties! { + let Ok(core) = context.connect_rc(Some(properties! { "media.category" => "Manager" - })) { - Ok(core) => Rc::new(core), - Err(_) => { - if !is_connecting { - is_connecting = true; - gtk_sender - .send(PipewireMessage::Connecting) - .expect("Failed to send message"); - } - - // If connection is failed, try to connect again in 200ms - let interval = Some(Duration::from_millis(200)); - - let timer = mainloop.add_timer(clone!(@strong mainloop => move |_| { - mainloop.quit(); - })); - - timer.update_timer(interval, None).into_result().unwrap(); - - let receiver = pw_receiver.attach(&mainloop, { - clone!(@strong mainloop, @strong is_stopped => move |msg| - if let GtkMessage::Terminate = msg { - // main thread requested stop - is_stopped.set(true); - mainloop.quit(); - } - ) - }); - - mainloop.run(); - pw_receiver = receiver.deattach(); - - continue; + })) else { + if !is_connecting { + is_connecting = true; + gtk_sender + .unbounded_send(PipewireMessage::Connecting) + .expect("Failed to send message"); } + + // If connection is failed, try to connect again in 200ms + let interval = Some(Duration::from_millis(200)); + + let timer = mainloop.loop_().add_timer(clone!( + #[strong] + mainloop, + move |_| { + mainloop.quit(); + } + )); + + timer.update_timer(interval, None).into_result().unwrap(); + + let receiver = pw_receiver.attach(mainloop.loop_(), { + clone!( + #[strong] + mainloop, + #[strong] + is_stopped, + move |msg| if let GtkMessage::Terminate = msg { + // main thread requested stop + is_stopped.set(true); + mainloop.quit(); + } + ) + }); + + mainloop.run(); + pw_receiver = receiver.deattach(); + + continue; }; if is_connecting { is_connecting = false; gtk_sender - .send(PipewireMessage::Connected) + .unbounded_send(PipewireMessage::Connected) .expect("Failed to send message"); } - let registry = Rc::new(core.get_registry().expect("Failed to get registry")); + let registry = core.get_registry_rc().expect("Failed to get registry"); // Keep proxies and their listeners alive so that we can receive info events. let proxies = Rc::new(RefCell::new(HashMap::new())); let state = Rc::new(RefCell::new(State::new())); - let receiver = pw_receiver.attach(&mainloop, { - clone!(@strong mainloop, @weak core, @weak registry, @strong state, @strong is_stopped => move |msg| match msg { - GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, ®istry, &state), - GtkMessage::Terminate => { - // main thread requested stop - is_stopped.set(true); - mainloop.quit(); + let receiver = pw_receiver.attach(mainloop.loop_(), { + clone!( + #[strong] + mainloop, + #[strong] + core, + #[strong] + registry, + #[strong] + state, + #[strong] + is_stopped, + move |msg| match msg { + GtkMessage::ToggleLink { port_from, port_to } => + toggle_link(port_from, port_to, &core, ®istry, &state.borrow()), + GtkMessage::Terminate => { + // main thread requested stop + is_stopped.set(true); + mainloop.quit(); + } } - }) + ) }); let gtk_sender = gtk_sender.clone(); - let _listener = core.add_listener_local() - .error(clone!(@strong mainloop, @strong gtk_sender, @strong is_stopped => move |id, _seq, res, message| { - if id != pipewire::PW_ID_CORE { - return; - } + let _listener = core + .add_listener_local() + .error(clone!( + #[strong] + mainloop, + #[strong] + gtk_sender, + move |id, _seq, res, message| { + if id != PW_ID_CORE { + return; + } - if res == -libc::EPIPE { - gtk_sender.send(PipewireMessage::Disconnected) - .expect("Failed to send message"); - mainloop.quit(); - } else { - let serr = SpaResult::from_c(res).into_result().unwrap_err(); - error!("Pipewire Core received error {serr}: {message}"); + if res == -libc::EPIPE { + gtk_sender + .unbounded_send(PipewireMessage::Disconnected) + .expect("Failed to send message"); + mainloop.quit(); + } else { + let serr = SpaResult::from_c(res).into_result().unwrap_err(); + error!("Pipewire Core received error {serr}: {message}"); + } } - })) + )) .register(); let _listener = registry .add_listener_local() - .global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state => + .global(clone!( + #[strong] + gtk_sender, + #[strong] + registry, + #[strong] + proxies, + #[strong] + state, move |global| match global.type_ { - ObjectType::Node => handle_node(global, >k_sender, ®istry, &proxies, &state), - ObjectType::Port => handle_port(global, >k_sender, ®istry, &proxies, &state), - ObjectType::Link => handle_link(global, >k_sender, ®istry, &proxies, &state), + ObjectType::Node => handle_node( + global, + >k_sender, + ®istry, + &proxies, + &mut state.borrow_mut() + ), + ObjectType::Port => + handle_port(global, >k_sender, ®istry, &proxies, &state), + ObjectType::Link => handle_link( + global, + >k_sender, + ®istry, + &mut proxies.borrow_mut(), + &state + ), _ => { // Other objects are not interesting to us } } )) - .global_remove(clone!(@strong proxies, @strong state => move |id| { - if let Some(item) = state.borrow_mut().remove(id) { - gtk_sender.send(match item { - Item::Node { .. } => PipewireMessage::NodeRemoved {id}, - Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id}, - Item::Link { .. } => PipewireMessage::LinkRemoved {id}, - }).expect("Failed to send message"); - } else { - warn!( - "Attempted to remove item with id {} that is not saved in state", - id - ); - } + .global_remove(clone!( + #[strong] + proxies, + #[strong] + state, + move |id| { + if let Some(item) = state.borrow_mut().remove(id) { + gtk_sender + .unbounded_send(match item { + Item::Node => PipewireMessage::NodeRemoved { id }, + Item::Port { node_id } => { + PipewireMessage::PortRemoved { id, node_id } + } + Item::Link { .. } => PipewireMessage::LinkRemoved { id }, + }) + .expect("Failed to send message"); + } else { + warn!( + "Attempted to remove item with id {} that is not saved in state", + id + ); + } - proxies.borrow_mut().remove(&id); - })) + proxies.borrow_mut().remove(&id); + } + )) .register(); mainloop.run(); @@ -187,21 +248,22 @@ pub(super) fn thread_main( } /// Get the nicest possible name for the node, using a fallback chain of possible name attributes -fn get_node_name(props: &ForeignDict) -> &str { +fn get_node_name(props: &DictRef) -> String { props .get(&keys::NODE_DESCRIPTION) .or_else(|| props.get(&keys::NODE_NICK)) .or_else(|| props.get(&keys::NODE_NAME)) .unwrap_or_default() + .to_string() } /// Handle a new node being added fn handle_node( - node: &GlobalObject, - sender: &glib::Sender, - registry: &Rc, + node: &GlobalObject<&DictRef>, + sender: &UnboundedSender, + registry: &RegistryRc, proxies: &Rc>>, - state: &Rc>, + state: &mut State, ) { let props = node .props @@ -230,10 +292,10 @@ fn handle_node( }) .or_else(|| props.get("media.class").and_then(media_class)); - state.borrow_mut().insert(node.id, Item::Node); + state.insert(node.id, Item::Node); sender - .send(PipewireMessage::NodeAdded { + .unbounded_send(PipewireMessage::NodeAdded { id: node.id, name, node_type, @@ -243,9 +305,15 @@ fn handle_node( let proxy: Node = registry.bind(node).expect("Failed to bind to node proxy"); let listener = proxy .add_listener_local() - .info(clone!(@strong sender, @strong proxies => move |info| { - handle_node_info(info, &sender, &proxies); - })) + .info(clone!( + #[strong] + sender, + #[strong] + proxies, + move |info| { + handle_node_info(info, &sender, &proxies.borrow()); + } + )) .register(); proxies.borrow_mut().insert( @@ -258,14 +326,13 @@ fn handle_node( } fn handle_node_info( - info: &NodeInfo, - sender: &glib::Sender, - proxies: &Rc>>, + info: &NodeInfoRef, + sender: &UnboundedSender, + proxies: &HashMap, ) { debug!("Received node info: {:?}", info); let id = info.id(); - let proxies = proxies.borrow(); let Some(ProxyItem::Node { .. }) = proxies.get(&id) else { error!("Received info on unknown node with id {id}"); return; @@ -276,7 +343,7 @@ fn handle_node_info( let name = get_node_name(props).to_string(); sender - .send(PipewireMessage::NodeNameChanged { + .unbounded_send(PipewireMessage::NodeNameChanged { id, name, media_name: media_name.to_string(), @@ -287,9 +354,9 @@ fn handle_node_info( /// Handle a new port being added fn handle_port( - port: &GlobalObject, - sender: &glib::Sender, - registry: &Rc, + port: &GlobalObject<&DictRef>, + sender: &UnboundedSender, + registry: &RegistryRc, proxies: &Rc>>, state: &Rc>, ) { @@ -297,16 +364,26 @@ fn handle_port( let proxy: Port = registry.bind(port).expect("Failed to bind to port proxy"); let listener = proxy .add_listener_local() - .info( - clone!(@strong proxies, @strong state, @strong sender => move |info| { - handle_port_info(info, &proxies, &state, &sender); - }), - ) - .param(clone!(@strong sender => move |_, param_id, _, _, param| { - if param_id == ParamType::EnumFormat { - handle_port_enum_format(port_id, param, &sender) + .info(clone!( + #[strong] + proxies, + #[strong] + state, + #[strong] + sender, + move |info| { + handle_port_info(info, &proxies.borrow(), &mut state.borrow_mut(), &sender); } - })) + )) + .param(clone!( + #[strong] + sender, + move |_, param_id, _, _, param| { + if param_id == ParamType::EnumFormat { + handle_port_enum_format(port_id, param, &sender) + } + } + )) .register(); proxies.borrow_mut().insert( @@ -319,22 +396,19 @@ fn handle_port( } fn handle_port_info( - info: &PortInfo, - proxies: &Rc>>, - state: &Rc>, - sender: &glib::Sender, + info: &PortInfoRef, + proxies: &HashMap, + state: &mut State, + sender: &UnboundedSender, ) { debug!("Received port info: {:?}", info); let id = info.id(); - let proxies = proxies.borrow(); let Some(ProxyItem::Port { proxy, .. }) = proxies.get(&id) else { log::error!("Received info on unknown port with id {id}"); return; }; - let mut state = state.borrow_mut(); - if let Some(Item::Port { .. }) = state.get(id) { // Info was an update, figure out if we should notify the GTK thread if info.change_mask().contains(PortChangeMask::PARAMS) { @@ -356,14 +430,14 @@ fn handle_port_info( let enum_format_info = params .iter() .find(|param| param.id() == ParamType::EnumFormat); - if let Some(enum_format_info) = enum_format_info { - if enum_format_info.flags().contains(ParamInfoFlags::READ) { - proxy.enum_params(0, Some(ParamType::EnumFormat), 0, u32::MAX); - } + if let Some(enum_format_info) = enum_format_info + && enum_format_info.flags().contains(ParamInfoFlags::READ) + { + proxy.enum_params(0, Some(ParamType::EnumFormat), 0, u32::MAX); } sender - .send(PipewireMessage::PortAdded { + .unbounded_send(PipewireMessage::PortAdded { id, node_id, name, @@ -376,7 +450,7 @@ fn handle_port_info( fn handle_port_enum_format( port_id: u32, param: Option<&pipewire::spa::pod::Pod>, - sender: &glib::Sender, + sender: &UnboundedSender, ) { let media_type = param .and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok()) @@ -384,19 +458,19 @@ fn handle_port_enum_format( .unwrap_or(MediaType::Unknown); sender - .send(PipewireMessage::PortFormatChanged { + .unbounded_send(PipewireMessage::PortFormatChanged { id: port_id, media_type, }) - .expect("Failed to send message") + .expect("Failed to send message"); } /// Handle a new link being added fn handle_link( - link: &GlobalObject, - sender: &glib::Sender, - registry: &Rc, - proxies: &Rc>>, + link: &GlobalObject<&DictRef>, + sender: &UnboundedSender, + registry: &RegistryRc, + proxies: &mut HashMap, state: &Rc>, ) { debug!( @@ -407,12 +481,18 @@ fn handle_link( let proxy: Link = registry.bind(link).expect("Failed to bind to link proxy"); let listener = proxy .add_listener_local() - .info(clone!(@strong state, @strong sender => move |info| { - handle_link_info(info, &state, &sender); - })) + .info(clone!( + #[strong] + state, + #[strong] + sender, + move |info| { + handle_link_info(info, &mut state.borrow_mut(), &sender); + } + )) .register(); - proxies.borrow_mut().insert( + proxies.insert( link.id, ProxyItem::Link { _proxy: proxy, @@ -422,20 +502,19 @@ fn handle_link( } fn handle_link_info( - info: &LinkInfo, - state: &Rc>, - sender: &glib::Sender, + info: &LinkInfoRef, + state: &mut State, + sender: &UnboundedSender, ) { debug!("Received link info: {:?}", info); let id = info.id(); - let mut state = state.borrow_mut(); if let Some(Item::Link { .. }) = state.get(id) { // Info was an update - figure out if we should notify the gtk thread if info.change_mask().contains(LinkChangeMask::STATE) { sender - .send(PipewireMessage::LinkStateChanged { + .unbounded_send(PipewireMessage::LinkStateChanged { id, active: matches!(info.state(), LinkState::Active), }) @@ -443,7 +522,7 @@ fn handle_link_info( } if info.change_mask().contains(LinkChangeMask::FORMAT) { sender - .send(PipewireMessage::LinkFormatChanged { + .unbounded_send(PipewireMessage::LinkFormatChanged { id, media_type: get_link_media_type(info), }) @@ -457,7 +536,7 @@ fn handle_link_info( state.insert(id, Item::Link { port_from, port_to }); sender - .send(PipewireMessage::LinkAdded { + .unbounded_send(PipewireMessage::LinkAdded { id, port_from, port_to, @@ -469,14 +548,7 @@ fn handle_link_info( } /// Toggle a link between the two specified ports. -fn toggle_link( - port_from: u32, - port_to: u32, - core: &Rc, - registry: &Rc, - state: &Rc>, -) { - let state = state.borrow_mut(); +fn toggle_link(port_from: u32, port_to: u32, core: &CoreRc, registry: &RegistryRc, state: &State) { if let Some(id) = state.get_link_id(port_from, port_to) { info!("Requesting removal of link with id {}", id); @@ -495,7 +567,7 @@ fn toggle_link( .get_node_of_port(port_to) .expect("Requested port not in state"); - if let Err(e) = core.create_object::( + if let Err(e) = core.create_object::( "link-factory", &properties! { "link.output.node" => node_from.to_string(), @@ -510,12 +582,10 @@ fn toggle_link( } } -fn get_link_media_type(link_info: &LinkInfo) -> MediaType { - let media_type = link_info +fn get_link_media_type(link_info: &LinkInfoRef) -> MediaType { + link_info .format() .and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok()) .map(|(media_type, _media_subtype)| media_type) - .unwrap_or(MediaType::Unknown); - - media_type + .unwrap_or(MediaType::Unknown) } diff --git a/src/ui/graph/graph_view.rs b/src/ui/graph/graph_view.rs index 5825dae..bf2b173 100644 --- a/src/ui/graph/graph_view.rs +++ b/src/ui/graph/graph_view.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,19 +15,28 @@ // // SPDX-License-Identifier: GPL-3.0-only +use std::{ + cell::{Cell, RefCell}, + cmp::Ordering, + collections::{HashMap, HashSet}, + sync::LazyLock, +}; + +use pipewire::spa::utils::Direction; + use adw::{ gio, glib::{self, clone}, - gtk::{ - self, cairo, - graphene::{self, Point}, - gsk, - }, prelude::*, subclass::prelude::*, }; +use gtk::{ + self, cairo, gdk, + graphene::{self, Point, Rect}, + gsk, +}; -use std::cmp::Ordering; +use log::warn; use super::{Link, Node, Port}; use crate::NodeType; @@ -36,33 +46,6 @@ const CANVAS_SIZE: f64 = 5000.0; mod imp { use super::*; - use std::cell::{Cell, RefCell}; - use std::collections::{HashMap, HashSet}; - - use adw::gtk::gdk::{self}; - use log::warn; - use once_cell::sync::Lazy; - use pipewire::spa::format::MediaType; - use pipewire::spa::Direction; - - pub struct Colors { - audio: gdk::RGBA, - video: gdk::RGBA, - midi: gdk::RGBA, - unknown: gdk::RGBA, - } - - impl Colors { - pub fn color_for_media_type(&self, media_type: MediaType) -> &gdk::RGBA { - match media_type { - MediaType::Audio => &self.audio, - MediaType::Video => &self.video, - MediaType::Stream | MediaType::Application => &self.midi, - _ => &self.unknown, - } - } - } - pub struct DragState { node: glib::WeakRef, /// This stores the offset of the pointer to the origin of the node, @@ -151,7 +134,7 @@ mod imp { } fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { + static PROPERTIES: LazyLock> = LazyLock::new(|| { vec![ glib::ParamSpecOverride::for_interface::("hadjustment"), glib::ParamSpecOverride::for_interface::("vadjustment"), @@ -228,14 +211,19 @@ mod imp { fn snapshot(&self, snapshot: >k::Snapshot) { let widget = &*self.obj(); - let alloc = widget.allocation(); + + let widget_rect = Rect::new(0.0, 0.0, widget.width() as f32, widget.height() as f32); // Draw all visible children self.nodes .borrow() .iter() // Cull nodes from rendering when they are outside the visible canvas area - .filter(|(node, _)| alloc.intersect(&node.allocation()).is_some()) + .filter(|(node, _)| { + widget_rect + .intersection(&node.compute_bounds(widget).unwrap()) + .is_some() + }) .for_each(|(node, _)| widget.snapshot_child(node, snapshot)); self.snapshot_links(widget, snapshot); @@ -276,6 +264,7 @@ mod imp { drag_controller.connect_drag_begin(|drag_controller, x, y| { let widget = drag_controller .widget() + .unwrap() .dynamic_cast::() .expect("drag-begin event is not on the GraphView"); let mut dragged_node = widget.imp().dragged_node.borrow_mut(); @@ -315,6 +304,7 @@ mod imp { drag_controller.connect_drag_update(|drag_controller, x, y| { let widget = drag_controller .widget() + .unwrap() .dynamic_cast::() .expect("drag-update event is not on the GraphView"); let dragged_node = widget.imp().dragged_node.borrow(); @@ -348,6 +338,7 @@ mod imp { controller.connect_enter(|controller, x, y| { let graph = controller .widget() + .unwrap() .downcast::() .expect("Widget should be a graphview"); @@ -357,6 +348,7 @@ mod imp { controller.connect_motion(|controller, x, y| { let graph = controller .widget() + .unwrap() .downcast::() .expect("Widget should be a graphview"); @@ -366,6 +358,7 @@ mod imp { controller.connect_leave(|controller| { let graph = controller .widget() + .unwrap() .downcast::() .expect("Widget should be a graphview"); @@ -386,14 +379,18 @@ mod imp { Port::static_type(), glib::Priority::DEFAULT, Option::<&gio::Cancellable>::None, - clone!(@weak self as imp => move|value| { - let Ok(value) = value else { - return; - }; - let port: &Port = value.get().expect("Value should contain a port"); + clone!( + #[weak(rename_to = imp)] + self, + move |value| { + let Ok(value) = value else { + return; + }; + let port: &Port = value.get().expect("Value should contain a port"); - imp.dragged_port.set(Some(port)); - }), + imp.dragged_port.set(Some(port)); + } + ), ); self.obj().queue_draw(); @@ -430,6 +427,7 @@ mod imp { { let widget = eventcontroller .widget() + .unwrap() .downcast::() .unwrap(); widget.set_zoom_factor(widget.zoom_factor() + (0.1 * -delta_y), None); @@ -445,7 +443,11 @@ mod imp { fn setup_zoom_gesture(&self) { let zoom_gesture = gtk::GestureZoom::new(); zoom_gesture.connect_begin(|gesture, _| { - let widget = gesture.widget().downcast::().unwrap(); + let widget = gesture + .widget() + .unwrap() + .downcast::() + .unwrap(); widget .imp() @@ -457,7 +459,11 @@ mod imp { .set(gesture.bounding_box_center()); }); zoom_gesture.connect_scale_changed(move |gesture, delta| { - let widget = gesture.widget().downcast::().unwrap(); + let widget = gesture + .widget() + .unwrap() + .downcast::() + .unwrap(); let initial_zoom = widget .imp() @@ -480,6 +486,7 @@ mod imp { drag_controller.connect_drag_begin(|drag_controller, _, _| { let widget = drag_controller .widget() + .unwrap() .downcast::() .unwrap(); @@ -489,6 +496,7 @@ mod imp { drag_controller.connect_drag_update(|drag_controller, x, y| { let widget = drag_controller .widget() + .unwrap() .downcast::() .unwrap(); @@ -518,7 +526,7 @@ mod imp { output_anchor: &Point, input_anchor: &Point, active: bool, - color: &gdk::RGBA, + color: gdk::RGBA, ) { let output_x: f64 = output_anchor.x().into(); let output_y: f64 = output_anchor.y().into(); @@ -567,7 +575,7 @@ mod imp { }; } - fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context, colors: &Colors) { + fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context) { let Some(port_anchor) = port.compute_point(&*self.obj(), &port.link_anchor()) else { return; }; @@ -595,51 +603,34 @@ mod imp { _ => unreachable!(), }; - let color = &colors.color_for_media_type(MediaType::from_raw(port.media_type())); - - self.draw_link(link_cr, output_anchor, input_anchor, false, color); + self.draw_link( + link_cr, + output_anchor, + input_anchor, + false, + port.get_handle().color(), + ); } fn snapshot_links(&self, widget: &super::GraphView, snapshot: >k::Snapshot) { - let alloc = widget.allocation(); - let link_cr = snapshot.append_cairo(&graphene::Rect::new( 0.0, 0.0, - alloc.width() as f32, - alloc.height() as f32, + widget.width() as f32, + widget.height() as f32, )); link_cr.set_line_width(2.0 * self.zoom_factor.get()); - let colors = Colors { - audio: widget - .style_context() - .lookup_color("media-type-audio") - .expect("color not found"), - video: widget - .style_context() - .lookup_color("media-type-video") - .expect("color not found"), - midi: widget - .style_context() - .lookup_color("media-type-midi") - .expect("color not found"), - unknown: widget - .style_context() - .lookup_color("media-type-unknown") - .expect("color not found"), - }; - for link in self.links.borrow().iter() { - let color = &colors.color_for_media_type(link.media_type()); - // TODO: Do not draw links when they are outside the view let Some((output_anchor, input_anchor)) = self.get_link_coordinates(link) else { warn!("Could not get allocation of ports of link: {:?}", link); continue; }; + let color = link.output_port().unwrap().get_handle().color(); + self.draw_link( &link_cr, &output_anchor, @@ -650,7 +641,7 @@ mod imp { } if let Some(port) = self.dragged_port.upgrade() { - self.draw_dragged_link(&port, &link_cr, &colors); + self.draw_dragged_link(&port, &link_cr); } } @@ -688,8 +679,11 @@ mod imp { } if let Some(adjustment) = adjustment { - adjustment - .connect_value_changed(clone!(@weak obj => move |_| obj.queue_allocate() )); + adjustment.connect_value_changed(clone!( + #[weak] + obj, + move |_| obj.queue_allocate() + )); } } @@ -720,7 +714,8 @@ mod imp { glib::wrapper! { pub struct GraphView(ObjectSubclass) - @extends gtk::Widget; + @extends gtk::Widget, + @implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable, gtk::Scrollable; } impl GraphView { @@ -748,12 +743,8 @@ impl GraphView { pub fn set_zoom_factor(&self, zoom_factor: f64, anchor: Option<(f64, f64)>) { let zoom_factor = zoom_factor.clamp(Self::ZOOM_MIN, Self::ZOOM_MAX); - let (anchor_x_screen, anchor_y_screen) = anchor.unwrap_or_else(|| { - ( - self.allocation().width() as f64 / 2.0, - self.allocation().height() as f64 / 2.0, - ) - }); + let (anchor_x_screen, anchor_y_screen) = + anchor.unwrap_or_else(|| (self.width() as f64 / 2.0, self.height() as f64 / 2.0)); let old_zoom = self.imp().zoom_factor.get(); let hadjustment_ref = self.imp().hadjustment.borrow(); @@ -822,15 +813,23 @@ impl GraphView { pub fn add_link(&self, link: Link) { link.connect_notify_local( Some("active"), - glib::clone!(@weak self as graph => move |_, _| { - graph.queue_draw(); - }), + glib::clone!( + #[weak(rename_to = graph)] + self, + move |_, _| { + graph.queue_draw(); + } + ), ); link.connect_notify_local( Some("media-type"), - glib::clone!(@weak self as graph => move |_, _| { - graph.queue_draw(); - }), + glib::clone!( + #[weak(rename_to = graph)] + self, + move |_, _| { + graph.queue_draw(); + } + ), ); self.imp().links.borrow_mut().insert(link); self.queue_draw(); diff --git a/src/ui/graph/link.rs b/src/ui/graph/link.rs index 140c74c..0e2bfde 100644 --- a/src/ui/graph/link.rs +++ b/src/ui/graph/link.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,18 +15,16 @@ // // SPDX-License-Identifier: GPL-3.0-only +use pipewire::spa::param::format::MediaType; +use std::{cell::Cell, sync::LazyLock}; + use adw::{glib, prelude::*, subclass::prelude::*}; -use pipewire::spa::format::MediaType; use super::Port; mod imp { use super::*; - use std::cell::Cell; - - use once_cell::sync::Lazy; - pub struct Link { pub output_port: glib::WeakRef, pub input_port: glib::WeakRef, @@ -53,7 +52,7 @@ mod imp { impl ObjectImpl for Link { fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { + static PROPERTIES: LazyLock> = LazyLock::new(|| { vec![ glib::ParamSpecObject::builder::("output-port") .flags(glib::ParamFlags::READWRITE) diff --git a/src/ui/graph/node.rs b/src/ui/graph/node.rs index a3f1afb..8a7ee28 100644 --- a/src/ui/graph/node.rs +++ b/src/ui/graph/node.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,22 +15,22 @@ // // SPDX-License-Identifier: GPL-3.0-only +use pipewire::spa::utils::Direction; +use std::{ + cell::{Cell, RefCell}, + collections::HashSet, +}; + use adw::{glib, gtk, prelude::*, subclass::prelude::*}; -use pipewire::spa::Direction; use super::Port; mod imp { use super::*; - use std::{ - cell::{Cell, RefCell}, - collections::HashSet, - }; - #[derive(glib::Properties, gtk::CompositeTemplate, Default)] #[properties(wrapper_type = super::Node)] - #[template(file = "node.ui")] + #[template(resource = "/org/pipewire/Helvum/ui/node.ui")] pub struct Node { #[property(get, set, construct_only)] pub(super) pipewire_id: Cell, @@ -149,7 +150,8 @@ mod imp { glib::wrapper! { pub struct Node(ObjectSubclass) - @extends gtk::Widget; + @extends gtk::Widget, + @implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable; } impl Node { diff --git a/src/ui/graph/node.ui b/src/ui/graph/node.ui deleted file mode 100644 index 9a80c56..0000000 --- a/src/ui/graph/node.ui +++ /dev/null @@ -1,56 +0,0 @@ - - - - - diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index 13a5d9b..76efafd 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -14,6 +15,12 @@ // // SPDX-License-Identifier: GPL-3.0-only +use pipewire::spa::{param::format::MediaType, utils::Direction}; +use std::{ + cell::{Cell, OnceCell}, + sync::LazyLock, +}; + use adw::{ gdk, glib::{self, subclass::Signal}, @@ -21,22 +28,16 @@ use adw::{ prelude::*, subclass::prelude::*, }; -use pipewire::spa::Direction; use super::PortHandle; mod imp { use super::*; - use std::cell::Cell; - - use once_cell::{sync::Lazy, unsync::OnceCell}; - use pipewire::spa::{format::MediaType, Direction}; - /// Graphical representation of a pipewire port. #[derive(gtk::CompositeTemplate, glib::Properties)] #[properties(wrapper_type = super::Port)] - #[template(file = "port.ui")] + #[template(resource = "/org/pipewire/Helvum/ui/port.ui")] pub struct Port { #[property(get, set, construct_only)] pub(super) pipewire_id: OnceCell, @@ -112,11 +113,13 @@ mod imp { } fn signals() -> &'static [Signal] { - static SIGNALS: Lazy> = Lazy::new(|| { - vec![Signal::builder("port-toggled") - // Provide id of output port and input port to signal handler. - .param_types([::static_type(), ::static_type()]) - .build()] + static SIGNALS: LazyLock> = LazyLock::new(|| { + vec![ + Signal::builder("port-toggled") + // Provide id of output port and input port to signal handler. + .param_types([::static_type(), ::static_type()]) + .build(), + ] }); SIGNALS.as_ref() @@ -218,6 +221,7 @@ mod imp { drag_src.connect_drag_begin(|drag_source, _| { let port = drag_source .widget() + .unwrap() .dynamic_cast::() .expect("Widget should be a Port"); @@ -226,6 +230,7 @@ mod imp { drag_src.connect_drag_cancel(|drag_source, _, _| { let port = drag_source .widget() + .unwrap() .dynamic_cast::() .expect("Widget should be a Port"); @@ -241,6 +246,7 @@ mod imp { drop_target.connect_value_notify(|drop_target| { let port = drop_target .widget() + .unwrap() .dynamic_cast::() .expect("Widget should be a Port"); @@ -260,6 +266,7 @@ mod imp { drop_target.connect_drop(|drop_target, val, _, _| { let port = drop_target .widget() + .unwrap() .dynamic_cast::() .expect("Widget should be a Port"); let other_port = val @@ -328,7 +335,8 @@ mod imp { glib::wrapper! { pub struct Port(ObjectSubclass) - @extends gtk::Widget; + @extends gtk::Widget, + @implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable; } impl Port { @@ -341,17 +349,11 @@ impl Port { } pub fn link_anchor(&self) -> graphene::Point { - let style_context = self.style_context(); - let padding_right: f32 = style_context.padding().right().into(); - let border_right: f32 = style_context.border().right().into(); - let padding_left: f32 = style_context.padding().left().into(); - let border_left: f32 = style_context.border().left().into(); - let direction = Direction::from_raw(self.direction()); graphene::Point::new( match direction { - Direction::Output => self.width() as f32 + padding_right + border_right, - Direction::Input => 0.0 - padding_left - border_left, + Direction::Output => self.width() as f32, + Direction::Input => 0.0, _ => unreachable!(), }, self.height() as f32 / 2.0, @@ -361,4 +363,8 @@ impl Port { pub fn is_linkable_to(&self, other_port: &Self) -> bool { self.direction() != other_port.direction() } + + pub fn get_handle(&self) -> &PortHandle { + &self.imp().handle + } } diff --git a/src/ui/graph/port.ui b/src/ui/graph/port.ui deleted file mode 100644 index 13722ca..0000000 --- a/src/ui/graph/port.ui +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/src/ui/graph/port_handle.rs b/src/ui/graph/port_handle.rs index a9ca8cc..e360c96 100644 --- a/src/ui/graph/port_handle.rs +++ b/src/ui/graph/port_handle.rs @@ -1,4 +1,5 @@ // Copyright 2021 Tom A. Wagner +// Copyright 2026 Jaŭhien Lavonćjeŭ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 as published by @@ -61,7 +62,8 @@ mod imp { glib::wrapper! { pub struct PortHandle(ObjectSubclass) - @extends gtk::Widget; + @extends gtk::Widget, + @implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable; } impl PortHandle { diff --git a/src/ui/graph/zoomentry.rs b/src/ui/graph/zoomentry.rs index 667236b..1b618cb 100644 --- a/src/ui/graph/zoomentry.rs +++ b/src/ui/graph/zoomentry.rs @@ -1,17 +1,20 @@ -use adw::{glib, gtk, prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, sync::LazyLock}; + +use adw::{ + gio, + glib::{self, clone}, + gtk, + prelude::*, + subclass::prelude::*, +}; use super::GraphView; mod imp { - use std::cell::RefCell; - use super::*; - use gtk::{gio, glib::clone}; - use once_cell::sync::Lazy; - #[derive(gtk::CompositeTemplate)] - #[template(file = "zoomentry.ui")] + #[template(resource = "/org/pipewire/Helvum/ui/zoomentry.ui")] pub struct ZoomEntry { pub graphview: RefCell>, #[template_child] @@ -65,37 +68,49 @@ mod imp { fn constructed(&self) { self.parent_constructed(); - self.zoom_out_button - .connect_clicked(clone!(@weak self as imp => move |_| { + self.zoom_out_button.connect_clicked(clone!( + #[weak(rename_to = imp)] + self, + move |_| { let graphview = imp.graphview.borrow(); if let Some(ref graphview) = *graphview { graphview.set_zoom_factor(graphview.zoom_factor() - 0.1, None); } - })); + } + )); - self.zoom_in_button - .connect_clicked(clone!(@weak self as imp => move |_| { + self.zoom_in_button.connect_clicked(clone!( + #[weak(rename_to = imp)] + self, + move |_| { let graphview = imp.graphview.borrow(); if let Some(ref graphview) = *graphview { graphview.set_zoom_factor(graphview.zoom_factor() + 0.1, None); } - })); + } + )); - self.entry - .connect_activate(clone!(@weak self as imp => move |entry| { + self.entry.connect_activate(clone!( + #[weak(rename_to = imp)] + self, + move |entry| { if let Ok(zoom_factor) = entry.text().trim_matches('%').parse::() { let graphview = imp.graphview.borrow(); if let Some(ref graphview) = *graphview { graphview.set_zoom_factor(zoom_factor / 100.0, None); } } - })); - self.entry - .connect_icon_press(clone!(@weak self as imp => move |_, pos| { + } + )); + self.entry.connect_icon_press(clone!( + #[weak(rename_to = imp)] + self, + move |_, pos| { if pos == gtk::EntryIconPosition::Secondary { - imp.popover.show(); + imp.popover.popup(); } - })); + } + )); self.popover.set_parent(&self.entry.get()); } @@ -109,10 +124,12 @@ mod imp { } fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecObject::builder::("zoomed-widget") - .flags(glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT) - .build()] + static PROPERTIES: LazyLock> = LazyLock::new(|| { + vec![ + glib::ParamSpecObject::builder::("zoomed-widget") + .flags(glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT) + .build(), + ] }); PROPERTIES.as_ref() @@ -132,9 +149,13 @@ mod imp { if let Some(ref widget) = widget { widget.connect_notify_local( Some("zoom-factor"), - clone!(@weak self as imp => move |graphview, _| { - imp.update_zoom_factor_text(graphview.zoom_factor()); - }), + clone!( + #[weak(rename_to = imp)] + self, + move |graphview, _| { + imp.update_zoom_factor_text(graphview.zoom_factor()); + } + ), ); self.update_zoom_factor_text(widget.zoom_factor()); } @@ -161,7 +182,8 @@ mod imp { glib::wrapper! { pub struct ZoomEntry(ObjectSubclass) - @extends gtk::Box, gtk::Widget; + @extends gtk::Box, gtk::Widget, + @implements gtk::Accessible, gtk::ConstraintTarget, gtk::Buildable; } impl ZoomEntry { diff --git a/src/ui/graph/zoomentry.ui b/src/ui/graph/zoomentry.ui deleted file mode 100644 index def1a6a..0000000 --- a/src/ui/graph/zoomentry.ui +++ /dev/null @@ -1,37 +0,0 @@ - - - - diff --git a/src/ui/window.rs b/src/ui/window.rs index e1e410b..c9529a6 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -7,7 +7,7 @@ mod imp { #[derive(Default, gtk::CompositeTemplate, glib::Properties)] #[properties(wrapper_type = super::Window)] - #[template(file = "window.ui")] + #[template(resource = "/org/pipewire/Helvum/ui/window.ui")] pub struct Window { #[template_child] pub header_bar: TemplateChild, @@ -49,7 +49,8 @@ mod imp { glib::wrapper! { pub struct Window(ObjectSubclass) @extends adw::ApplicationWindow, gtk::ApplicationWindow, gtk::Window, gtk::Widget, - @implements gio::ActionGroup, gio::ActionMap; + @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, + gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; } impl Window { diff --git a/src/ui/window.ui b/src/ui/window.ui deleted file mode 100644 index c1df524..0000000 --- a/src/ui/window.ui +++ /dev/null @@ -1,66 +0,0 @@ - - - - - -
- - _About Helvum - app.about - -
-
- -