14 Commits

Author SHA1 Message Date
Tom Wagner
d7dd6033a6 graphview: Draw links using new gdk path API instead of cairo 2024-03-25 13:55:49 +01:00
Tom Wagner
f32559511d Update to latest gtk-rs crates 2024-03-25 10:08:38 +01:00
Tom Wagner
57cba6381b flatpak: Update runtime to gnome 46 2024-03-25 09:49:16 +01:00
Dorinda Bassey
d1b9b0f11f update Helvum to track the latest pipewire0.8.0
update Helvum to track the latest pipewire0.8.0
2024-03-19 14:06:57 +01:00
Tom A. Wagner
4549ba6ff5 ui: Force LTR direction on the nodes port grid and on ports
Forces the direction on the nodes internal port grid and on the ports themselves
to be always left-to-right (LTR), to avoid the UI becoming messed up when defaulting
to right-to-left (RTL).

Previously, when using RTL, the side of input and output ports would be swapped,
causing the port handles to be inside the node instead of at the edge.
2023-12-09 16:01:11 +01:00
Denis Drakhnia
e78d6f5fb4 ui: Move view with middle mouse button. 2023-11-18 14:19:20 +02:00
Denis Drakhnia
96c079d29e ui: Display node media name in graph view 2023-10-13 11:40:21 +00:00
Tom A. Wagner
5d4931b418 pw: Set media.category property to manager
This will make the session manager give Helvum full permissions even when
used from flatpak or otherwise restricted, so that we can always change
the graph even if permissions become more restricted in the future.
2023-10-12 08:36:08 +00:00
Tom A. Wagner
b983ade736 ui: Move "disconnected" banner from headerbar into content
The change to AdwToolbarView put the disconnected banner into the toolbar, resulting in a weird-looking
separator when the bar is shown.

This moves the banner into the "content" widget of the AdwToolbarView to fix that issue.
2023-10-11 22:48:37 +02:00
Angelo Verlain
94d5e95695 use AdwToolbarView 2023-10-11 22:48:37 +02:00
Angelo Verlain Shema
e1f63ddd28 Use responsive design 2023-10-10 18:16:23 +00:00
Angelo Verlain
903df21ba3 attach about window 2023-10-06 17:46:59 +02:00
Tom A. Wagner
39437eaf29 Release v0.5.1 2023-09-28 14:13:00 +02:00
Tom A. Wagner
a1a4594a25 ui: Fix headerbar becoming too large
Removes the .toolbar class from a box in the headerbar and instead sets spacing property.

The .toolbar class added extra vertical padding, so the headerbar had to increase its size
2023-09-28 14:05:48 +02:00
20 changed files with 694 additions and 317 deletions

View File

@@ -3,7 +3,7 @@ stages:
- lint - lint
.flatpak: .flatpak:
image: 'quay.io/gnome_infrastructure/gnome-runtime-images:gnome-45' image: 'quay.io/gnome_infrastructure/gnome-runtime-images:gnome-46'
variables: variables:
FLATPAK_BUILD_DIR: _build FLATPAK_BUILD_DIR: _build
MANIFEST_PATH: build-aux/org.pipewire.Helvum.json MANIFEST_PATH: build-aux/org.pipewire.Helvum.json

373
Cargo.lock generated
View File

@@ -11,12 +11,35 @@ dependencies = [
"memchr", "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]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.75" version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -25,16 +48,17 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.66.1" version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
dependencies = [ dependencies = [
"bitflags 2.4.0", "annotate-snippets",
"bitflags",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"peeking_take_while",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
@@ -43,12 +67,6 @@ dependencies = [
"syn 2.0.37", "syn 2.0.37",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.4.0" version = "2.4.0"
@@ -57,23 +75,22 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.18.2" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0466dfa8c0ee78deef390c274ad756801e0a6dbb86c5ef0924a298c5761c4d" checksum = "2650f66005301bd33cc486dec076e1293c4cecf768bc7ba9bf5d2b1be339b99c"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib",
"libc", "libc",
"once_cell",
"thiserror", "thiserror",
] ]
[[package]] [[package]]
name = "cairo-sys-rs" name = "cairo-sys-rs"
version = "0.18.2" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" checksum = "fd3bb3119664efbd78b5e6c93957447944f16bdbced84c17a9f41c7829b81e64"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -125,6 +142,15 @@ dependencies = [
"libloading", "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]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.6.0" version = "0.6.0"
@@ -140,19 +166,52 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 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]] [[package]]
name = "field-offset" name = "field-offset"
version = "0.3.6" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
dependencies = [ dependencies = [
"memoffset 0.9.0", "memoffset",
"rustc_version", "rustc_version",
] ]
@@ -221,22 +280,21 @@ dependencies = [
[[package]] [[package]]
name = "gdk-pixbuf" name = "gdk-pixbuf"
version = "0.18.0" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc9c2ed73a81d556b65d08879ba4ee58808a6b1927ce915262185d6d547c6f3" checksum = "f6a23f8a0b5090494fd04924662d463f8386cc678dd3915015a838c1a3679b92"
dependencies = [ dependencies = [
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gio", "gio",
"glib", "glib",
"libc", "libc",
"once_cell",
] ]
[[package]] [[package]]
name = "gdk-pixbuf-sys" name = "gdk-pixbuf-sys"
version = "0.18.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" checksum = "3dcbd04c1b2c4834cc008b4828bc917d062483b88d26effde6342e5622028f96"
dependencies = [ dependencies = [
"gio-sys", "gio-sys",
"glib-sys", "glib-sys",
@@ -247,9 +305,9 @@ dependencies = [
[[package]] [[package]]
name = "gdk4" name = "gdk4"
version = "0.7.3" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6" checksum = "9100b25604183f2fd97f55ef087fae96ab4934d7215118a35303e422688e6e4b"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"gdk-pixbuf", "gdk-pixbuf",
@@ -262,9 +320,9 @@ dependencies = [
[[package]] [[package]]
name = "gdk4-sys" name = "gdk4-sys"
version = "0.7.2" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0" checksum = "d0b76874c40bb8d1c7d03a7231e23ac75fa577a456cd53af32ec17ec8f121626"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
@@ -279,9 +337,9 @@ dependencies = [
[[package]] [[package]]
name = "gio" name = "gio"
version = "0.18.2" version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57052f84e8e5999b258e8adf8f5f2af0ac69033864936b8b6838321db2f759b1" checksum = "c64947d08d7fbb03bf8ad1f25a8ac6cf4329bc772c9b7e5abe7bf9493c81194f"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -290,7 +348,6 @@ dependencies = [
"gio-sys", "gio-sys",
"glib", "glib",
"libc", "libc",
"once_cell",
"pin-project-lite", "pin-project-lite",
"smallvec", "smallvec",
"thiserror", "thiserror",
@@ -298,24 +355,24 @@ dependencies = [
[[package]] [[package]]
name = "gio-sys" name = "gio-sys"
version = "0.18.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" checksum = "bcf8e1d9219bb294636753d307b030c1e8a032062cba74f493c431a5c8b81ce4"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
"libc", "libc",
"system-deps", "system-deps",
"winapi", "windows-sys",
] ]
[[package]] [[package]]
name = "glib" name = "glib"
version = "0.18.2" version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19" checksum = "01e191cc1af1f35b9699213107068cd3fe05d9816275ac118dc785a0dd8faebf"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor", "futures-executor",
@@ -328,20 +385,18 @@ dependencies = [
"libc", "libc",
"log", "log",
"memchr", "memchr",
"once_cell",
"smallvec", "smallvec",
"thiserror", "thiserror",
] ]
[[package]] [[package]]
name = "glib-macros" name = "glib-macros"
version = "0.18.2" version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8da903822b136d42360518653fcf154455defc437d3e7a81475bf9a95ff1e47" checksum = "9972bb91643d589c889654693a4f1d07697fdcb5d104b5c44fb68649ba1bf68d"
dependencies = [ dependencies = [
"heck", "heck 0.5.0",
"proc-macro-crate", "proc-macro-crate",
"proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.37", "syn 2.0.37",
@@ -349,9 +404,9 @@ dependencies = [
[[package]] [[package]]
name = "glib-sys" name = "glib-sys"
version = "0.18.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" checksum = "630f097773d7c7a0bb3258df4e8157b47dc98bbfa0e60ad9ab56174813feced4"
dependencies = [ dependencies = [
"libc", "libc",
"system-deps", "system-deps",
@@ -365,9 +420,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "gobject-sys" name = "gobject-sys"
version = "0.18.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" checksum = "c85e2b1080b9418dd0c58b498da3a5c826030343e0ef07bde6a955d28de54979"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -376,9 +431,9 @@ dependencies = [
[[package]] [[package]]
name = "graphene-rs" name = "graphene-rs"
version = "0.18.1" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401" checksum = "99e4d388e96c5f29e2b2f67045d229ddf826d0a8d6d282f94ed3b34452222c91"
dependencies = [ dependencies = [
"glib", "glib",
"graphene-sys", "graphene-sys",
@@ -387,9 +442,9 @@ dependencies = [
[[package]] [[package]]
name = "graphene-sys" name = "graphene-sys"
version = "0.18.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59" checksum = "236ed66cc9b18d8adf233716f75de803d0bf6fc806f60d14d948974a12e240d0"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -399,9 +454,9 @@ dependencies = [
[[package]] [[package]]
name = "gsk4" name = "gsk4"
version = "0.7.3" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e" checksum = "c65036fc8f99579e8cb37b12487969b707ab23ec8ab953682ff347cbd15d396e"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"gdk4", "gdk4",
@@ -414,9 +469,9 @@ dependencies = [
[[package]] [[package]]
name = "gsk4-sys" name = "gsk4-sys"
version = "0.7.3" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55" checksum = "bd24c814379f9c3199dc53e52253ee8d0f657eae389ab282c330505289d24738"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk4-sys", "gdk4-sys",
@@ -430,9 +485,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4" name = "gtk4"
version = "0.7.3" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842" checksum = "aa82753b8c26277e4af1446c70e35b19aad4fb794a7b143859e7eeb9a4025d83"
dependencies = [ dependencies = [
"cairo-rs", "cairo-rs",
"field-offset", "field-offset",
@@ -451,9 +506,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4-macros" name = "gtk4-macros"
version = "0.7.2" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f" checksum = "40300bf071d2fcd4c94eacc09e84ec6fe73129d2ceb635cf7e55b026b5443567"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"proc-macro-crate", "proc-macro-crate",
@@ -465,9 +520,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4-sys" name = "gtk4-sys"
version = "0.7.3" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8" checksum = "0db1b104138f087ccdc81d2c332de5dd049b89de3d384437cc1093b17cd2da18"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
@@ -495,10 +550,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "helvum" name = "heck"
version = "0.5.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "helvum"
version = "0.5.1"
dependencies = [ dependencies = [
"async-channel",
"glib", "glib",
"gtk4",
"libadwaita", "libadwaita",
"libc", "libc",
"log", "log",
@@ -516,6 +579,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@@ -530,9 +602,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libadwaita" name = "libadwaita"
version = "0.5.3" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c" checksum = "91b4990248b9e1ec5e72094a2ccaea70ec3809f88f6fd52192f2af306b87c5d9"
dependencies = [ dependencies = [
"gdk-pixbuf", "gdk-pixbuf",
"gdk4", "gdk4",
@@ -546,9 +618,9 @@ dependencies = [
[[package]] [[package]]
name = "libadwaita-sys" name = "libadwaita-sys"
version = "0.5.3" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a" checksum = "23a748e4e92be1265cd9e93d569c0b5dfc7814107985aa6743d670ab281ea1a8"
dependencies = [ dependencies = [
"gdk4-sys", "gdk4-sys",
"gio-sys", "gio-sys",
@@ -578,11 +650,11 @@ dependencies = [
[[package]] [[package]]
name = "libspa" name = "libspa"
version = "0.7.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0434617020ddca18b86067912970c55410ca654cdafd775480322f50b857a8c4" checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags",
"cc", "cc",
"convert_case", "convert_case",
"cookie-factory", "cookie-factory",
@@ -595,9 +667,9 @@ dependencies = [
[[package]] [[package]]
name = "libspa-sys" name = "libspa-sys"
version = "0.7.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e70ca3f3e70f858ef363046d06178c427b4e0b63d210c95fd87d752679d345" checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
@@ -612,18 +684,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.3" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@@ -642,15 +705,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.26.4" version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"cfg-if", "cfg-if",
"libc", "libc",
"memoffset 0.7.1",
"pin-utils",
] ]
[[package]] [[package]]
@@ -665,28 +726,27 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.18.0" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.18.0" version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06a9e54b831d033206160096b825f2070cf5fda7e35167b1c01e9e774f9202d1" checksum = "b1264d13deb823cc652f26cfe59afb1ec4b9db2a5bd27c41b738c879cc1bfaa1"
dependencies = [ dependencies = [
"gio", "gio",
"glib", "glib",
"libc", "libc",
"once_cell",
"pango-sys", "pango-sys",
] ]
[[package]] [[package]]
name = "pango-sys" name = "pango-sys"
version = "0.18.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" checksum = "f52ef6a881c19fbfe3b1484df5cad411acaaba29dbec843941c3110d19f340ea"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
@@ -695,10 +755,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "peeking_take_while" name = "parking"
version = "0.1.2" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
@@ -714,12 +774,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pipewire" name = "pipewire"
version = "0.7.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d009c8dd65e890b515a71950f7e4c801523b8894ff33863a40830bf762e9e9" checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.4.0", "bitflags",
"libc", "libc",
"libspa", "libspa",
"libspa-sys", "libspa-sys",
@@ -731,9 +791,9 @@ dependencies = [
[[package]] [[package]]
name = "pipewire-sys" name = "pipewire-sys"
version = "0.7.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "890c084e7b737246cb4799c86b71a0e4da536031ff7473dd639eba9f95039f64" checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"libspa-sys", "libspa-sys",
@@ -742,18 +802,17 @@ dependencies = [
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.27" version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
dependencies = [ dependencies = [
"once_cell", "toml_edit 0.21.1",
"toml_edit",
] ]
[[package]] [[package]]
@@ -894,9 +953,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.11.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "syn" name = "syn"
@@ -927,7 +986,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
dependencies = [ dependencies = [
"cfg-expr", "cfg-expr",
"heck", "heck 0.4.1",
"pkg-config", "pkg-config",
"toml", "toml",
"version-compare", "version-compare",
@@ -968,14 +1027,14 @@ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit", "toml_edit 0.19.15",
] ]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.3" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -993,6 +1052,17 @@ dependencies = [
"winnow", "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]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.12"
@@ -1005,6 +1075,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]] [[package]]
name = "version-compare" name = "version-compare"
version = "0.1.1" version = "0.1.1"
@@ -1039,6 +1115,72 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[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.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[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.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[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.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.15" version = "0.5.15"
@@ -1047,3 +1189,12 @@ checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "yansi-term"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi",
]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "helvum" name = "helvum"
version = "0.5.0" version = "0.5.1"
authors = ["Tom Wagner <tom.a.wagner@protonmail.com>"] authors = ["Tom Wagner <tom.a.wagner@protonmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.70" rust-version = "1.70"
@@ -14,12 +14,14 @@ categories = ["gui", "multimedia"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
pipewire = "0.7.1" pipewire = "0.8.0"
adw = { version = "0.5", package = "libadwaita", features = ["v1_3"] } adw = { version = "0.6", package = "libadwaita", features = ["v1_4"] }
glib = { version = "0.18", features = ["log"] } gtk = { version = "0.8", package = "gtk4", features = ["v4_14"] }
glib = { version = "0.19", features = ["log"] }
async-channel = "2.2"
log = "0.4.11" log = "0.4.11"
once_cell = "1.7.2" once_cell = "1.19"
libc = "0.2" libc = "0.2"

View File

@@ -23,7 +23,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: Then install the required flatpak platform and SDK, if you dont have them already:
```shell ```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: To compile and install as a flatpak, clone the project, change to the project directory, and run:

View File

@@ -1,7 +1,7 @@
{ {
"id": "org.pipewire.Helvum", "id": "org.pipewire.Helvum",
"runtime": "org.gnome.Platform", "runtime": "org.gnome.Platform",
"runtime-version": "45", "runtime-version": "46",
"sdk": "org.gnome.Sdk", "sdk": "org.gnome.Sdk",
"sdk-extensions": [ "sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable", "org.freedesktop.Sdk.Extension.rust-stable",

View File

@@ -23,6 +23,7 @@
<url type="bugtracker">https://gitlab.freedesktop.org/pipewire/helvum/-/issues</url> <url type="bugtracker">https://gitlab.freedesktop.org/pipewire/helvum/-/issues</url>
<content_rating type="oars-1.0" /> <content_rating type="oars-1.0" />
<releases> <releases>
<release version="0.5.1" date="2023-09-28" />
<release version="0.5.0" date="2023-09-28" /> <release version="0.5.0" date="2023-09-28" />
<release version="0.4.1" date="2023-08-18" /> <release version="0.4.1" date="2023-08-18" />
<release version="0.4.0" date="2023-02-12" /> <release version="0.4.0" date="2023-02-12" />

View File

@@ -1,7 +1,7 @@
project( project(
'helvum', 'helvum',
'rust', 'rust',
version: '0.5.0', version: '0.5.1',
license: 'GPL-3.0', license: 'GPL-3.0',
meson_version: '>=0.59.0' meson_version: '>=0.59.0'
) )
@@ -11,8 +11,8 @@ gnome = import('gnome')
base_id = 'org.pipewire.Helvum' base_id = 'org.pipewire.Helvum'
dependency('glib-2.0', version: '>= 2.66') dependency('glib-2.0', version: '>= 2.66')
dependency('gtk4', version: '>= 4.4.0') dependency('gtk4', version: '>= 4.14.0')
dependency('libadwaita-1', version: '>= 1.3') dependency('libadwaita-1', version: '>= 1.4')
dependency('libpipewire-0.3') dependency('libpipewire-0.3')
desktop_file_validate = find_program('desktop-file-validate', required: false) desktop_file_validate = find_program('desktop-file-validate', required: false)

View File

@@ -16,7 +16,7 @@
use adw::{ use adw::{
gio, gio,
glib::{self, clone, Receiver}, glib::{self, clone},
gtk, gtk,
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
@@ -33,8 +33,9 @@ static AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
mod imp { mod imp {
use super::*; use super::*;
use std::cell::OnceCell;
use adw::subclass::prelude::AdwApplicationImpl; use adw::subclass::prelude::AdwApplicationImpl;
use once_cell::unsync::OnceCell;
#[derive(Default)] #[derive(Default)]
pub struct Application { pub struct Application {
@@ -112,9 +113,12 @@ mod imp {
} }
fn show_about_dialog(&self) { fn show_about_dialog(&self) {
let obj = &*self.obj();
let window = obj.active_window().unwrap();
let authors: Vec<&str> = AUTHORS.split(':').collect(); let authors: Vec<&str> = AUTHORS.split(':').collect();
let about_window = adw::AboutWindow::builder() let about_window = adw::AboutWindow::builder()
.transient_for(&window)
.application_icon(APP_ID) .application_icon(APP_ID)
.application_name("Helvum") .application_name("Helvum")
.developer_name("Tom Wagner") .developer_name("Tom Wagner")
@@ -140,7 +144,7 @@ impl Application {
/// Create the view. /// Create the view.
/// This will set up the entire user interface and prepare it for being run. /// This will set up the entire user interface and prepare it for being run.
pub(super) fn new( pub(super) fn new(
gtk_receiver: Receiver<PipewireMessage>, gtk_receiver: async_channel::Receiver<PipewireMessage>,
pw_sender: Sender<GtkMessage>, pw_sender: Sender<GtkMessage>,
) -> Self { ) -> Self {
let app: Application = glib::Object::builder() let app: Application = glib::Object::builder()

View File

@@ -23,9 +23,7 @@ use crate::{ui::graph::GraphView, GtkMessage, PipewireMessage};
mod imp { mod imp {
use super::*; use super::*;
use std::{cell::RefCell, collections::HashMap}; use std::{cell::OnceCell, cell::RefCell, collections::HashMap};
use once_cell::unsync::OnceCell;
use crate::{ui::graph, MediaType, NodeType}; use crate::{ui::graph, MediaType, NodeType};
@@ -53,35 +51,58 @@ mod imp {
impl ObjectImpl for GraphManager {} impl ObjectImpl for GraphManager {}
impl GraphManager { impl GraphManager {
pub fn attach_receiver(&self, receiver: glib::Receiver<crate::PipewireMessage>) { pub async fn receive(&self, receiver: async_channel::Receiver<crate::PipewireMessage>) {
receiver.attach(None, glib::clone!( loop {
@weak self as imp => @default-return glib::ControlFlow::Continue, let Ok(msg) = receiver.recv().await else {
move |msg| { continue;
match msg { };
PipewireMessage::NodeAdded { id, name, node_type } => imp.add_node(id, name.as_str(), node_type), match msg {
PipewireMessage::PortAdded { id, node_id, name, direction } => imp.add_port(id, name.as_str(), node_id, direction), PipewireMessage::NodeAdded {
PipewireMessage::PortFormatChanged { id, media_type } => imp.port_media_type_changed(id, media_type), id,
PipewireMessage::LinkAdded { name,
id, port_from, port_to, active, media_type node_type,
} => imp.add_link(id, port_from, port_to, active, media_type), } => self.add_node(id, name.as_str(), node_type),
PipewireMessage::LinkStateChanged { id, active } => imp.link_state_changed(id, active), PipewireMessage::NodeNameChanged {
PipewireMessage::LinkFormatChanged { id, media_type } => imp.link_format_changed(id, media_type), id,
PipewireMessage::NodeRemoved { id } => imp.remove_node(id), name,
PipewireMessage::PortRemoved { id, node_id } => imp.remove_port(id, node_id), media_name,
PipewireMessage::LinkRemoved { id } => imp.remove_link(id), } => self.node_name_changed(id, &name, &media_name),
PipewireMessage::Connecting => { PipewireMessage::PortAdded {
imp.obj().connection_banner().set_revealed(true); id,
} node_id,
PipewireMessage::Connected => { name,
imp.obj().connection_banner().set_revealed(false); direction,
}, } => self.add_port(id, name.as_str(), node_id, direction),
PipewireMessage::Disconnected => { PipewireMessage::PortFormatChanged { id, media_type } => {
imp.clear(); self.port_media_type_changed(id, media_type)
}, }
}; PipewireMessage::LinkAdded {
glib::ControlFlow::Continue 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. /// Add a new node to the view.
@@ -95,6 +116,23 @@ mod imp {
self.obj().graph().add_node(node, node_type); self.obj().graph().add_node(node, node_type);
} }
/// Update a node tooltip to the view.
fn node_name_changed(&self, id: u32, node_name: &str, media_name: &str) {
let items = self.items.borrow();
let Some(node) = items.get(&id) else {
log::warn!("Node (id: {id}) for changed name not found in graph manager");
return;
};
let Some(node) = node.dynamic_cast_ref::<graph::Node>() else {
log::warn!("Graph Manager item under node (id: {id}) is not a node");
return;
};
node.set_node_name(node_name);
node.set_media_name(media_name);
}
/// Remove the node with the specified id from the view. /// Remove the node with the specified id from the view.
fn remove_node(&self, id: u32) { fn remove_node(&self, id: u32) {
log::info!("Removing node from graph: id {}", id); log::info!("Removing node from graph: id {}", id);
@@ -112,7 +150,13 @@ mod imp {
} }
/// Add a new port to the view. /// 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); log::info!("Adding port to graph: id {}", id);
let mut items = self.items.borrow_mut(); let mut items = self.items.borrow_mut();
@@ -255,7 +299,11 @@ mod imp {
link.set_active(active); 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 items = self.items.borrow();
let Some(link) = items.get(&id) else { let Some(link) = items.get(&id) else {
@@ -304,19 +352,23 @@ glib::wrapper! {
pub struct GraphManager(ObjectSubclass<imp::GraphManager>); 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 { impl GraphManager {
pub fn new( pub fn new(
graph: &GraphView, graph: &GraphView,
connection_banner: &adw::Banner, connection_banner: &adw::Banner,
sender: PwSender<GtkMessage>, sender: PwSender<GtkMessage>,
receiver: glib::Receiver<PipewireMessage>, receiver: async_channel::Receiver<PipewireMessage>,
) -> Self { ) -> Self {
let res: Self = glib::Object::builder() let res: Self = glib::Object::builder()
.property("graph", graph) .property("graph", graph)
.property("connection-banner", connection_banner) .property("connection-banner", connection_banner)
.build(); .build();
res.imp().attach_receiver(receiver); glib::MainContext::default().spawn_local(receive(res.clone(), receiver));
assert!( assert!(
res.imp().pw_sender.set(sender).is_ok(), res.imp().pw_sender.set(sender).is_ok(),
"Should be able to set pw_sender)" "Should be able to set pw_sender)"

View File

@@ -20,7 +20,7 @@ mod pipewire_connection;
mod ui; mod ui;
use adw::{gtk, prelude::*}; 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. /// Messages sent by the GTK thread to notify the pipewire thread.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -39,6 +39,11 @@ pub enum PipewireMessage {
name: String, name: String,
node_type: Option<NodeType>, node_type: Option<NodeType>,
}, },
NodeNameChanged {
id: u32,
name: String,
media_name: String,
},
PortAdded { PortAdded {
id: u32, id: u32,
node_id: u32, node_id: u32,
@@ -115,7 +120,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Start the pipewire thread with channels in both directions. // 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_sender, pw_receiver) = pipewire::channel::channel();
let pw_thread = let pw_thread =
std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver)); std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver));

View File

@@ -26,23 +26,31 @@ use std::{
use adw::glib::{self, clone}; use adw::glib::{self, clone};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use pipewire::{ use pipewire::{
link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState}, context::Context,
port::{Port, PortChangeMask, PortInfo, PortListener}, core::{Core, PW_ID_CORE},
prelude::*, keys,
properties, link::{Link, LinkChangeMask, LinkInfoRef, LinkListener, LinkState},
main_loop::MainLoop,
node::{Node, NodeInfoRef, NodeListener},
port::{Port, PortChangeMask, PortInfoRef, PortListener},
properties::properties,
registry::{GlobalObject, Registry}, registry::{GlobalObject, Registry},
spa::{ spa::{
param::{ParamInfoFlags, ParamType}, param::{ParamInfoFlags, ParamType},
ForeignDict, SpaResult, utils::dict::DictRef,
utils::result::SpaResult,
}, },
types::ObjectType, types::ObjectType,
Context, Core, MainLoop,
}; };
use crate::{GtkMessage, MediaType, NodeType, PipewireMessage}; use crate::{GtkMessage, MediaType, NodeType, PipewireMessage};
use state::{Item, State}; use state::{Item, State};
enum ProxyItem { enum ProxyItem {
Node {
_proxy: Node,
_listener: NodeListener,
},
Port { Port {
proxy: Port, proxy: Port,
_listener: PortListener, _listener: PortListener,
@@ -55,36 +63,40 @@ enum ProxyItem {
/// The "main" function of the pipewire thread. /// The "main" function of the pipewire thread.
pub(super) fn thread_main( pub(super) fn thread_main(
gtk_sender: glib::Sender<PipewireMessage>, gtk_sender: async_channel::Sender<PipewireMessage>,
mut pw_receiver: pipewire::channel::Receiver<GtkMessage>, 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 context = Rc::new(Context::new(&mainloop).expect("Failed to create context"));
let is_stopped = Rc::new(Cell::new(false)); let is_stopped = Rc::new(Cell::new(false));
let mut is_connecting = false; let mut is_connecting = false;
while !is_stopped.get() { while !is_stopped.get() {
// Try to connect // Try to connect
let core = match context.connect(None) { let core = match context.connect(Some(properties! {
"media.category" => "Manager"
})) {
Ok(core) => Rc::new(core), Ok(core) => Rc::new(core),
Err(_) => { Err(_) => {
if !is_connecting { if !is_connecting {
is_connecting = true; is_connecting = true;
gtk_sender gtk_sender
.send(PipewireMessage::Connecting) .send_blocking(PipewireMessage::Connecting)
.expect("Failed to send message"); .expect("Failed to send message");
} }
// If connection is failed, try to connect again in 200ms // If connection is failed, try to connect again in 200ms
let interval = Some(Duration::from_millis(200)); let interval = Some(Duration::from_millis(200));
let timer = mainloop.add_timer(clone!(@strong mainloop => move |_| { let timer = mainloop
mainloop.quit(); .loop_()
})); .add_timer(clone!(@strong mainloop => move |_| {
mainloop.quit();
}));
timer.update_timer(interval, None).into_result().unwrap(); timer.update_timer(interval, None).into_result().unwrap();
let receiver = pw_receiver.attach(&mainloop, { let receiver = pw_receiver.attach(mainloop.loop_(), {
clone!(@strong mainloop, @strong is_stopped => move |msg| clone!(@strong mainloop, @strong is_stopped => move |msg|
if let GtkMessage::Terminate = msg { if let GtkMessage::Terminate = msg {
// main thread requested stop // main thread requested stop
@@ -104,7 +116,7 @@ pub(super) fn thread_main(
if is_connecting { if is_connecting {
is_connecting = false; is_connecting = false;
gtk_sender gtk_sender
.send(PipewireMessage::Connected) .send_blocking(PipewireMessage::Connected)
.expect("Failed to send message"); .expect("Failed to send message");
} }
@@ -114,7 +126,7 @@ pub(super) fn thread_main(
let proxies = Rc::new(RefCell::new(HashMap::new())); let proxies = Rc::new(RefCell::new(HashMap::new()));
let state = Rc::new(RefCell::new(State::new())); let state = Rc::new(RefCell::new(State::new()));
let receiver = pw_receiver.attach(&mainloop, { let receiver = pw_receiver.attach(mainloop.loop_(), {
clone!(@strong mainloop, @weak core, @weak registry, @strong state, @strong is_stopped => move |msg| match msg { 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, &registry, &state), GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, &registry, &state),
GtkMessage::Terminate => { GtkMessage::Terminate => {
@@ -128,12 +140,12 @@ pub(super) fn thread_main(
let gtk_sender = gtk_sender.clone(); let gtk_sender = gtk_sender.clone();
let _listener = core.add_listener_local() let _listener = core.add_listener_local()
.error(clone!(@strong mainloop, @strong gtk_sender, @strong is_stopped => move |id, _seq, res, message| { .error(clone!(@strong mainloop, @strong gtk_sender, @strong is_stopped => move |id, _seq, res, message| {
if id != pipewire::PW_ID_CORE { if id != PW_ID_CORE {
return; return;
} }
if res == -libc::EPIPE { if res == -libc::EPIPE {
gtk_sender.send(PipewireMessage::Disconnected) gtk_sender.send_blocking(PipewireMessage::Disconnected)
.expect("Failed to send message"); .expect("Failed to send message");
mainloop.quit(); mainloop.quit();
} else { } else {
@@ -147,7 +159,7 @@ pub(super) fn thread_main(
.add_listener_local() .add_listener_local()
.global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state => .global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state =>
move |global| match global.type_ { move |global| match global.type_ {
ObjectType::Node => handle_node(global, &gtk_sender, &state), ObjectType::Node => handle_node(global, &gtk_sender, &registry, &proxies, &state),
ObjectType::Port => handle_port(global, &gtk_sender, &registry, &proxies, &state), ObjectType::Port => handle_port(global, &gtk_sender, &registry, &proxies, &state),
ObjectType::Link => handle_link(global, &gtk_sender, &registry, &proxies, &state), ObjectType::Link => handle_link(global, &gtk_sender, &registry, &proxies, &state),
_ => { _ => {
@@ -157,7 +169,7 @@ pub(super) fn thread_main(
)) ))
.global_remove(clone!(@strong proxies, @strong state => move |id| { .global_remove(clone!(@strong proxies, @strong state => move |id| {
if let Some(item) = state.borrow_mut().remove(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::Node { .. } => PipewireMessage::NodeRemoved {id},
Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id}, Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id},
Item::Link { .. } => PipewireMessage::LinkRemoved {id}, Item::Link { .. } => PipewireMessage::LinkRemoved {id},
@@ -178,10 +190,21 @@ 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: &DictRef) -> &str {
props
.get(&keys::NODE_DESCRIPTION)
.or_else(|| props.get(&keys::NODE_NICK))
.or_else(|| props.get(&keys::NODE_NAME))
.unwrap_or_default()
}
/// Handle a new node being added /// Handle a new node being added
fn handle_node( fn handle_node(
node: &GlobalObject<ForeignDict>, node: &GlobalObject<&DictRef>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
registry: &Rc<Registry>,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
state: &Rc<RefCell<State>>, state: &Rc<RefCell<State>>,
) { ) {
let props = node let props = node
@@ -189,15 +212,7 @@ fn handle_node(
.as_ref() .as_ref()
.expect("Node object is missing properties"); .expect("Node object is missing properties");
// Get the nicest possible name for the node, using a fallback chain of possible name attributes. let name = get_node_name(props).to_string();
let name = String::from(
props
.get("node.description")
.or_else(|| props.get("node.nick"))
.or_else(|| props.get("node.name"))
.unwrap_or_default(),
);
let media_class = |class: &str| { let media_class = |class: &str| {
if class.contains("Sink") || class.contains("Input") { if class.contains("Sink") || class.contains("Input") {
Some(NodeType::Input) Some(NodeType::Input)
@@ -222,18 +237,62 @@ fn handle_node(
state.borrow_mut().insert(node.id, Item::Node); state.borrow_mut().insert(node.id, Item::Node);
sender sender
.send(PipewireMessage::NodeAdded { .send_blocking(PipewireMessage::NodeAdded {
id: node.id, id: node.id,
name, name,
node_type, node_type,
}) })
.expect("Failed to send message"); .expect("Failed to send message");
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);
}))
.register();
proxies.borrow_mut().insert(
node.id,
ProxyItem::Node {
_proxy: proxy,
_listener: listener,
},
);
}
fn handle_node_info(
info: &NodeInfoRef,
sender: &async_channel::Sender<PipewireMessage>,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
) {
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;
};
let props = info.props().expect("NodeInfo object is missing properties");
if let Some(media_name) = props.get(&keys::MEDIA_NAME) {
let name = get_node_name(props).to_string();
sender
.send_blocking(PipewireMessage::NodeNameChanged {
id,
name,
media_name: media_name.to_string(),
})
.expect("Failed to send message");
}
} }
/// Handle a new port being added /// Handle a new port being added
fn handle_port( fn handle_port(
port: &GlobalObject<ForeignDict>, port: &GlobalObject<&DictRef>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
registry: &Rc<Registry>, registry: &Rc<Registry>,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>, proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
state: &Rc<RefCell<State>>, state: &Rc<RefCell<State>>,
@@ -264,10 +323,10 @@ fn handle_port(
} }
fn handle_port_info( fn handle_port_info(
info: &PortInfo, info: &PortInfoRef,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>, proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
state: &Rc<RefCell<State>>, state: &Rc<RefCell<State>>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
) { ) {
debug!("Received port info: {:?}", info); debug!("Received port info: {:?}", info);
@@ -308,7 +367,7 @@ fn handle_port_info(
} }
sender sender
.send(PipewireMessage::PortAdded { .send_blocking(PipewireMessage::PortAdded {
id, id,
node_id, node_id,
name, name,
@@ -321,7 +380,7 @@ fn handle_port_info(
fn handle_port_enum_format( fn handle_port_enum_format(
port_id: u32, port_id: u32,
param: Option<&pipewire::spa::pod::Pod>, param: Option<&pipewire::spa::pod::Pod>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
) { ) {
let media_type = param let media_type = param
.and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok()) .and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok())
@@ -329,7 +388,7 @@ fn handle_port_enum_format(
.unwrap_or(MediaType::Unknown); .unwrap_or(MediaType::Unknown);
sender sender
.send(PipewireMessage::PortFormatChanged { .send_blocking(PipewireMessage::PortFormatChanged {
id: port_id, id: port_id,
media_type, media_type,
}) })
@@ -338,8 +397,8 @@ fn handle_port_enum_format(
/// Handle a new link being added /// Handle a new link being added
fn handle_link( fn handle_link(
link: &GlobalObject<ForeignDict>, link: &GlobalObject<&DictRef>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
registry: &Rc<Registry>, registry: &Rc<Registry>,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>, proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
state: &Rc<RefCell<State>>, state: &Rc<RefCell<State>>,
@@ -367,9 +426,9 @@ fn handle_link(
} }
fn handle_link_info( fn handle_link_info(
info: &LinkInfo, info: &LinkInfoRef,
state: &Rc<RefCell<State>>, state: &Rc<RefCell<State>>,
sender: &glib::Sender<PipewireMessage>, sender: &async_channel::Sender<PipewireMessage>,
) { ) {
debug!("Received link info: {:?}", info); debug!("Received link info: {:?}", info);
@@ -380,7 +439,7 @@ fn handle_link_info(
// Info was an update - figure out if we should notify the gtk thread // Info was an update - figure out if we should notify the gtk thread
if info.change_mask().contains(LinkChangeMask::STATE) { if info.change_mask().contains(LinkChangeMask::STATE) {
sender sender
.send(PipewireMessage::LinkStateChanged { .send_blocking(PipewireMessage::LinkStateChanged {
id, id,
active: matches!(info.state(), LinkState::Active), active: matches!(info.state(), LinkState::Active),
}) })
@@ -388,7 +447,7 @@ fn handle_link_info(
} }
if info.change_mask().contains(LinkChangeMask::FORMAT) { if info.change_mask().contains(LinkChangeMask::FORMAT) {
sender sender
.send(PipewireMessage::LinkFormatChanged { .send_blocking(PipewireMessage::LinkFormatChanged {
id, id,
media_type: get_link_media_type(info), media_type: get_link_media_type(info),
}) })
@@ -402,7 +461,7 @@ fn handle_link_info(
state.insert(id, Item::Link { port_from, port_to }); state.insert(id, Item::Link { port_from, port_to });
sender sender
.send(PipewireMessage::LinkAdded { .send_blocking(PipewireMessage::LinkAdded {
id, id,
port_from, port_from,
port_to, port_to,
@@ -440,7 +499,7 @@ fn toggle_link(
.get_node_of_port(port_to) .get_node_of_port(port_to)
.expect("Requested port not in state"); .expect("Requested port not in state");
if let Err(e) = core.create_object::<Link, _>( if let Err(e) = core.create_object::<Link>(
"link-factory", "link-factory",
&properties! { &properties! {
"link.output.node" => node_from.to_string(), "link.output.node" => node_from.to_string(),
@@ -455,7 +514,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 let media_type = link_info
.format() .format()
.and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok()) .and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok())

View File

@@ -41,7 +41,7 @@ node {
background-color: @headerbar_bg_color; background-color: @headerbar_bg_color;
} }
node label.heading { node .node-title {
padding: 4px 7px; padding: 4px 7px;
} }
@@ -53,3 +53,20 @@ port-handle {
border-radius: 50%; border-radius: 50%;
background-color: @media-type-unknown; background-color: @media-type-unknown;
} }
button.rounded {
padding: 6px;
border-radius: 9999px;
}
entry.rounded {
border-radius: 9999px;
}
entry.rounded > :first-child {
padding-left: 12px;
}
entry.rounded > :nth-child(2) {
padding-right: 12px;
}

View File

@@ -42,8 +42,8 @@ mod imp {
use adw::gtk::gdk::{self}; use adw::gtk::gdk::{self};
use log::warn; use log::warn;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use pipewire::spa::format::MediaType; use pipewire::spa::param::format::MediaType;
use pipewire::spa::Direction; use pipewire::spa::utils::Direction;
pub struct Colors { pub struct Colors {
audio: gdk::RGBA, audio: gdk::RGBA,
@@ -93,6 +93,9 @@ mod imp {
// Memorized data for an in-progress zoom gesture // Memorized data for an in-progress zoom gesture
pub zoom_gesture_initial_zoom: Cell<Option<f64>>, pub zoom_gesture_initial_zoom: Cell<Option<f64>>,
pub zoom_gesture_anchor: Cell<Option<(f64, f64)>>, pub zoom_gesture_anchor: Cell<Option<(f64, f64)>>,
// This keeps track of an ongoing move view gesture.
pub move_view_state: Cell<(f64, f64)>,
} }
impl Default for GraphView { impl Default for GraphView {
@@ -108,6 +111,7 @@ mod imp {
port_drag_cursor: Cell::new(Point::new(0.0, 0.0)), port_drag_cursor: Cell::new(Point::new(0.0, 0.0)),
zoom_gesture_initial_zoom: Default::default(), zoom_gesture_initial_zoom: Default::default(),
zoom_gesture_anchor: Default::default(), zoom_gesture_anchor: Default::default(),
move_view_state: Default::default(),
} }
} }
} }
@@ -136,6 +140,7 @@ mod imp {
self.setup_port_drag_and_drop(); self.setup_port_drag_and_drop();
self.setup_scroll_zooming(); self.setup_scroll_zooming();
self.setup_zoom_gesture(); self.setup_zoom_gesture();
self.setup_move_view();
} }
fn dispose(&self) { fn dispose(&self) {
@@ -465,38 +470,66 @@ mod imp {
self.obj().add_controller(zoom_gesture); self.obj().add_controller(zoom_gesture);
} }
fn draw_link( fn setup_move_view(&self) {
let drag_controller = gtk::GestureDrag::new();
drag_controller.set_button(gtk::gdk::BUTTON_MIDDLE);
// TODO: set `all-scroll` cursor while dragging view
drag_controller.connect_drag_begin(|drag_controller, _, _| {
let widget = drag_controller
.widget()
.downcast::<super::GraphView>()
.unwrap();
widget.imp().move_view_state.set((0.0, 0.0));
});
drag_controller.connect_drag_update(|drag_controller, x, y| {
let widget = drag_controller
.widget()
.downcast::<super::GraphView>()
.unwrap();
let imp = widget.imp();
let state = imp.move_view_state.replace((x, y));
let delta_x = state.0 - x;
let delta_y = state.1 - y;
let hadjustment_ref = imp.hadjustment.borrow();
let vadjustment_ref = imp.vadjustment.borrow();
let hadjustment = hadjustment_ref.as_ref().unwrap();
let vadjustment = vadjustment_ref.as_ref().unwrap();
let new_hadjustment = hadjustment.value() + delta_x;
let new_vadjustment = vadjustment.value() + delta_y;
hadjustment.set_value(new_hadjustment);
vadjustment.set_value(new_vadjustment);
});
self.obj().add_controller(drag_controller);
}
fn snapshot_link(
&self, &self,
link_cr: &cairo::Context, snapshot: &gtk::Snapshot,
output_anchor: &Point, output_anchor: &Point,
input_anchor: &Point, input_anchor: &Point,
active: bool, active: bool,
color: &gdk::RGBA, color: &gdk::RGBA,
) { ) {
let output_x: f64 = output_anchor.x().into(); let output_x = output_anchor.x();
let output_y: f64 = output_anchor.y().into(); let output_y = output_anchor.y();
let input_x: f64 = input_anchor.x().into(); let input_x = input_anchor.x();
let input_y: f64 = input_anchor.y().into(); let input_y = input_anchor.y();
// Use dashed line for inactive links, full line otherwise.
if active {
link_cr.set_dash(&[], 0.0);
} else {
link_cr.set_dash(&[10.0, 5.0], 0.0);
}
link_cr.set_source_rgba(
color.red().into(),
color.green().into(),
color.blue().into(),
color.alpha().into(),
);
// If the output port is farther right than the input port and they have // If the output port is farther right than the input port and they have
// a similar y coordinate, apply a y offset to the control points // a similar y coordinate, apply a y offset to the control points
// so that the curve sticks out a bit. // so that the curve sticks out a bit.
let y_control_offset = if output_x > input_x { let y_control_offset = if output_x > input_x {
f64::max(0.0, 25.0 - (output_y - input_y).abs()) f32::max(0.0, 25.0 - (output_y - input_y).abs())
} else { } else {
0.0 0.0
}; };
@@ -504,9 +537,10 @@ mod imp {
// Place curve control offset by half the x distance between the two points. // Place curve control offset by half the x distance between the two points.
// This makes the curve scale well for varying distances between the two ports, // This makes the curve scale well for varying distances between the two ports,
// especially when the output port is farther right than the input port. // especially when the output port is farther right than the input port.
let half_x_dist = f64::abs(output_x - input_x) / 2.0; let half_x_dist = f32::abs(output_x - input_x) / 2.0;
link_cr.move_to(output_x, output_y); let path_builder = gsk::PathBuilder::new();
link_cr.curve_to( path_builder.move_to(output_x, output_y);
path_builder.cubic_to(
output_x + half_x_dist, output_x + half_x_dist,
output_y - y_control_offset, output_y - y_control_offset,
input_x - half_x_dist, input_x - half_x_dist,
@@ -514,13 +548,20 @@ mod imp {
input_x, input_x,
input_y, input_y,
); );
let path = path_builder.to_path();
if let Err(e) = link_cr.stroke() { let stroke = gsk::Stroke::new(2.0 * (self.zoom_factor.get() as f32));
warn!("Failed to draw graphview links: {}", e); // Use dashed line for inactive links, full line otherwise.
}; if active {
stroke.set_dash(&[]);
} else {
stroke.set_dash(&[10.0, 5.0]);
}
snapshot.append_stroke(&path, &stroke, color);
} }
fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context, colors: &Colors) { fn snapshot_dragged_link(&self, snapshot: &gtk::Snapshot, port: &Port, colors: &Colors) {
let Some(port_anchor) = port.compute_point(&*self.obj(), &port.link_anchor()) else { let Some(port_anchor) = port.compute_point(&*self.obj(), &port.link_anchor()) else {
return; return;
}; };
@@ -550,21 +591,10 @@ mod imp {
let color = &colors.color_for_media_type(MediaType::from_raw(port.media_type())); 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.snapshot_link(snapshot, output_anchor, input_anchor, false, color);
} }
fn snapshot_links(&self, widget: &super::GraphView, snapshot: &gtk::Snapshot) { fn snapshot_links(&self, widget: &super::GraphView, snapshot: &gtk::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,
));
link_cr.set_line_width(2.0 * self.zoom_factor.get());
let colors = Colors { let colors = Colors {
audio: widget audio: widget
.style_context() .style_context()
@@ -593,8 +623,8 @@ mod imp {
continue; continue;
}; };
self.draw_link( self.snapshot_link(
&link_cr, snapshot,
&output_anchor, &output_anchor,
&input_anchor, &input_anchor,
link.active(), link.active(),
@@ -603,7 +633,7 @@ mod imp {
} }
if let Some(port) = self.dragged_port.upgrade() { if let Some(port) = self.dragged_port.upgrade() {
self.draw_dragged_link(&port, &link_cr, &colors); self.snapshot_dragged_link(&snapshot, &port, &colors);
} }
} }

View File

@@ -15,7 +15,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use adw::{glib, prelude::*, subclass::prelude::*}; use adw::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::format::MediaType; use pipewire::spa::param::format::MediaType;
use super::Port; use super::Port;

View File

@@ -15,7 +15,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use adw::{glib, gtk, prelude::*, subclass::prelude::*}; use adw::{glib, gtk, prelude::*, subclass::prelude::*};
use pipewire::spa::Direction; use pipewire::spa::utils::Direction;
use super::Port; use super::Port;
@@ -34,15 +34,26 @@ mod imp {
#[property(get, set, construct_only)] #[property(get, set, construct_only)]
pub(super) pipewire_id: Cell<u32>, pub(super) pipewire_id: Cell<u32>,
#[property( #[property(
name = "name", type = String, name = "node-name", type = String,
get = |this: &Self| this.label.text().to_string(), get = |this: &Self| this.node_name.text().to_string(),
set = |this: &Self, val| { set = |this: &Self, val| {
this.label.set_text(val); this.node_name.set_text(val);
this.label.set_tooltip_text(Some(val)); this.node_name.set_tooltip_text(Some(val));
} }
)] )]
#[template_child] #[template_child]
pub(super) label: TemplateChild<gtk::Label>, pub(super) node_name: TemplateChild<gtk::Label>,
#[property(
name = "media-name", type = String,
get = |this: &Self| this.media_name.text().to_string(),
set = |this: &Self, val| {
this.media_name.set_text(val);
this.media_name.set_tooltip_text(Some(val));
this.media_name.set_visible(!val.is_empty());
}
)]
#[template_child]
pub(super) media_name: TemplateChild<gtk::Label>,
#[template_child] #[template_child]
pub(super) separator: TemplateChild<gtk::Separator>, pub(super) separator: TemplateChild<gtk::Separator>,
#[template_child] #[template_child]
@@ -74,8 +85,11 @@ mod imp {
fn constructed(&self) { fn constructed(&self) {
self.parent_constructed(); self.parent_constructed();
// Force left-to-right direction for the ports grid to avoid messed up UI when defaulting to right-to-left
self.port_grid.set_direction(gtk::TextDirection::Ltr);
// Display a grab cursor when the mouse is over the label so the user knows the node can be dragged. // Display a grab cursor when the mouse is over the label so the user knows the node can be dragged.
self.label self.node_name
.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); .set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
} }
@@ -141,7 +155,7 @@ glib::wrapper! {
impl Node { impl Node {
pub fn new(name: &str, pipewire_id: u32) -> Self { pub fn new(name: &str, pipewire_id: u32) -> Self {
glib::Object::builder() glib::Object::builder()
.property("name", name) .property("node-name", name)
.property("pipewire-id", pipewire_id) .property("pipewire-id", pipewire_id)
.build() .build()
} }

View File

@@ -9,14 +9,36 @@
<object class="GtkBox"> <object class="GtkBox">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkLabel" id="label"> <object class="GtkBox">
<style> <style>
<class name="heading"></class> <class name="node-title"></class>
</style> </style>
<property name="wrap">true</property> <property name="orientation">vertical</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property> <property name="spacing">1</property>
<property name="lines">2</property> <child>
<property name="max-width-chars">20</property> <object class="GtkLabel" id="node_name">
<style>
<class name="heading"></class>
</style>
<property name="wrap">true</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
<property name="lines">2</property>
<property name="max-width-chars">20</property>
</object>
</child>
<child>
<object class="GtkLabel" id="media_name">
<style>
<class name="dim-label"></class>
<class name="caption"></class>
</style>
<property name="visible">false</property>
<property name="wrap">true</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
<property name="lines">2</property>
<property name="max-width-chars">20</property>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>

View File

@@ -21,17 +21,17 @@ use adw::{
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
}; };
use pipewire::spa::Direction; use pipewire::spa::utils::Direction;
use super::PortHandle; use super::PortHandle;
mod imp { mod imp {
use super::*; use super::*;
use std::cell::Cell; use std::cell::{Cell, OnceCell};
use once_cell::{sync::Lazy, unsync::OnceCell}; use once_cell::sync::Lazy;
use pipewire::spa::{format::MediaType, Direction}; use pipewire::spa::{param::format::MediaType, utils::Direction};
/// Graphical representation of a pipewire port. /// Graphical representation of a pipewire port.
#[derive(gtk::CompositeTemplate, glib::Properties)] #[derive(gtk::CompositeTemplate, glib::Properties)]
@@ -101,6 +101,9 @@ mod imp {
fn constructed(&self) { fn constructed(&self) {
self.parent_constructed(); self.parent_constructed();
// Force left-to-right direction for the ports grid to avoid messed up UI when defaulting to right-to-left
self.obj().set_direction(gtk::TextDirection::Ltr);
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port. // Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port.
self.obj() self.obj()
.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); .set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());

View File

@@ -34,6 +34,7 @@ mod imp {
menu.append(Some("200%"), Some("win.set-zoom(2.0)")); menu.append(Some("200%"), Some("win.set-zoom(2.0)"));
menu.append(Some("300%"), Some("win.set-zoom(3.0)")); menu.append(Some("300%"), Some("win.set-zoom(3.0)"));
let popover = gtk::PopoverMenu::from_model(Some(&menu)); let popover = gtk::PopoverMenu::from_model(Some(&menu));
popover.set_position(gtk::PositionType::Top);
ZoomEntry { ZoomEntry {
graphview: Default::default(), graphview: Default::default(),

View File

@@ -1,26 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<template class="HelvumZoomEntry" parent="GtkBox"> <template class="HelvumZoomEntry" parent="GtkBox">
<child> <property name="spacing">12</property>
<object class="GtkButton" id="zoom_out_button">
<property name="icon-name">zoom-out-symbolic</property>
<property name="tooltip-text">Zoom out</property>
</object>
</child>
<child> <child>
<object class="GtkEntry" id="entry"> <object class="GtkEntry" id="entry">
<property name="secondary-icon-name">go-down-symbolic</property> <property name="secondary-icon-name">go-down-symbolic</property>
<property name="input-purpose">digits</property> <property name="input-purpose">digits</property>
<property name="max-width-chars">5</property>
<style>
<class name="osd"/>
<class name="rounded"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="zoom_out_button">
<property name="icon-name">zoom-out-symbolic</property>
<property name="tooltip-text">Zoom out</property>
<style>
<class name="osd"/>
<class name="rounded"/>
</style>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton" id="zoom_in_button"> <object class="GtkButton" id="zoom_in_button">
<property name="icon-name">zoom-in-symbolic</property> <property name="icon-name">zoom-in-symbolic</property>
<property name="tooltip-text">Zoom in</property> <property name="tooltip-text">Zoom in</property>
<style>
<class name="osd"/>
<class name="rounded"/>
</style>
</object> </object>
</child> </child>
<style>
<class name="linked"/>
</style>
</template> </template>
</interface> </interface>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<requires lib="gtk" version="4.0"/> <requires lib="gtk" version="4.0"/>
<requires lib="Adw" version="1.0"/> <requires lib="Adw" version="1.4"/>
<menu id="primary_menu"> <menu id="primary_menu">
<section> <section>
<item> <item>
@@ -15,46 +15,51 @@
<property name="default-height">720</property> <property name="default-height">720</property>
<property name="title">Helvum - Pipewire Patchbay</property> <property name="title">Helvum - Pipewire Patchbay</property>
<child> <child>
<object class="GtkBox"> <object class="AdwToolbarView">
<property name="orientation">vertical</property> <child type="top">
<child>
<object class="AdwHeaderBar" id="header_bar"> <object class="AdwHeaderBar" id="header_bar">
<child type="end"> <child type="end">
<object class="GtkBox"> <object class="GtkMenuButton">
<style> <property name="icon-name">open-menu-symbolic</property>
<class name="toolbar"></class> <property name="menu-model">primary_menu</property>
</style> </object>
</child>
</object>
</child>
<property name="content">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwBanner" id="connection_banner">
<property name="title" translatable="yes">Disconnected</property>
<property name="revealed">false</property>
</object>
</child>
<child>
<object class="GtkOverlay">
<child> <child>
<object class="GtkScrolledWindow">
<child>
<object class="HelvumGraphView" id="graph">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="HelvumZoomEntry"> <object class="HelvumZoomEntry">
<property name="zoomed-widget">graph</property> <property name="zoomed-widget">graph</property>
</object> <property name="halign">end</property>
</child> <property name="valign">end</property>
<child> <property name="margin-end">24</property>
<object class="GtkMenuButton"> <property name="margin-bottom">24</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">primary_menu</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object> </object>
</child> </property>
<child>
<object class="AdwBanner" id="connection_banner">
<property name="title" translatable="yes">Disconnected</property>
<property name="revealed">false</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="HelvumGraphView" id="graph">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</template> </template>