mirror of
https://gitlab.freedesktop.org/pipewire/helvum.git
synced 2026-04-17 21:10:39 +02:00
Merge branch 'dynamic-config' into 'main'
add TOML configuration for node layout and remove hardcoded node height See merge request pipewire/helvum!76
This commit is contained in:
commit
103f09d8a8
19 changed files with 1235 additions and 283 deletions
|
|
@ -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-46'
|
||||
variables:
|
||||
FLATPAK_BUILD_DIR: _build
|
||||
MANIFEST_PATH: build-aux/org.pipewire.Helvum.json
|
||||
|
|
|
|||
548
Cargo.lock
generated
548
Cargo.lock
generated
|
|
@ -11,12 +11,35 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"yansi-term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
|
@ -25,16 +48,17 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.66.1"
|
||||
version = "0.69.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"annotate-snippets",
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
|
@ -43,12 +67,6 @@ dependencies = [
|
|||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
|
|
@ -57,23 +75,22 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
|||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.18.2"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c0466dfa8c0ee78deef390c274ad756801e0a6dbb86c5ef0924a298c5761c4d"
|
||||
checksum = "2650f66005301bd33cc486dec076e1293c4cecf768bc7ba9bf5d2b1be339b99c"
|
||||
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.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
|
||||
checksum = "fd3bb3119664efbd78b5e6c93957447944f16bdbced84c17a9f41c7829b81e64"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -125,6 +142,15 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
|
|
@ -140,19 +166,73 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "field-offset"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
|
||||
dependencies = [
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
|
|
@ -221,22 +301,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf"
|
||||
version = "0.18.0"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbc9c2ed73a81d556b65d08879ba4ee58808a6b1927ce915262185d6d547c6f3"
|
||||
checksum = "f6a23f8a0b5090494fd04924662d463f8386cc678dd3915015a838c1a3679b92"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
|
||||
checksum = "3dcbd04c1b2c4834cc008b4828bc917d062483b88d26effde6342e5622028f96"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
|
|
@ -247,9 +326,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6"
|
||||
checksum = "9100b25604183f2fd97f55ef087fae96ab4934d7215118a35303e422688e6e4b"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk-pixbuf",
|
||||
|
|
@ -262,9 +341,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.7.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0"
|
||||
checksum = "d0b76874c40bb8d1c7d03a7231e23ac75fa577a456cd53af32ec17ec8f121626"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -278,10 +357,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.18.2"
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57052f84e8e5999b258e8adf8f5f2af0ac69033864936b8b6838321db2f759b1"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64947d08d7fbb03bf8ad1f25a8ac6cf4329bc772c9b7e5abe7bf9493c81194f"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -290,7 +380,6 @@ dependencies = [
|
|||
"gio-sys",
|
||||
"glib",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
|
|
@ -298,24 +387,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
|
||||
checksum = "bcf8e1d9219bb294636753d307b030c1e8a032062cba74f493c431a5c8b81ce4"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.18.2"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19"
|
||||
checksum = "01e191cc1af1f35b9699213107068cd3fe05d9816275ac118dc785a0dd8faebf"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
|
|
@ -328,20 +417,18 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.18.2"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8da903822b136d42360518653fcf154455defc437d3e7a81475bf9a95ff1e47"
|
||||
checksum = "9972bb91643d589c889654693a4f1d07697fdcb5d104b5c44fb68649ba1bf68d"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
|
|
@ -349,9 +436,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
|
||||
checksum = "630f097773d7c7a0bb3258df4e8157b47dc98bbfa0e60ad9ab56174813feced4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
|
|
@ -365,9 +452,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
|
||||
checksum = "c85e2b1080b9418dd0c58b498da3a5c826030343e0ef07bde6a955d28de54979"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -376,9 +463,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.18.1"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401"
|
||||
checksum = "99e4d388e96c5f29e2b2f67045d229ddf826d0a8d6d282f94ed3b34452222c91"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
|
|
@ -387,9 +474,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59"
|
||||
checksum = "236ed66cc9b18d8adf233716f75de803d0bf6fc806f60d14d948974a12e240d0"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
|
@ -399,9 +486,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e"
|
||||
checksum = "c65036fc8f99579e8cb37b12487969b707ab23ec8ab953682ff347cbd15d396e"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
|
|
@ -414,9 +501,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55"
|
||||
checksum = "bd24c814379f9c3199dc53e52253ee8d0f657eae389ab282c330505289d24738"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
|
|
@ -430,9 +517,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842"
|
||||
checksum = "aa82753b8c26277e4af1446c70e35b19aad4fb794a7b143859e7eeb9a4025d83"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"field-offset",
|
||||
|
|
@ -451,9 +538,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-macros"
|
||||
version = "0.7.2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f"
|
||||
checksum = "40300bf071d2fcd4c94eacc09e84ec6fe73129d2ceb635cf7e55b026b5443567"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro-crate",
|
||||
|
|
@ -465,9 +552,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.7.3"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8"
|
||||
checksum = "0db1b104138f087ccdc81d2c332de5dd049b89de3d384437cc1093b17cd2da18"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -494,16 +581,27 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "helvum"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"dirs",
|
||||
"glib",
|
||||
"libadwaita",
|
||||
"libc",
|
||||
"log",
|
||||
"natord",
|
||||
"once_cell",
|
||||
"pipewire",
|
||||
"serde",
|
||||
"toml 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -516,6 +614,15 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
|
@ -530,9 +637,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libadwaita"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c"
|
||||
checksum = "91b4990248b9e1ec5e72094a2ccaea70ec3809f88f6fd52192f2af306b87c5d9"
|
||||
dependencies = [
|
||||
"gdk-pixbuf",
|
||||
"gdk4",
|
||||
|
|
@ -546,9 +653,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libadwaita-sys"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a"
|
||||
checksum = "23a748e4e92be1265cd9e93d569c0b5dfc7814107985aa6743d670ab281ea1a8"
|
||||
dependencies = [
|
||||
"gdk4-sys",
|
||||
"gio-sys",
|
||||
|
|
@ -562,9 +669,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
|
@ -577,12 +684,22 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.7.2"
|
||||
name = "libredox"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"convert_case",
|
||||
"cookie-factory",
|
||||
|
|
@ -595,9 +712,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345"
|
||||
checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
|
|
@ -612,18 +729,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
|
|
@ -641,16 +749,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
name = "natord"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -665,28 +777,33 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.18.0"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06a9e54b831d033206160096b825f2070cf5fda7e35167b1c01e9e774f9202d1"
|
||||
checksum = "b1264d13deb823cc652f26cfe59afb1ec4b9db2a5bd27c41b738c879cc1bfaa1"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"pango-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
|
||||
checksum = "f52ef6a881c19fbfe3b1484df5cad411acaaba29dbec843941c3110d19f340ea"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
|
@ -695,10 +812,10 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
|
|
@ -714,12 +831,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9"
|
||||
checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
|
|
@ -731,9 +848,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64"
|
||||
checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
|
|
@ -742,18 +859,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit",
|
||||
"toml_edit 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -798,6 +914,17 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.5"
|
||||
|
|
@ -894,9 +1021,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
|
|
@ -927,9 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
|
||||
dependencies = [
|
||||
"cfg-expr",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 0.7.8",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
|
|
@ -968,14 +1095,26 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -993,6 +1132,30 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
|
|
@ -1005,6 +1168,12 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
|
|
@ -1017,6 +1186,12 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
@ -1039,6 +1214,138 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.4",
|
||||
"windows_aarch64_msvc 0.52.4",
|
||||
"windows_i686_gnu 0.52.4",
|
||||
"windows_i686_msvc 0.52.4",
|
||||
"windows_x86_64_gnu 0.52.4",
|
||||
"windows_x86_64_gnullvm 0.52.4",
|
||||
"windows_x86_64_msvc 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.15"
|
||||
|
|
@ -1047,3 +1354,12 @@ checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
|
|||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi-term"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
|
|
|||
13
Cargo.toml
13
Cargo.toml
|
|
@ -14,12 +14,17 @@ 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"] }
|
||||
pipewire = "0.8.0"
|
||||
adw = { version = "0.6", package = "libadwaita", features = ["v1_4"] }
|
||||
glib = { version = "0.19", features = ["log"] }
|
||||
async-channel = "2.2"
|
||||
|
||||
log = "0.4.11"
|
||||
|
||||
once_cell = "1.7.2"
|
||||
once_cell = "1.19"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
dirs = "5.0"
|
||||
|
||||
libc = "0.2"
|
||||
natord = "1.0.9"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ $ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.fl
|
|||
|
||||
Then install the required flatpak platform and SDK, if you dont have them already:
|
||||
```shell
|
||||
$ flatpak install org.gnome.{Platform,Sdk}//45 org.freedesktop.Sdk.Extension.rust-stable//23.08 org.freedesktop.Sdk.Extension.llvm16//23.08
|
||||
$ flatpak install org.gnome.{Platform,Sdk}//46 org.freedesktop.Sdk.Extension.rust-stable//23.08 org.freedesktop.Sdk.Extension.llvm16//23.08
|
||||
```
|
||||
|
||||
To compile and install as a flatpak, clone the project, change to the project directory, and run:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "org.pipewire.Helvum",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "45",
|
||||
"runtime-version": "46",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"sdk-extensions": [
|
||||
"org.freedesktop.Sdk.Extension.rust-stable",
|
||||
|
|
|
|||
21
docs/example_layout.toml
Normal file
21
docs/example_layout.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Example Helvum Configuration
|
||||
|
||||
[layout]
|
||||
columns = [
|
||||
{ x = 20, name = "sources" },
|
||||
{ x = 500, name = "filters" },
|
||||
{ x = 950, name = "apps" },
|
||||
{ x = 1200, name = "sinks" }
|
||||
]
|
||||
|
||||
rules = [
|
||||
{ pattern = "Firefox*", column = "sources" },
|
||||
{ pattern = "vlc*", column = "sources" },
|
||||
{ pattern = "Built-in*", column = "sources", node_type = "source" },
|
||||
{ pattern = "Built-in*", column = "sinks", node_type = "sink" },
|
||||
]
|
||||
|
||||
[layout.defaults]
|
||||
source = 20
|
||||
other = 500
|
||||
sink = 1200
|
||||
76
docs/layout_configuration.md
Normal file
76
docs/layout_configuration.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Layout Configuration
|
||||
|
||||
This feature allows you to customize node placement in Helvum using a configuration file with column-based layout and pattern matching.
|
||||
|
||||
## Configuration File Format
|
||||
|
||||
The configuration uses TOML format with three main sections:
|
||||
|
||||
### 1. Columns Section
|
||||
Define columns with their X positions:
|
||||
```toml
|
||||
[layout]
|
||||
columns = [
|
||||
{ x = 50.0, name = "sources" },
|
||||
{ x = 300.0, name = "filters" },
|
||||
{ x = 550.0, name = "apps" },
|
||||
{ x = 800.0, name = "sinks" }
|
||||
]
|
||||
```
|
||||
|
||||
### 2. Rules Section
|
||||
Pattern matching rules (processed in order, first match wins):
|
||||
```toml
|
||||
[[layout.rules]]
|
||||
pattern = "Firefox*" # Simple glob patterns
|
||||
column = "apps"
|
||||
node_type = "filter" # Optional: restrict to specific node types
|
||||
|
||||
[[layout.rules]]
|
||||
pattern = "*Microphone*"
|
||||
column = "sources"
|
||||
node_type = "source" # Optional: only match source nodes
|
||||
|
||||
[[layout.rules]]
|
||||
pattern = "*sink*" # Case-insensitive matching
|
||||
column = "sinks"
|
||||
```
|
||||
|
||||
**Supported Patterns:**
|
||||
- `*` - matches any number of characters
|
||||
- `?` - matches exactly one character
|
||||
- Case-insensitive matching
|
||||
|
||||
**Node Type Constraints (optional):**
|
||||
- `"source"` - matches NodeType::Output nodes only
|
||||
- `"sink"` - matches NodeType::Input nodes only
|
||||
- `"filter"` - matches nodes that are neither input nor output
|
||||
|
||||
### 3. Defaults Section
|
||||
Default column positions when no pattern matches:
|
||||
```toml
|
||||
[layout.defaults]
|
||||
sink = 800.0
|
||||
source = 50.0
|
||||
other = 300.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Locations
|
||||
The application will automatically look for configuration files in this order:
|
||||
1. `./layout.toml` (current working directory where helvum is launched)
|
||||
2. `~/.config/helvum/layout.toml` (user config directory)
|
||||
|
||||
|
||||
## Example Configuration
|
||||
|
||||
See `example_layout.toml` for a complete example with common audio application patterns.
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
- Layout must contain at least one column
|
||||
- Layout must contain at least one rule
|
||||
- All rule column references must exist in the columns list
|
||||
- Columns must be spaced at least `COLUMN_WIDTH` pixels apart to prevent overlap
|
||||
- Invalid configurations will log warnings and fall back to original behavior
|
||||
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
use adw::{
|
||||
gio,
|
||||
glib::{self, clone, Receiver},
|
||||
glib::{self, clone},
|
||||
gtk,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use log::error;
|
||||
use pipewire::channel::Sender;
|
||||
|
||||
use crate::{graph_manager::GraphManager, ui, GtkMessage, PipewireMessage};
|
||||
|
|
@ -30,11 +31,14 @@ static APP_ID: &str = "org.pipewire.Helvum";
|
|||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
|
||||
const DEFAULT_REMOTE_NAME: &str = "Default Remote";
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use adw::subclass::prelude::AdwApplicationImpl;
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Application {
|
||||
|
|
@ -130,6 +134,49 @@ mod imp {
|
|||
|
||||
about_window.present();
|
||||
}
|
||||
|
||||
pub(super) fn setup_options(&self, pw_sender: Sender<GtkMessage>) {
|
||||
let obj = &*self.obj();
|
||||
|
||||
obj.add_main_option(
|
||||
"socket",
|
||||
glib::char::Char::from(b's'),
|
||||
glib::OptionFlags::NONE,
|
||||
glib::OptionArg::String,
|
||||
"PipeWire socket to connect",
|
||||
Some("PATH"),
|
||||
);
|
||||
|
||||
obj.add_main_option(
|
||||
"config",
|
||||
glib::char::Char::from(b'c'),
|
||||
glib::OptionFlags::NONE,
|
||||
glib::OptionArg::String,
|
||||
"Configuration file to use",
|
||||
Some("PATH"),
|
||||
);
|
||||
|
||||
let current_remote_label = obj.imp().window.current_remote_label();
|
||||
let graph_view = obj.imp().window.graph();
|
||||
obj.connect_handle_local_options(clone!(@strong pw_sender, @strong graph_view => move |_, opts| {
|
||||
match opts.lookup::<String>("socket") {
|
||||
Ok(p) => {
|
||||
current_remote_label.set_label(p.as_deref().unwrap_or(DEFAULT_REMOTE_NAME));
|
||||
pw_sender.send(GtkMessage::Connect(p)).unwrap();
|
||||
},
|
||||
Err(e) => error!("Invalid socket path: {e}"),
|
||||
}
|
||||
|
||||
match opts.lookup::<String>("config") {
|
||||
Ok(config_path_option) => {
|
||||
let config_path = config_path_option.map(std::path::PathBuf::from);
|
||||
graph_view.set_config_from_path(config_path);
|
||||
},
|
||||
Err(e) => error!("Invalid config path: {e}"),
|
||||
}
|
||||
-1
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +190,7 @@ 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<PipewireMessage>,
|
||||
gtk_receiver: async_channel::Receiver<PipewireMessage>,
|
||||
pw_sender: Sender<GtkMessage>,
|
||||
) -> Self {
|
||||
let app: Application = glib::Object::builder()
|
||||
|
|
@ -152,6 +199,8 @@ impl Application {
|
|||
|
||||
let imp = app.imp();
|
||||
|
||||
imp.setup_options(pw_sender.clone());
|
||||
|
||||
imp.graph_manager
|
||||
.set(GraphManager::new(
|
||||
&imp.window.graph(),
|
||||
|
|
|
|||
259
src/config.rs
Normal file
259
src/config.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::constants::COLUMN_WIDTH;
|
||||
use crate::NodeType;
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub layout: Layout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct Layout {
|
||||
pub columns: Vec<Column>,
|
||||
pub rules: Vec<MatchRule>,
|
||||
#[serde(default)]
|
||||
pub defaults: Option<Defaults>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Defaults {
|
||||
pub source: f32,
|
||||
pub sink: f32,
|
||||
pub other: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Column {
|
||||
pub x: f32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct MatchRule {
|
||||
pub pattern: String,
|
||||
pub column: String,
|
||||
pub node_type: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Create a new Config instance and load from default locations
|
||||
pub fn new() -> Self {
|
||||
Self::load(None)
|
||||
}
|
||||
|
||||
/// Create a new Config instance and load from specified path or default locations
|
||||
pub fn load(config_path: Option<std::path::PathBuf>) -> Self {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
if let Some(parameter_path) = config_path {
|
||||
paths.push(parameter_path);
|
||||
}
|
||||
|
||||
paths.push(std::path::PathBuf::from("layout.toml"));
|
||||
|
||||
if let Some(default_config_path) = dirs::config_dir().map(|mut p| {
|
||||
p.push("helvum");
|
||||
p.push("layout.toml");
|
||||
p
|
||||
}) {
|
||||
paths.push(default_config_path);
|
||||
}
|
||||
|
||||
for path in paths {
|
||||
if !path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match Self::load_from_file(&path) {
|
||||
Ok(config) => {
|
||||
log::info!(
|
||||
"Loaded configuration from '{}'",
|
||||
path.canonicalize().unwrap_or(path).display()
|
||||
);
|
||||
return config;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to load config from '{}': {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("No configuration file found, using defaults");
|
||||
Config::default()
|
||||
}
|
||||
|
||||
fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = fs::read_to_string(path)?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
config.validate()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
if self.layout.columns.is_empty() {
|
||||
return Err("Layout must contain at least one column".to_string());
|
||||
}
|
||||
|
||||
if self.layout.rules.is_empty() {
|
||||
return Err(
|
||||
"Layout must contain at least one rule to assign nodes to columns".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let column_names: std::collections::HashSet<_> =
|
||||
self.layout.columns.iter().map(|c| &c.name).collect();
|
||||
|
||||
for rule in &self.layout.rules {
|
||||
if !column_names.contains(&rule.column) {
|
||||
return Err(format!(
|
||||
"Rule references unknown column '{}'. Available columns: {:?}",
|
||||
rule.column,
|
||||
column_names.iter().collect::<Vec<_>>()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted_columns = self.layout.columns.clone();
|
||||
sorted_columns.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
for window in sorted_columns.windows(2) {
|
||||
let distance = (window[1].x - window[0].x).abs();
|
||||
if distance < COLUMN_WIDTH {
|
||||
return Err(format!(
|
||||
"Columns '{}' (x={}) and '{}' (x={}) are too close together. Minimum distance should be {} pixels.",
|
||||
window[0].name, window[0].x,
|
||||
window[1].name, window[1].x,
|
||||
COLUMN_WIDTH
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get column position for a node, trying rules first, then defaults
|
||||
pub fn get_column(
|
||||
&self,
|
||||
node_name: &str,
|
||||
node_type: Option<&NodeType>,
|
||||
) -> Option<(f32, String)> {
|
||||
for rule in &self.layout.rules {
|
||||
if glob_match(&rule.pattern, node_name)
|
||||
&& self.node_type_matches(&rule.node_type, node_type)
|
||||
{
|
||||
for column in &self.layout.columns {
|
||||
if column.name == rule.column {
|
||||
return Some((column.x, column.name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.get_defaults_column(node_type)
|
||||
}
|
||||
|
||||
fn get_defaults_column(&self, node_type: Option<&NodeType>) -> Option<(f32, String)> {
|
||||
if let Some(defaults) = &self.layout.defaults {
|
||||
match node_type {
|
||||
Some(NodeType::Output) => Some((defaults.source, "source".to_string())),
|
||||
Some(NodeType::Input) => Some((defaults.sink, "sink".to_string())),
|
||||
None => Some((defaults.other, "other".to_string())),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn node_type_matches(
|
||||
&self,
|
||||
rule_node_type: &Option<String>,
|
||||
node_type: Option<&NodeType>,
|
||||
) -> bool {
|
||||
match rule_node_type.as_deref() {
|
||||
None => true, // Rule has no type constraint, matches any node
|
||||
Some("source") => node_type == Some(&NodeType::Output),
|
||||
Some("sink") => node_type == Some(&NodeType::Input),
|
||||
Some("filter") => node_type.is_none(),
|
||||
Some(unknown_type) => {
|
||||
log::warn!("Unknown node type in rule: {}", unknown_type);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_match(pattern: &str, text: &str) -> bool {
|
||||
glob_match_recursive(pattern, text)
|
||||
}
|
||||
|
||||
fn glob_match_recursive(pattern: &str, text: &str) -> bool {
|
||||
match (pattern.chars().next(), text.chars().next()) {
|
||||
(None, None) => true,
|
||||
(Some('*'), _) => {
|
||||
if glob_match_recursive(&pattern[1..], text) {
|
||||
return true;
|
||||
}
|
||||
if text.chars().next().is_some() {
|
||||
glob_match_recursive(pattern, &text[1..])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Some('?'), Some(_)) => glob_match_recursive(&pattern[1..], &text[1..]),
|
||||
(Some(p), Some(t)) if p.eq_ignore_ascii_case(&t) => {
|
||||
glob_match_recursive(&pattern[1..], &text[1..])
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_glob_match() {
|
||||
assert!(glob_match("hello", "hello"));
|
||||
assert!(glob_match("hello*", "hello"));
|
||||
assert!(glob_match("hello*", "hello world"));
|
||||
assert!(glob_match("*world", "hello world"));
|
||||
assert!(glob_match("*", "anything"));
|
||||
assert!(glob_match("h?llo", "hello"));
|
||||
assert!(glob_match("h?llo", "hallo"));
|
||||
assert!(!glob_match("hello", "world"));
|
||||
assert!(!glob_match("h?llo", "hllo"));
|
||||
assert!(glob_match("FIREFOX*", "firefox browser"));
|
||||
assert!(glob_match("firefox*", "FIREFOX BROWSER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_validation() {
|
||||
let config = Config {
|
||||
layout: Layout {
|
||||
columns: vec![
|
||||
Column {
|
||||
x: 50.0,
|
||||
name: "sources".to_string(),
|
||||
},
|
||||
Column {
|
||||
x: 300.0,
|
||||
name: "apps".to_string(),
|
||||
},
|
||||
],
|
||||
rules: vec![MatchRule {
|
||||
pattern: "Firefox*".to_string(),
|
||||
column: "apps".to_string(),
|
||||
node_type: None,
|
||||
}],
|
||||
defaults: Some(Defaults {
|
||||
source: 50.0,
|
||||
sink: 300.0,
|
||||
other: 550.0,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
assert!(config.validate().is_ok());
|
||||
}
|
||||
}
|
||||
16
src/constants.rs
Normal file
16
src/constants.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Layout and positioning constants for the graph view
|
||||
|
||||
/// Minimum gap between nodes in a column
|
||||
pub const MIN_NODE_GAP: f32 = 20.0;
|
||||
|
||||
/// Top margin
|
||||
pub const TOP_MARGIN: f32 = 20.0;
|
||||
|
||||
/// Minimum gap required when placing new nodes between existing ones
|
||||
pub const MIN_PLACEMENT_GAP: f32 = 2.0 * MIN_NODE_GAP + 80.0;
|
||||
|
||||
/// Width threshold for considering nodes to be in the same column
|
||||
pub const COLUMN_WIDTH: f32 = 200.0;
|
||||
|
||||
/// Canvas size for the graph view
|
||||
pub const CANVAS_SIZE: f64 = 5000.0;
|
||||
|
|
@ -23,9 +23,7 @@ use crate::{ui::graph::GraphView, GtkMessage, PipewireMessage};
|
|||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::{cell::OnceCell, cell::RefCell, collections::HashMap};
|
||||
|
||||
use crate::{ui::graph, MediaType, NodeType};
|
||||
|
||||
|
|
@ -53,36 +51,58 @@ mod imp {
|
|||
impl ObjectImpl for GraphManager {}
|
||||
|
||||
impl GraphManager {
|
||||
pub fn attach_receiver(&self, receiver: glib::Receiver<crate::PipewireMessage>) {
|
||||
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 async fn receive(&self, receiver: async_channel::Receiver<crate::PipewireMessage>) {
|
||||
loop {
|
||||
let Ok(msg) = receiver.recv().await else {
|
||||
continue;
|
||||
};
|
||||
match msg {
|
||||
PipewireMessage::NodeAdded {
|
||||
id,
|
||||
name,
|
||||
node_type,
|
||||
} => self.add_node(id, name.as_str(), node_type),
|
||||
PipewireMessage::NodeNameChanged {
|
||||
id,
|
||||
name,
|
||||
media_name,
|
||||
} => self.node_name_changed(id, &name, &media_name),
|
||||
PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
direction,
|
||||
} => self.add_port(id, name.as_str(), node_id, direction),
|
||||
PipewireMessage::PortFormatChanged { id, media_type } => {
|
||||
self.port_media_type_changed(id, media_type)
|
||||
}
|
||||
PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
active,
|
||||
media_type,
|
||||
} => self.add_link(id, port_from, port_to, active, media_type),
|
||||
PipewireMessage::LinkStateChanged { id, active } => {
|
||||
self.link_state_changed(id, active)
|
||||
}
|
||||
PipewireMessage::LinkFormatChanged { id, media_type } => {
|
||||
self.link_format_changed(id, media_type)
|
||||
}
|
||||
PipewireMessage::NodeRemoved { id } => self.remove_node(id),
|
||||
PipewireMessage::PortRemoved { id, node_id } => self.remove_port(id, node_id),
|
||||
PipewireMessage::LinkRemoved { id } => self.remove_link(id),
|
||||
PipewireMessage::Connecting => {
|
||||
self.obj().connection_banner().set_revealed(true);
|
||||
}
|
||||
PipewireMessage::Connected => {
|
||||
self.obj().connection_banner().set_revealed(false);
|
||||
}
|
||||
PipewireMessage::Disconnected => {
|
||||
self.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new node to the view.
|
||||
|
|
@ -130,7 +150,13 @@ 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: pipewire::spa::utils::Direction,
|
||||
) {
|
||||
log::info!("Adding port to graph: id {}", id);
|
||||
|
||||
let mut items = self.items.borrow_mut();
|
||||
|
|
@ -164,6 +190,8 @@ mod imp {
|
|||
items.insert(id, port.clone().upcast());
|
||||
|
||||
node.add_port(port);
|
||||
|
||||
self.obj().graph().add_port_to_node(&node);
|
||||
}
|
||||
|
||||
fn port_media_type_changed(&self, id: u32, media_type: MediaType) {
|
||||
|
|
@ -273,7 +301,11 @@ 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: pipewire::spa::param::format::MediaType,
|
||||
) {
|
||||
let items = self.items.borrow();
|
||||
|
||||
let Some(link) = items.get(&id) else {
|
||||
|
|
@ -322,19 +354,23 @@ glib::wrapper! {
|
|||
pub struct GraphManager(ObjectSubclass<imp::GraphManager>);
|
||||
}
|
||||
|
||||
async fn receive(graph_manager: GraphManager, receiver: async_channel::Receiver<PipewireMessage>) {
|
||||
graph_manager.imp().receive(receiver).await
|
||||
}
|
||||
|
||||
impl GraphManager {
|
||||
pub fn new(
|
||||
graph: &GraphView,
|
||||
connection_banner: &adw::Banner,
|
||||
sender: PwSender<GtkMessage>,
|
||||
receiver: glib::Receiver<PipewireMessage>,
|
||||
receiver: async_channel::Receiver<PipewireMessage>,
|
||||
) -> Self {
|
||||
let res: Self = glib::Object::builder()
|
||||
.property("graph", graph)
|
||||
.property("connection-banner", connection_banner)
|
||||
.build();
|
||||
|
||||
res.imp().attach_receiver(receiver);
|
||||
glib::MainContext::default().spawn_local(receive(res.clone(), receiver));
|
||||
assert!(
|
||||
res.imp().pw_sender.set(sender).is_ok(),
|
||||
"Should be able to set pw_sender)"
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -15,18 +15,22 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod application;
|
||||
mod config;
|
||||
mod constants;
|
||||
mod graph_manager;
|
||||
mod pipewire_connection;
|
||||
mod ui;
|
||||
|
||||
use adw::{gtk, prelude::*};
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
use pipewire::spa::{param::format::MediaType, utils::Direction};
|
||||
|
||||
/// Messages sent by the GTK thread to notify the pipewire thread.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GtkMessage {
|
||||
/// Toggle a link between the two specified ports.
|
||||
ToggleLink { port_from: u32, port_to: u32 },
|
||||
/// Connect to PipeWire service.
|
||||
Connect(Option<String>),
|
||||
/// Quit the event loop and let the thread finish.
|
||||
Terminate,
|
||||
}
|
||||
|
|
@ -84,7 +88,7 @@ pub enum PipewireMessage {
|
|||
Disconnected,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NodeType {
|
||||
Input,
|
||||
Output,
|
||||
|
|
@ -120,7 +124,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
// 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) = async_channel::unbounded();
|
||||
let (pw_sender, pw_receiver) = pipewire::channel::channel();
|
||||
let pw_thread =
|
||||
std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver));
|
||||
|
|
|
|||
|
|
@ -16,29 +16,26 @@
|
|||
|
||||
mod state;
|
||||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration};
|
||||
|
||||
use adw::glib::{self, clone};
|
||||
use log::{debug, error, info, warn};
|
||||
use pipewire::{
|
||||
context::Context,
|
||||
core::{Core, PW_ID_CORE},
|
||||
keys,
|
||||
link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState},
|
||||
node::{Node, NodeInfo, NodeListener},
|
||||
port::{Port, PortChangeMask, PortInfo, PortListener},
|
||||
prelude::*,
|
||||
properties,
|
||||
link::{Link, LinkChangeMask, LinkInfoRef, LinkListener, LinkState},
|
||||
main_loop::MainLoop,
|
||||
node::{Node, NodeInfoRef, NodeListener},
|
||||
port::{Port, PortChangeMask, PortInfoRef, PortListener},
|
||||
properties::{properties, Properties},
|
||||
registry::{GlobalObject, Registry},
|
||||
spa::{
|
||||
param::{ParamInfoFlags, ParamType},
|
||||
ForeignDict, SpaResult,
|
||||
utils::dict::DictRef,
|
||||
utils::result::SpaResult,
|
||||
},
|
||||
types::ObjectType,
|
||||
Context, Core, MainLoop,
|
||||
};
|
||||
|
||||
use crate::{GtkMessage, MediaType, NodeType, PipewireMessage};
|
||||
|
|
@ -59,44 +56,78 @@ enum ProxyItem {
|
|||
},
|
||||
}
|
||||
|
||||
struct LoopState {
|
||||
is_stopped: bool,
|
||||
props: Properties,
|
||||
}
|
||||
|
||||
impl LoopState {
|
||||
fn handle_message(&mut self, msg: GtkMessage) -> bool {
|
||||
match msg {
|
||||
GtkMessage::Terminate => self.is_stopped = true,
|
||||
GtkMessage::Connect(remote) => match remote {
|
||||
Some(s) => self.props.insert(*keys::REMOTE_NAME, s),
|
||||
None => self.props.remove(*keys::REMOTE_NAME),
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The "main" function of the pipewire thread.
|
||||
pub(super) fn thread_main(
|
||||
gtk_sender: glib::Sender<PipewireMessage>,
|
||||
gtk_sender: async_channel::Sender<PipewireMessage>,
|
||||
mut pw_receiver: pipewire::channel::Receiver<GtkMessage>,
|
||||
) {
|
||||
let mainloop = MainLoop::new().expect("Failed to create mainloop");
|
||||
let mainloop = MainLoop::new(None).expect("Failed to create mainloop");
|
||||
let context = Rc::new(Context::new(&mainloop).expect("Failed to create context"));
|
||||
let is_stopped = Rc::new(Cell::new(false));
|
||||
let loop_state = Rc::new(RefCell::new(LoopState {
|
||||
is_stopped: false,
|
||||
props: properties! {
|
||||
"media.category" => "Manager",
|
||||
},
|
||||
}));
|
||||
let mut is_connecting = false;
|
||||
|
||||
while !is_stopped.get() {
|
||||
// Wait PipeWire service to connect from command line arguments.
|
||||
let receiver = pw_receiver.attach(mainloop.loop_(), {
|
||||
clone!(@strong mainloop, @strong loop_state => move |msg|
|
||||
if loop_state.borrow_mut().handle_message(msg) {
|
||||
mainloop.quit();
|
||||
}
|
||||
)
|
||||
});
|
||||
mainloop.run();
|
||||
pw_receiver = receiver.deattach();
|
||||
|
||||
while !loop_state.borrow().is_stopped {
|
||||
// Try to connect
|
||||
let core = match context.connect(Some(properties! {
|
||||
"media.category" => "Manager"
|
||||
})) {
|
||||
let props = loop_state.borrow().props.clone();
|
||||
let core = match context.connect(Some(props)) {
|
||||
Ok(core) => Rc::new(core),
|
||||
Err(_) => {
|
||||
if !is_connecting {
|
||||
is_connecting = true;
|
||||
gtk_sender
|
||||
.send(PipewireMessage::Connecting)
|
||||
.send_blocking(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();
|
||||
}));
|
||||
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, {
|
||||
clone!(@strong mainloop, @strong is_stopped => move |msg|
|
||||
if let GtkMessage::Terminate = msg {
|
||||
// main thread requested stop
|
||||
is_stopped.set(true);
|
||||
let receiver = pw_receiver.attach(mainloop.loop_(), {
|
||||
clone!(@strong mainloop, @strong loop_state => move |msg|
|
||||
if loop_state.borrow_mut().handle_message(msg) {
|
||||
mainloop.quit();
|
||||
}
|
||||
)
|
||||
|
|
@ -112,7 +143,7 @@ pub(super) fn thread_main(
|
|||
if is_connecting {
|
||||
is_connecting = false;
|
||||
gtk_sender
|
||||
.send(PipewireMessage::Connected)
|
||||
.send_blocking(PipewireMessage::Connected)
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
|
||||
|
|
@ -122,33 +153,34 @@ pub(super) fn thread_main(
|
|||
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 {
|
||||
let receiver = pw_receiver.attach(mainloop.loop_(), {
|
||||
clone!(@strong mainloop, @weak core, @weak registry, @strong state, @strong loop_state => 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);
|
||||
GtkMessage::Terminate | GtkMessage::Connect(_) => {
|
||||
loop_state.borrow_mut().handle_message(msg);
|
||||
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.send_blocking(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
|
||||
|
|
@ -163,9 +195,9 @@ pub(super) fn thread_main(
|
|||
}
|
||||
}
|
||||
))
|
||||
.global_remove(clone!(@strong proxies, @strong state => move |id| {
|
||||
.global_remove(clone!(@strong gtk_sender, @strong proxies, @strong state => move |id| {
|
||||
if let Some(item) = state.borrow_mut().remove(id) {
|
||||
gtk_sender.send(match item {
|
||||
gtk_sender.send_blocking(match item {
|
||||
Item::Node { .. } => PipewireMessage::NodeRemoved {id},
|
||||
Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id},
|
||||
Item::Link { .. } => PipewireMessage::LinkRemoved {id},
|
||||
|
|
@ -183,11 +215,15 @@ pub(super) fn thread_main(
|
|||
|
||||
mainloop.run();
|
||||
pw_receiver = receiver.deattach();
|
||||
|
||||
gtk_sender
|
||||
.send_blocking(PipewireMessage::Disconnected)
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> &str {
|
||||
props
|
||||
.get(&keys::NODE_DESCRIPTION)
|
||||
.or_else(|| props.get(&keys::NODE_NICK))
|
||||
|
|
@ -197,8 +233,8 @@ fn get_node_name(props: &ForeignDict) -> &str {
|
|||
|
||||
/// Handle a new node being added
|
||||
fn handle_node(
|
||||
node: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
node: &GlobalObject<&DictRef>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
|
|
@ -233,7 +269,7 @@ fn handle_node(
|
|||
state.borrow_mut().insert(node.id, Item::Node);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeAdded {
|
||||
.send_blocking(PipewireMessage::NodeAdded {
|
||||
id: node.id,
|
||||
name,
|
||||
node_type,
|
||||
|
|
@ -258,8 +294,8 @@ fn handle_node(
|
|||
}
|
||||
|
||||
fn handle_node_info(
|
||||
info: &NodeInfo,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
info: &NodeInfoRef,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
) {
|
||||
debug!("Received node info: {:?}", info);
|
||||
|
|
@ -276,7 +312,7 @@ fn handle_node_info(
|
|||
let name = get_node_name(props).to_string();
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeNameChanged {
|
||||
.send_blocking(PipewireMessage::NodeNameChanged {
|
||||
id,
|
||||
name,
|
||||
media_name: media_name.to_string(),
|
||||
|
|
@ -287,8 +323,8 @@ fn handle_node_info(
|
|||
|
||||
/// Handle a new port being added
|
||||
fn handle_port(
|
||||
port: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
port: &GlobalObject<&DictRef>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
|
|
@ -319,10 +355,10 @@ fn handle_port(
|
|||
}
|
||||
|
||||
fn handle_port_info(
|
||||
info: &PortInfo,
|
||||
info: &PortInfoRef,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received port info: {:?}", info);
|
||||
|
||||
|
|
@ -363,7 +399,7 @@ fn handle_port_info(
|
|||
}
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortAdded {
|
||||
.send_blocking(PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
|
|
@ -376,7 +412,7 @@ fn handle_port_info(
|
|||
fn handle_port_enum_format(
|
||||
port_id: u32,
|
||||
param: Option<&pipewire::spa::pod::Pod>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
) {
|
||||
let media_type = param
|
||||
.and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok())
|
||||
|
|
@ -384,7 +420,7 @@ fn handle_port_enum_format(
|
|||
.unwrap_or(MediaType::Unknown);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortFormatChanged {
|
||||
.send_blocking(PipewireMessage::PortFormatChanged {
|
||||
id: port_id,
|
||||
media_type,
|
||||
})
|
||||
|
|
@ -393,8 +429,8 @@ fn handle_port_enum_format(
|
|||
|
||||
/// Handle a new link being added
|
||||
fn handle_link(
|
||||
link: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
link: &GlobalObject<&DictRef>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
|
|
@ -422,9 +458,9 @@ fn handle_link(
|
|||
}
|
||||
|
||||
fn handle_link_info(
|
||||
info: &LinkInfo,
|
||||
info: &LinkInfoRef,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
sender: &async_channel::Sender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received link info: {:?}", info);
|
||||
|
||||
|
|
@ -435,7 +471,7 @@ fn handle_link_info(
|
|||
// Info was an update - figure out if we should notify the gtk thread
|
||||
if info.change_mask().contains(LinkChangeMask::STATE) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkStateChanged {
|
||||
.send_blocking(PipewireMessage::LinkStateChanged {
|
||||
id,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
})
|
||||
|
|
@ -443,7 +479,7 @@ fn handle_link_info(
|
|||
}
|
||||
if info.change_mask().contains(LinkChangeMask::FORMAT) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkFormatChanged {
|
||||
.send_blocking(PipewireMessage::LinkFormatChanged {
|
||||
id,
|
||||
media_type: get_link_media_type(info),
|
||||
})
|
||||
|
|
@ -457,7 +493,7 @@ fn handle_link_info(
|
|||
state.insert(id, Item::Link { port_from, port_to });
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::LinkAdded {
|
||||
.send_blocking(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
|
|
@ -495,7 +531,7 @@ fn toggle_link(
|
|||
.get_node_of_port(port_to)
|
||||
.expect("Requested port not in state");
|
||||
|
||||
if let Err(e) = core.create_object::<Link, _>(
|
||||
if let Err(e) = core.create_object::<Link>(
|
||||
"link-factory",
|
||||
&properties! {
|
||||
"link.output.node" => node_from.to_string(),
|
||||
|
|
@ -510,7 +546,7 @@ fn toggle_link(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_link_media_type(link_info: &LinkInfo) -> MediaType {
|
||||
fn get_link_media_type(link_info: &LinkInfoRef) -> MediaType {
|
||||
let media_type = link_info
|
||||
.format()
|
||||
.and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok())
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ use adw::{
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use super::{Link, Node, Port};
|
||||
use crate::NodeType;
|
||||
|
||||
const CANVAS_SIZE: f64 = 5000.0;
|
||||
use crate::{config::Config, constants::*, NodeType};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -42,8 +40,8 @@ mod imp {
|
|||
use adw::gtk::gdk::{self};
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use pipewire::spa::format::MediaType;
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::spa::param::format::MediaType;
|
||||
use pipewire::spa::utils::Direction;
|
||||
|
||||
pub struct Colors {
|
||||
audio: gdk::RGBA,
|
||||
|
|
@ -96,6 +94,9 @@ mod imp {
|
|||
|
||||
// This keeps track of an ongoing move view gesture.
|
||||
pub move_view_state: Cell<(f64, f64)>,
|
||||
|
||||
// Cache configuration from file
|
||||
pub config: RefCell<Option<Config>>,
|
||||
}
|
||||
|
||||
impl Default for GraphView {
|
||||
|
|
@ -112,6 +113,7 @@ mod imp {
|
|||
zoom_gesture_initial_zoom: Default::default(),
|
||||
zoom_gesture_anchor: Default::default(),
|
||||
move_view_state: Default::default(),
|
||||
config: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -731,6 +733,23 @@ impl GraphView {
|
|||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// Set configuration from a specific path
|
||||
pub fn set_config_from_path(&self, config_path: Option<std::path::PathBuf>) {
|
||||
let mut cached = self.imp().config.borrow_mut();
|
||||
*cached = Some(Config::load(config_path));
|
||||
}
|
||||
|
||||
/// Get the cached configuration, creating it if not already cached
|
||||
fn get_config(&self) -> Config {
|
||||
let mut cached = self.imp().config.borrow_mut();
|
||||
|
||||
if cached.is_none() {
|
||||
*cached = Some(Config::new());
|
||||
}
|
||||
|
||||
cached.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn zoom_factor(&self) -> f64 {
|
||||
self.property("zoom-factor")
|
||||
}
|
||||
|
|
@ -773,40 +792,117 @@ impl GraphView {
|
|||
self.set_property("zoom-factor", zoom_factor);
|
||||
}
|
||||
|
||||
pub fn add_node(&self, node: Node, node_type: Option<NodeType>) {
|
||||
/// Collects all nodes in a vertical column starting from a given y-coordinate.
|
||||
///
|
||||
/// Returns nodes that are within `COLUMN_WIDTH` of the specified x-coordinate
|
||||
/// and have y-coordinates greater than or equal to `from_y`. The results are
|
||||
/// sorted by y-coordinate in ascending order.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `x` - The x-coordinate defining the center of the column
|
||||
/// * `from_y` - The minimum y-coordinate to include nodes from
|
||||
///
|
||||
/// # Returns
|
||||
/// A vector of tuples containing (Node, Point) pairs sorted by y-coordinate
|
||||
fn collect_column_nodes(&self, x: f32, from_y: f32) -> Vec<(Node, Point)> {
|
||||
let imp = self.imp();
|
||||
node.set_parent(self);
|
||||
|
||||
// Place widgets in colums of 3, growing down
|
||||
let x = if let Some(node_type) = node_type {
|
||||
match node_type {
|
||||
NodeType::Output => 20.0,
|
||||
NodeType::Input => 820.0,
|
||||
}
|
||||
} else {
|
||||
420.0
|
||||
};
|
||||
|
||||
let y = imp
|
||||
let mut column_nodes: Vec<_> = imp
|
||||
.nodes
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|node| {
|
||||
// Map nodes to their locations
|
||||
let point = self.node_position(&node.0.clone().upcast()).unwrap();
|
||||
(point.x(), point.y())
|
||||
.filter_map(|(node, position)| {
|
||||
let is_in_column =
|
||||
((x - position.x()).abs() < COLUMN_WIDTH) && (position.y() >= from_y);
|
||||
is_in_column.then_some((node.clone(), *position))
|
||||
})
|
||||
.filter(|(x2, _)| {
|
||||
// Only look for other nodes that have a similar x coordinate
|
||||
(x - x2).abs() < 50.0
|
||||
})
|
||||
.max_by(|y1, y2| {
|
||||
// Get max in column
|
||||
y1.partial_cmp(y2).unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.map_or(20_f32, |(_x, y)| y + 120.0);
|
||||
.collect();
|
||||
|
||||
imp.nodes.borrow_mut().insert(node, Point::new(x, y));
|
||||
column_nodes.sort_unstable_by(|(_, pos_a), (_, pos_b)| {
|
||||
pos_a.y().partial_cmp(&pos_b.y()).unwrap_or(Ordering::Less)
|
||||
});
|
||||
|
||||
column_nodes
|
||||
}
|
||||
|
||||
/// Recursively repositions nodes in a column to maintain minimum gap requirements.
|
||||
///
|
||||
/// Starting from `from_y`, examines all node pairs in the column and moves nodes
|
||||
/// downward if the gap between them is less than `MIN_NODE_GAP`. When a node is
|
||||
/// moved, recursively repositions all nodes below it to prevent cascading overlaps.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `x` - The x-coordinate defining the column to reposition
|
||||
/// * `from_y` - The y-coordinate to start repositioning from (inclusive)
|
||||
fn reposition_nodes_in_column(&self, x: f32, from_y: f32) {
|
||||
let column = self.collect_column_nodes(x, from_y);
|
||||
|
||||
for node_pair in column.windows(2) {
|
||||
let (upper_node, upper_node_position) = &node_pair[0];
|
||||
let (lower_node, lower_node_position) = &node_pair[1];
|
||||
|
||||
let upper_node_bottom = upper_node_position.y() + self.node_height(upper_node);
|
||||
let node_gap = lower_node_position.y() - upper_node_bottom;
|
||||
|
||||
if node_gap < MIN_NODE_GAP {
|
||||
let new_lower_node_y = upper_node_bottom + MIN_NODE_GAP;
|
||||
|
||||
self.move_node(
|
||||
lower_node,
|
||||
&Point::new(lower_node_position.x(), new_lower_node_y),
|
||||
);
|
||||
|
||||
self.reposition_nodes_in_column(x, new_lower_node_y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_port_to_node(&self, node: &Node) {
|
||||
let node_position = self.node_position(node).unwrap();
|
||||
|
||||
let column = self.collect_column_nodes(node_position.x(), node_position.y());
|
||||
|
||||
if let Some((_, next_node_position)) = column.get(1) {
|
||||
let node_height = self.node_height(node);
|
||||
// If there is not enough space below the node, reposition all nodes in the column
|
||||
if (next_node_position.y() - node_position.y() - node_height) < MIN_NODE_GAP {
|
||||
self.reposition_nodes_in_column(node_position.x(), node_position.y());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_node(&self, node: Node, node_type: Option<NodeType>) {
|
||||
node.set_parent(self);
|
||||
|
||||
let node_name = node.property::<String>("node-name");
|
||||
|
||||
let x = match self.get_config().get_column(&node_name, node_type.as_ref()) {
|
||||
Some((x_from_config, _)) => x_from_config,
|
||||
None => match node_type {
|
||||
Some(NodeType::Output) => 20.0,
|
||||
Some(NodeType::Input) => 820.0,
|
||||
None => 420.0,
|
||||
},
|
||||
};
|
||||
|
||||
let column = self.collect_column_nodes(x, 0.0);
|
||||
|
||||
let y = column
|
||||
.windows(2)
|
||||
.find_map(|pair| {
|
||||
let (upper_node, upper_pos) = &pair[0];
|
||||
let (_, lower_pos) = &pair[1];
|
||||
let upper_bottom = upper_pos.y() + self.node_height(upper_node);
|
||||
let available_gap = lower_pos.y() - upper_bottom;
|
||||
(available_gap >= MIN_PLACEMENT_GAP).then_some(upper_bottom + MIN_NODE_GAP)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
column.last().map_or(TOP_MARGIN, |(last_node, last_pos)| {
|
||||
last_pos.y() + self.node_height(last_node) + MIN_NODE_GAP
|
||||
})
|
||||
});
|
||||
|
||||
self.imp().nodes.borrow_mut().insert(node, Point::new(x, y));
|
||||
}
|
||||
|
||||
pub fn remove_node(&self, node: &Node) {
|
||||
|
|
@ -858,6 +954,12 @@ impl GraphView {
|
|||
self.imp().nodes.borrow().get(node).copied()
|
||||
}
|
||||
|
||||
/// Get the height of the specified node inside the graphview.
|
||||
pub fn node_height(&self, node: &Node) -> f32 {
|
||||
let (_, natural_size) = node.preferred_size();
|
||||
natural_size.height() as f32
|
||||
}
|
||||
|
||||
pub(super) fn move_node(&self, widget: &Node, point: &Point) {
|
||||
let mut nodes = self.imp().nodes.borrow_mut();
|
||||
let node_point = nodes.get_mut(widget).expect("Node is not on the graph");
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use adw::{glib, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::format::MediaType;
|
||||
use pipewire::spa::param::format::MediaType;
|
||||
|
||||
use super::Port;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use adw::{glib, gtk, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::spa::utils::Direction;
|
||||
|
||||
use super::Port;
|
||||
|
||||
|
|
@ -129,8 +129,8 @@ mod imp {
|
|||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
ports_out.sort_unstable_by_key(|port| port.name());
|
||||
ports_in.sort_unstable_by_key(|port| port.name());
|
||||
ports_out.sort_unstable_by(|&a, &b| natord::compare(&a.name(), &b.name()));
|
||||
ports_in.sort_unstable_by(|&a, &b| natord::compare(&a.name(), &b.name()));
|
||||
|
||||
// In case no ports have been added to the port, hide the seperator as it is not needed
|
||||
self.separator
|
||||
|
|
|
|||
|
|
@ -21,17 +21,17 @@ use adw::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::spa::utils::Direction;
|
||||
|
||||
use super::PortHandle;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, OnceCell};
|
||||
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
use once_cell::sync::Lazy;
|
||||
use pipewire::spa::{param::format::MediaType, utils::Direction};
|
||||
|
||||
/// Graphical representation of a pipewire port.
|
||||
#[derive(gtk::CompositeTemplate, glib::Properties)]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ mod imp {
|
|||
#[property(type = adw::Banner, get = |_| self.connection_banner.clone())]
|
||||
pub connection_banner: TemplateChild<adw::Banner>,
|
||||
#[template_child]
|
||||
#[property(type = gtk::Label, get = |_| self.current_remote_label.clone())]
|
||||
pub current_remote_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
#[property(type = graph::GraphView, get = |_| self.graph.clone())]
|
||||
pub graph: TemplateChild<graph::GraphView>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,40 @@
|
|||
<template class="HelvumWindow" parent="AdwApplicationWindow">
|
||||
<property name="default-width">1280</property>
|
||||
<property name="default-height">720</property>
|
||||
<property name="title">Helvum - Pipewire Patchbay</property>
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar" id="header_bar">
|
||||
<property name="title-widget">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label">Helvum - Pipewire Patchbay</property>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="width-chars">5</property>
|
||||
<property name="vexpand">yes</property>
|
||||
<property name="valign">end</property>
|
||||
<style>
|
||||
<class name="title"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="current_remote_label">
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="ellipsize">start</property>
|
||||
<property name="vexpand">yes</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="caption"/>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton">
|
||||
<property name="icon-name">open-menu-symbolic</property>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue