mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-15 19:46:10 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46b2175a78 | ||
|
|
9aeea6b108 | ||
|
|
c8f94ae302 | ||
|
|
92101d860c | ||
|
|
907ef328d2 | ||
|
|
118c1ca28c | ||
|
|
a9aec985b0 | ||
|
|
dce228ff60 | ||
|
|
24fd54affe | ||
|
|
3cd19f2d1d | ||
|
|
6d60095da8 |
141
Cargo.lock
generated
141
Cargo.lock
generated
@@ -90,8 +90,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-sys-rs",
|
||||
@@ -102,8 +102,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
@@ -112,9 +112,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.67"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
|
||||
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
@@ -220,9 +220,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "field-offset"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf539fba70056b50f40a22e0da30639518a12ee18c35807858a63b158cb6dde7"
|
||||
checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"rustc_version",
|
||||
@@ -236,24 +236,24 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
|
||||
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
|
||||
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
|
||||
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -262,22 +262,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
|
||||
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
|
||||
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
|
||||
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
@@ -293,8 +294,8 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"gio",
|
||||
@@ -304,8 +305,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
@@ -317,7 +318,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -332,7 +333,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
@@ -347,8 +348,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
@@ -363,8 +364,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
@@ -375,8 +376,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
@@ -393,8 +394,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -407,8 +408,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
@@ -422,8 +423,8 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
@@ -432,8 +433,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
@@ -442,8 +443,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
@@ -454,7 +455,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -469,7 +470,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
@@ -484,7 +485,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -506,7 +507,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gtk4-macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -521,7 +522,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
|
||||
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
@@ -538,16 +539,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "helvum"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"gtk4",
|
||||
@@ -607,9 +608,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.94"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
|
||||
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -624,7 +625,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.3.0"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
@@ -639,7 +640,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.3.0"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"system-deps",
|
||||
@@ -662,9 +663,9 @@ checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
|
||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -713,8 +714,8 @@ checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"glib",
|
||||
@@ -725,8 +726,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
@@ -764,7 +765,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.3.0"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
@@ -781,7 +782,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.3.0"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
|
||||
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
@@ -830,9 +831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
|
||||
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
@@ -910,9 +911,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.125"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
@@ -1027,18 +1028,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.24"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
|
||||
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.24"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
|
||||
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helvum"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Tom A. Wagner <tom.a.wagner@protonmail.com>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
@@ -11,7 +11,9 @@ More suggestions are welcome!
|
||||
|
||||
# Distribution packages
|
||||
|
||||
- ArchLinux: [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
|
||||
- ArchLinux:
|
||||
- [aur/helvum](https://aur.archlinux.org/packages/helvum)
|
||||
- [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
|
||||
|
||||
# Building
|
||||
For compilation, you will need:
|
||||
|
||||
@@ -14,41 +14,37 @@ Helvum uses an architecture with the components laid out like this:
|
||||
│ View │
|
||||
└────┬─┘
|
||||
Λ ┆
|
||||
│<───── updates view
|
||||
│ ┆
|
||||
│ ┆<─ notifies of user input
|
||||
│ ┆ (using signals)
|
||||
│ ┆
|
||||
│ ┆
|
||||
│ V notifies of remote changes
|
||||
┌┴────────────┐ via messages ┌───────────────────┐
|
||||
│<───── updates view ┌───────┐
|
||||
│ ┆ │ State │
|
||||
│ ┆<─ notifies of user input └───────┘
|
||||
│ ┆ (using signals) Λ
|
||||
│ ┆ │
|
||||
│ ┆ │<─── updates/reads state
|
||||
│ V notifies of remote changes │
|
||||
┌┴────────────┐ via messages ┌─────────┴─────────┐
|
||||
│ Application │<╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ Seperate │
|
||||
│ Object ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌>│ Pipewire Thread │
|
||||
└┬────────────┘ request changes to remote └───────────────────┘
|
||||
│ via messages Λ
|
||||
│ ║
|
||||
│<─── updates/reads state ║
|
||||
│ ║
|
||||
V ║
|
||||
┌───────┐ V
|
||||
│ State │ [ Remote Pipewire Server ]
|
||||
└───────┘
|
||||
└─────────────┘ request changes to remote └───────────────────┘
|
||||
via messages Λ
|
||||
║
|
||||
║
|
||||
V
|
||||
[ Remote Pipewire Server ]
|
||||
```
|
||||
The program is split between two threads, with most stuff happening inside the GTK thread.
|
||||
The GTK thread will sit in a GTK event processing loop, while the pipewire thread will sit in a
|
||||
pipewire event processing loop.
|
||||
The program is split between two cooperating threads.
|
||||
The GTK thread (displayed on the left side) will sit in a GTK event processing loop, while the pipewire thread (displayed on the right side) will sit in a pipewire event processing loop.
|
||||
|
||||
The `Application` object inside the GTK thread is the centerpiece of this architecture.
|
||||
It communicates with the pipewire thread using two channels,
|
||||
The `Application` object inside the GTK thread communicates with the pipewire thread using two channels,
|
||||
where each message sent by one thread will trigger the loop of the other thread to invoke a callback
|
||||
with the received message.
|
||||
|
||||
For each change on the remote pipewire server, the `Application` in the GTK thread is notified by the pipewire thread
|
||||
and updates the view to reflect those changes, and additionally memorizes anything it might need later in the state.
|
||||
For each change on the remote pipewire server, the pipewire thread updates the state and notifies
|
||||
the `Application` in the GTK thread if changes are needed.
|
||||
The `Application` then updates the view to reflect those changes.
|
||||
|
||||
Additionally, a user may also make changes using the view.
|
||||
For each change, the view notifies the `Application` by emitting a matching signal.
|
||||
The `Application` will then request the pipewire thread to make those changes on the remote. \
|
||||
The `Application` will then ask the pipewire thread to make those changes on the remote. \
|
||||
These changes will then be applied to the view like any other remote changes as explained above.
|
||||
|
||||
# View Architecture
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use gtk::{
|
||||
gio,
|
||||
@@ -11,33 +11,10 @@ use pipewire::{channel::Sender, spa::Direction};
|
||||
|
||||
use crate::{
|
||||
view::{self},
|
||||
GtkMessage, PipewireLink, PipewireMessage,
|
||||
GtkMessage, MediaType, PipewireLink, PipewireMessage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MediaType {
|
||||
Audio,
|
||||
Video,
|
||||
Midi,
|
||||
}
|
||||
|
||||
// FIXME: This should be in its own .css file.
|
||||
static STYLE: &str = "
|
||||
.audio {
|
||||
background: rgb(50,100,240);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.video {
|
||||
background: rgb(200,200,0);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.midi {
|
||||
background: rgb(200,0,50);
|
||||
color: black;
|
||||
}
|
||||
";
|
||||
static STYLE: &str = include_str!("style.css");
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
@@ -47,7 +24,6 @@ mod imp {
|
||||
#[derive(Default)]
|
||||
pub struct Application {
|
||||
pub(super) graphview: view::GraphView,
|
||||
pub(super) state: RefCell<State>,
|
||||
pub(super) pw_sender: OnceCell<RefCell<Sender<GtkMessage>>>,
|
||||
}
|
||||
|
||||
@@ -132,19 +108,12 @@ impl Application {
|
||||
@weak app => @default-return Continue(true),
|
||||
move |msg| {
|
||||
match msg {
|
||||
PipewireMessage::NodeAdded {
|
||||
id,
|
||||
name,
|
||||
media_type,
|
||||
} => app.add_node(id, name, media_type),
|
||||
PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
direction,
|
||||
} => app.add_port(id, name, node_id, direction),
|
||||
PipewireMessage::LinkAdded { id, link } => app.add_link(id, link),
|
||||
PipewireMessage::ObjectRemoved { id } => app.remove_global(id),
|
||||
PipewireMessage::NodeAdded{ id, name } => app.add_node(id,name),
|
||||
PipewireMessage::PortAdded{ id, node_id, name, direction, media_type} => app.add_port(id,name,node_id,direction,media_type),
|
||||
PipewireMessage::LinkAdded{ id, node_from, port_from, node_to, port_to} => app.add_link(id,node_from,port_from,node_to,port_to),
|
||||
PipewireMessage::NodeRemoved { id } => app.remove_node(id),
|
||||
PipewireMessage::PortRemoved { id, node_id } => app.remove_port(id, node_id),
|
||||
PipewireMessage::LinkRemoved { id } => app.remove_link(id)
|
||||
};
|
||||
Continue(true)
|
||||
}
|
||||
@@ -155,40 +124,27 @@ impl Application {
|
||||
}
|
||||
|
||||
/// Add a new node to the view.
|
||||
pub fn add_node(&self, id: u32, name: String, media_type: Option<MediaType>) {
|
||||
pub fn add_node(&self, id: u32, name: String) {
|
||||
info!("Adding node to graph: id {}", id);
|
||||
|
||||
let imp = imp::Application::from_instance(self);
|
||||
|
||||
imp.state.borrow_mut().insert(
|
||||
id,
|
||||
Item::Node {
|
||||
// widget: node_widget,
|
||||
media_type,
|
||||
},
|
||||
);
|
||||
|
||||
imp.graphview.add_node(id, view::Node::new(name.as_str()));
|
||||
imp::Application::from_instance(self)
|
||||
.graphview
|
||||
.add_node(id, view::Node::new(name.as_str()));
|
||||
}
|
||||
|
||||
/// Add a new port to the view.
|
||||
pub fn add_port(&self, id: u32, name: String, node_id: u32, direction: Direction) {
|
||||
pub fn add_port(
|
||||
&self,
|
||||
id: u32,
|
||||
name: String,
|
||||
node_id: u32,
|
||||
direction: Direction,
|
||||
media_type: Option<MediaType>,
|
||||
) {
|
||||
info!("Adding port to graph: id {}", id);
|
||||
|
||||
let imp = imp::Application::from_instance(self);
|
||||
|
||||
// Find out the nodes media type so that the port can be colored.
|
||||
let media_type =
|
||||
if let Some(Item::Node { media_type, .. }) = imp.state.borrow().get(node_id) {
|
||||
media_type.to_owned()
|
||||
} else {
|
||||
warn!("Node not found for Port {}", id);
|
||||
None
|
||||
};
|
||||
|
||||
// Save node_id so we can delete this port easily.
|
||||
imp.state.borrow_mut().insert(id, Item::Port { node_id });
|
||||
|
||||
let port = view::Port::new(id, name.as_str(), direction, media_type);
|
||||
|
||||
// Create or delete a link if the widget emits the "port-toggled" signal.
|
||||
@@ -212,77 +168,30 @@ impl Application {
|
||||
}
|
||||
|
||||
/// Add a new link to the view.
|
||||
pub fn add_link(&self, id: u32, link: PipewireLink) {
|
||||
pub fn add_link(&self, id: u32, node_from: u32, port_from: u32, node_to: u32, port_to: u32) {
|
||||
info!("Adding link to graph: id {}", id);
|
||||
|
||||
let imp = imp::Application::from_instance(self);
|
||||
|
||||
// FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are.
|
||||
|
||||
imp.state.borrow_mut().insert(
|
||||
// Update graph to contain the new link.
|
||||
imp::Application::from_instance(self).graphview.add_link(
|
||||
id,
|
||||
Item::Link {
|
||||
output_port: link.port_from,
|
||||
input_port: link.port_to,
|
||||
PipewireLink {
|
||||
node_from,
|
||||
port_from,
|
||||
node_to,
|
||||
port_to,
|
||||
},
|
||||
);
|
||||
|
||||
// Update graph to contain the new link.
|
||||
imp.graphview.add_link(id, link);
|
||||
}
|
||||
|
||||
// Toggle a link between the two specified ports on the remote pipewire server.
|
||||
fn toggle_link(&self, port_from: u32, port_to: u32) {
|
||||
let imp = imp::Application::from_instance(self);
|
||||
let sender = imp.pw_sender.get().expect("pw_sender not set").borrow_mut();
|
||||
let state = imp.state.borrow_mut();
|
||||
|
||||
if let Some(id) = state.get_link_id(port_from, port_to) {
|
||||
info!("Requesting removal of link with id {}", id);
|
||||
|
||||
sender
|
||||
.send(GtkMessage::DestroyGlobal(id))
|
||||
.send(GtkMessage::ToggleLink { port_from, port_to })
|
||||
.expect("Failed to send message");
|
||||
} else {
|
||||
info!(
|
||||
"Requesting creation of link from port id:{} to port id:{}",
|
||||
port_from, port_to
|
||||
);
|
||||
|
||||
let node_from = state
|
||||
.get_node_of_port(port_from)
|
||||
.expect("Requested port not in state");
|
||||
let node_to = state
|
||||
.get_node_of_port(port_to)
|
||||
.expect("Requested port not in state");
|
||||
|
||||
sender
|
||||
.send(GtkMessage::CreateLink(PipewireLink {
|
||||
node_from,
|
||||
port_from,
|
||||
node_to,
|
||||
port_to,
|
||||
}))
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a global object being removed.
|
||||
pub fn remove_global(&self, id: u32) {
|
||||
let imp = imp::Application::from_instance(self);
|
||||
|
||||
if let Some(item) = imp.state.borrow_mut().remove(id) {
|
||||
match item {
|
||||
Item::Node { .. } => self.remove_node(id),
|
||||
Item::Port { node_id } => self.remove_port(id, node_id),
|
||||
Item::Link { .. } => self.remove_link(id),
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Attempted to remove item with id {} that is not saved in state",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the node with the specified id from the view.
|
||||
@@ -310,83 +219,3 @@ impl Application {
|
||||
imp.graphview.remove_link(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Any pipewire item we need to keep track of.
|
||||
/// These will be saved in the [`Application`]s `state` struct associated with their id.
|
||||
enum Item {
|
||||
Node {
|
||||
// Keep track of the nodes media type to color ports on it.
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
Port {
|
||||
// Save the id of the node this is on so we can remove the port from it
|
||||
// when it is deleted.
|
||||
node_id: u32,
|
||||
},
|
||||
// We don't need to memorize anything about links right now, but we need to
|
||||
// be able to find out an id is a link.
|
||||
Link {
|
||||
output_port: u32,
|
||||
input_port: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// This struct keeps track of any relevant items and stores them under their IDs.
|
||||
///
|
||||
/// Given two port ids, it can also efficiently find the id of the link that connects them.
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
/// Map pipewire ids to items.
|
||||
items: HashMap<u32, Item>,
|
||||
/// Map `(output port id, input port id)` tuples to the id of the link that connects them.
|
||||
links: HashMap<(u32, u32), u32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Add a new item under the specified id.
|
||||
fn insert(&mut self, id: u32, item: Item) {
|
||||
if let Item::Link {
|
||||
output_port,
|
||||
input_port,
|
||||
} = item
|
||||
{
|
||||
self.links.insert((output_port, input_port), id);
|
||||
}
|
||||
|
||||
self.items.insert(id, item);
|
||||
}
|
||||
|
||||
/// Get the item that has the specified id.
|
||||
fn get(&self, id: u32) -> Option<&Item> {
|
||||
self.items.get(&id)
|
||||
}
|
||||
|
||||
/// Get the id of the link that links the two specified ports.
|
||||
fn get_link_id(&self, output_port: u32, input_port: u32) -> Option<u32> {
|
||||
self.links.get(&(output_port, input_port)).copied()
|
||||
}
|
||||
|
||||
/// Remove the item with the specified id, returning it if it exists.
|
||||
fn remove(&mut self, id: u32) -> Option<Item> {
|
||||
let removed = self.items.remove(&id);
|
||||
|
||||
if let Some(Item::Link {
|
||||
output_port,
|
||||
input_port,
|
||||
}) = removed
|
||||
{
|
||||
self.links.remove(&(output_port, input_port));
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
/// Convenience function: Get the id of the node a port is on
|
||||
fn get_node_of_port(&self, port: u32) -> Option<u32> {
|
||||
if let Some(Item::Port { node_id }) = self.get(port) {
|
||||
Some(*node_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
src/main.rs
47
src/main.rs
@@ -2,44 +2,59 @@ mod application;
|
||||
mod pipewire_connection;
|
||||
mod view;
|
||||
|
||||
use application::MediaType;
|
||||
use gtk::{
|
||||
glib::{self, PRIORITY_DEFAULT},
|
||||
prelude::*,
|
||||
};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
/// Messages used GTK thread to command the pipewire thread.
|
||||
/// Messages sent by the GTK thread to notify the pipewire thread.
|
||||
#[derive(Debug, Clone)]
|
||||
enum GtkMessage {
|
||||
/// Create a new link.
|
||||
CreateLink(PipewireLink),
|
||||
/// Destroy the global with the specified id.
|
||||
DestroyGlobal(u32),
|
||||
/// Toggle a link between the two specified ports.
|
||||
ToggleLink { port_from: u32, port_to: u32 },
|
||||
/// Quit the event loop and let the thread finish.
|
||||
Terminate,
|
||||
}
|
||||
|
||||
/// Messages used pipewire thread to notify the GTK thread.
|
||||
/// Messages sent by the pipewire thread to notify the GTK thread.
|
||||
#[derive(Debug, Clone)]
|
||||
enum PipewireMessage {
|
||||
/// A new node has appeared.
|
||||
NodeAdded {
|
||||
id: u32,
|
||||
name: String,
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
/// A new port has appeared.
|
||||
PortAdded {
|
||||
id: u32,
|
||||
node_id: u32,
|
||||
name: String,
|
||||
direction: Direction,
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
/// A new link has appeared.
|
||||
LinkAdded { id: u32, link: PipewireLink },
|
||||
/// An object was removed
|
||||
ObjectRemoved { id: u32 },
|
||||
LinkAdded {
|
||||
id: u32,
|
||||
node_from: u32,
|
||||
port_from: u32,
|
||||
node_to: u32,
|
||||
port_to: u32,
|
||||
},
|
||||
NodeRemoved {
|
||||
id: u32,
|
||||
},
|
||||
PortRemoved {
|
||||
id: u32,
|
||||
node_id: u32,
|
||||
},
|
||||
LinkRemoved {
|
||||
id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MediaType {
|
||||
Audio,
|
||||
Video,
|
||||
Midi,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -54,6 +69,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::init();
|
||||
gtk::init()?;
|
||||
|
||||
// Aquire main context so that we can attach the gtk channel later.
|
||||
let ctx = glib::MainContext::default();
|
||||
let _guard = ctx.acquire().unwrap();
|
||||
|
||||
// Start the pipewire thread with channels in both directions.
|
||||
let (gtk_sender, gtk_receiver) = glib::MainContext::channel(PRIORITY_DEFAULT);
|
||||
let (pw_sender, pw_receiver) = pipewire::channel::channel();
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
use std::rc::Rc;
|
||||
mod state;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use gtk::glib::{self, clone};
|
||||
use log::warn;
|
||||
use log::{debug, info, warn};
|
||||
use pipewire::{
|
||||
link::Link,
|
||||
link::{Link, LinkListener},
|
||||
prelude::*,
|
||||
properties,
|
||||
registry::GlobalObject,
|
||||
registry::{GlobalObject, Registry},
|
||||
spa::{Direction, ForeignDict},
|
||||
types::ObjectType,
|
||||
Context, MainLoop,
|
||||
Context, Core, MainLoop,
|
||||
};
|
||||
|
||||
use crate::{application::MediaType, GtkMessage, PipewireMessage};
|
||||
use crate::{GtkMessage, MediaType, PipewireMessage};
|
||||
use state::{Item, State};
|
||||
|
||||
enum ProxyItem {
|
||||
Link {
|
||||
_proxy: Link,
|
||||
_listener: LinkListener,
|
||||
},
|
||||
}
|
||||
|
||||
/// The "main" function of the pipewire thread.
|
||||
pub(super) fn thread_main(
|
||||
@@ -21,59 +31,60 @@ pub(super) fn thread_main(
|
||||
) {
|
||||
let mainloop = MainLoop::new().expect("Failed to create mainloop");
|
||||
let context = Context::new(&mainloop).expect("Failed to create context");
|
||||
let core = context.connect(None).expect("Failed to connect to remote");
|
||||
let core = Rc::new(context.connect(None).expect("Failed to connect to remote"));
|
||||
let registry = Rc::new(core.get_registry().expect("Failed to get registry"));
|
||||
|
||||
// Keep proxies and their listeners alive so that we can receive info events.
|
||||
let proxies = Rc::new(RefCell::new(HashMap::new()));
|
||||
|
||||
let state = Rc::new(RefCell::new(State::new()));
|
||||
|
||||
let _receiver = pw_receiver.attach(&mainloop, {
|
||||
let mainloop = mainloop.clone();
|
||||
clone!(@weak registry => move |msg| match msg {
|
||||
GtkMessage::CreateLink(link) => {
|
||||
if let Err(e) = core.create_object::<Link, _>(
|
||||
"link-factory",
|
||||
&properties! {
|
||||
"link.output.node" => link.node_from.to_string(),
|
||||
"link.output.port" => link.port_from.to_string(),
|
||||
"link.input.node" => link.node_to.to_string(),
|
||||
"link.input.port" => link.port_to.to_string(),
|
||||
"object.linger" => "1"
|
||||
},
|
||||
) {
|
||||
warn!("Failed to create link: {}", e);
|
||||
}
|
||||
}
|
||||
GtkMessage::DestroyGlobal(id) => {
|
||||
// FIXME: Handle error
|
||||
registry.destroy_global(id);
|
||||
}
|
||||
clone!(@strong mainloop, @weak core, @weak registry, @strong state => move |msg| match msg {
|
||||
GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, ®istry, &state),
|
||||
GtkMessage::Terminate => mainloop.quit(),
|
||||
})
|
||||
});
|
||||
|
||||
let _listener = registry
|
||||
.add_listener_local()
|
||||
.global({
|
||||
let sender = gtk_sender.clone();
|
||||
.global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state =>
|
||||
move |global| match global.type_ {
|
||||
ObjectType::Node => handle_node(global, &sender),
|
||||
ObjectType::Port => handle_port(global, &sender),
|
||||
ObjectType::Link => handle_link(global, &sender),
|
||||
ObjectType::Node => handle_node(global, >k_sender, &state),
|
||||
ObjectType::Port => handle_port(global, >k_sender, &state),
|
||||
ObjectType::Link => handle_link(global, >k_sender, ®istry, &proxies, &state),
|
||||
_ => {
|
||||
// Other objects are not interesting to us
|
||||
}
|
||||
}
|
||||
})
|
||||
.global_remove(move |id| {
|
||||
gtk_sender
|
||||
.send(PipewireMessage::ObjectRemoved { id })
|
||||
.expect("Failed to send message")
|
||||
})
|
||||
))
|
||||
.global_remove(clone!(@strong proxies, @strong state => move |id| {
|
||||
if let Some(item) = state.borrow_mut().remove(id) {
|
||||
gtk_sender.send(match item {
|
||||
Item::Node { .. } => PipewireMessage::NodeRemoved {id},
|
||||
Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id},
|
||||
Item::Link { .. } => PipewireMessage::LinkRemoved {id},
|
||||
}).expect("Failed to send message");
|
||||
} else {
|
||||
warn!(
|
||||
"Attempted to remove item with id {} that is not saved in state",
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
proxies.borrow_mut().remove(&id);
|
||||
}))
|
||||
.register();
|
||||
|
||||
mainloop.run();
|
||||
}
|
||||
|
||||
/// Handle a new node being added
|
||||
fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
|
||||
fn handle_node(
|
||||
node: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
let props = node
|
||||
.props
|
||||
.as_ref()
|
||||
@@ -88,7 +99,7 @@ fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
|
||||
// FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead.
|
||||
let media_type = props
|
||||
.get("media.class")
|
||||
.map(|class| {
|
||||
@@ -104,17 +115,25 @@ fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
|
||||
})
|
||||
.flatten();
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeAdded {
|
||||
id: node.id,
|
||||
name,
|
||||
state.borrow_mut().insert(
|
||||
node.id,
|
||||
Item::Node {
|
||||
// widget: node_widget,
|
||||
media_type,
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeAdded { id: node.id, name })
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
|
||||
/// Handle a new port being added
|
||||
fn handle_port(port: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
|
||||
fn handle_port(
|
||||
port: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
let props = port
|
||||
.props
|
||||
.as_ref()
|
||||
@@ -131,52 +150,124 @@ fn handle_port(port: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
|
||||
Direction::Output
|
||||
};
|
||||
|
||||
// Find out the nodes media type so that the port can be colored.
|
||||
let media_type = if let Some(Item::Node { media_type, .. }) = state.borrow().get(node_id) {
|
||||
media_type.to_owned()
|
||||
} else {
|
||||
warn!("Node not found for Port {}", port.id);
|
||||
None
|
||||
};
|
||||
|
||||
// Save node_id so we can delete this port easily.
|
||||
state.borrow_mut().insert(port.id, Item::Port { node_id });
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortAdded {
|
||||
id: port.id,
|
||||
node_id,
|
||||
name,
|
||||
direction,
|
||||
media_type,
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
|
||||
/// Handle a new link being added
|
||||
fn handle_link(link: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
|
||||
let props = link
|
||||
.props
|
||||
.as_ref()
|
||||
.expect("Link object is missing properties");
|
||||
let node_from: u32 = props
|
||||
.get("link.output.node")
|
||||
.expect("Link has no link.input.node property")
|
||||
.parse()
|
||||
.expect("Could not parse link.input.node property");
|
||||
let port_from: u32 = props
|
||||
.get("link.output.port")
|
||||
.expect("Link has no link.output.port property")
|
||||
.parse()
|
||||
.expect("Could not parse link.output.port property");
|
||||
let node_to: u32 = props
|
||||
.get("link.input.node")
|
||||
.expect("Link has no link.input.node property")
|
||||
.parse()
|
||||
.expect("Could not parse link.input.node property");
|
||||
let port_to: u32 = props
|
||||
.get("link.input.port")
|
||||
.expect("Link has no link.input.port property")
|
||||
.parse()
|
||||
.expect("Could not parse link.input.port property");
|
||||
fn handle_link(
|
||||
link: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
debug!(
|
||||
"New link (id:{}) appeared, setting up info listener.",
|
||||
link.id
|
||||
);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::LinkAdded {
|
||||
id: link.id,
|
||||
link: crate::PipewireLink {
|
||||
let proxy: Link = registry.bind(link).expect("Failed to bind to link proxy");
|
||||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(clone!(@strong state, @strong sender => move |info| {
|
||||
debug!("Received link info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(Item::Link { .. }) = state.get(id) {
|
||||
// Info was an update - figure out if we should notify the gtk thread
|
||||
// TODO
|
||||
} else {
|
||||
// First time we get info. We can now notify the gtk thread of a new link.
|
||||
let node_from = info.output_node_id();
|
||||
let port_from = info.output_port_id();
|
||||
let node_to = info.input_node_id();
|
||||
let port_to = info.input_port_id();
|
||||
|
||||
state.insert(id, Item::Link {
|
||||
port_from, port_to
|
||||
});
|
||||
|
||||
sender.send(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
node_from,
|
||||
port_from,
|
||||
node_to,
|
||||
port_to,
|
||||
},
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
port_to
|
||||
}).expect(
|
||||
"Failed to send message"
|
||||
);
|
||||
}
|
||||
}))
|
||||
.register();
|
||||
|
||||
proxies.borrow_mut().insert(
|
||||
link.id,
|
||||
ProxyItem::Link {
|
||||
_proxy: proxy,
|
||||
_listener: listener,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Toggle a link between the two specified ports.
|
||||
fn toggle_link(
|
||||
port_from: u32,
|
||||
port_to: u32,
|
||||
core: &Rc<Core>,
|
||||
registry: &Rc<Registry>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
let state = state.borrow_mut();
|
||||
if let Some(id) = state.get_link_id(port_from, port_to) {
|
||||
info!("Requesting removal of link with id {}", id);
|
||||
|
||||
// FIXME: Handle error
|
||||
registry.destroy_global(id);
|
||||
} else {
|
||||
info!(
|
||||
"Requesting creation of link from port id:{} to port id:{}",
|
||||
port_from, port_to
|
||||
);
|
||||
|
||||
let node_from = state
|
||||
.get_node_of_port(port_from)
|
||||
.expect("Requested port not in state");
|
||||
let node_to = state
|
||||
.get_node_of_port(port_to)
|
||||
.expect("Requested port not in state");
|
||||
|
||||
if let Err(e) = core.create_object::<Link, _>(
|
||||
"link-factory",
|
||||
&properties! {
|
||||
"link.output.node" => node_from.to_string(),
|
||||
"link.output.port" => port_from.to_string(),
|
||||
"link.input.node" => node_to.to_string(),
|
||||
"link.input.port" => port_to.to_string(),
|
||||
"object.linger" => "1"
|
||||
},
|
||||
) {
|
||||
warn!("Failed to create link: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
src/pipewire_connection/state.rs
Normal file
81
src/pipewire_connection/state.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::MediaType;
|
||||
|
||||
/// Any pipewire item we need to keep track of.
|
||||
/// These will be saved in the `State` struct associated with their id.
|
||||
pub(super) enum Item {
|
||||
Node {
|
||||
// Keep track of the nodes media type to color ports on it.
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
Port {
|
||||
// Save the id of the node this is on so we can remove the port from it
|
||||
// when it is deleted.
|
||||
node_id: u32,
|
||||
},
|
||||
Link {
|
||||
port_from: u32,
|
||||
port_to: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// This struct keeps track of any relevant items and stores them under their IDs.
|
||||
///
|
||||
/// Given two port ids, it can also efficiently find the id of the link that connects them.
|
||||
#[derive(Default)]
|
||||
pub(super) struct State {
|
||||
/// Map pipewire ids to items.
|
||||
items: HashMap<u32, Item>,
|
||||
/// Map `(output port id, input port id)` tuples to the id of the link that connects them.
|
||||
links: HashMap<(u32, u32), u32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Create a new, empty state.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Add a new item under the specified id.
|
||||
pub fn insert(&mut self, id: u32, item: Item) {
|
||||
if let Item::Link {
|
||||
port_from, port_to, ..
|
||||
} = item
|
||||
{
|
||||
self.links.insert((port_from, port_to), id);
|
||||
}
|
||||
|
||||
self.items.insert(id, item);
|
||||
}
|
||||
|
||||
/// Get the item that has the specified id.
|
||||
pub fn get(&self, id: u32) -> Option<&Item> {
|
||||
self.items.get(&id)
|
||||
}
|
||||
|
||||
/// Get the id of the link that links the two specified ports.
|
||||
pub fn get_link_id(&self, output_port: u32, input_port: u32) -> Option<u32> {
|
||||
self.links.get(&(output_port, input_port)).copied()
|
||||
}
|
||||
|
||||
/// Remove the item with the specified id, returning it if it exists.
|
||||
pub fn remove(&mut self, id: u32) -> Option<Item> {
|
||||
let removed = self.items.remove(&id);
|
||||
|
||||
if let Some(Item::Link { port_from, port_to }) = removed {
|
||||
self.links.remove(&(port_from, port_to));
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
|
||||
/// Convenience function: Get the id of the node a port is on
|
||||
pub fn get_node_of_port(&self, port: u32) -> Option<u32> {
|
||||
if let Some(Item::Port { node_id }) = self.get(port) {
|
||||
Some(*node_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/style.css
Normal file
14
src/style.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.audio {
|
||||
background: rgb(50,100,240);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.video {
|
||||
background: rgb(200,200,0);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.midi {
|
||||
background: rgb(200,0,50);
|
||||
color: black;
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
use super::Node;
|
||||
use super::{Node, Port};
|
||||
|
||||
use gtk::{gdk, glib, graphene, gsk, prelude::*, subclass::prelude::*};
|
||||
use gtk::{
|
||||
glib::{self, clone},
|
||||
graphene, gsk,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -15,7 +20,6 @@ mod imp {
|
||||
pub struct GraphView {
|
||||
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
||||
pub(super) links: RefCell<HashMap<u32, crate::PipewireLink>>,
|
||||
pub(super) dragged: Rc<RefCell<Option<gtk::Widget>>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@@ -34,28 +38,47 @@ mod imp {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
// Move the Node that is currently being dragged to the cursor position as long as Mouse Button 1 is held.
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
motion_controller.connect_motion(|controller, x, y| {
|
||||
let instance = controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.unwrap();
|
||||
let this = imp::GraphView::from_instance(&instance);
|
||||
let drag_state = Rc::new(RefCell::new(None));
|
||||
let drag_controller = gtk::GestureDrag::new();
|
||||
|
||||
if let Some(ref widget) = *this.dragged.borrow() {
|
||||
if controller
|
||||
.current_event()
|
||||
.unwrap()
|
||||
.modifier_state()
|
||||
.contains(gdk::ModifierType::BUTTON1_MASK)
|
||||
{
|
||||
instance.move_node(&widget, x as f32, y as f32);
|
||||
drag_controller.connect_drag_begin(
|
||||
clone!(@strong drag_state => move |drag_controller, x, y| {
|
||||
let mut drag_state = drag_state.borrow_mut();
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.expect("drag-begin event has no widget")
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.expect("drag-begin event is not on the GraphView");
|
||||
// pick() should at least return the widget itself.
|
||||
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("drag-begin pick() did not return a widget");
|
||||
*drag_state = if target.ancestor(Port::static_type()).is_some() {
|
||||
// The user targeted a port, so the dragging should be handled by the Port
|
||||
// component instead of here.
|
||||
None
|
||||
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
||||
// The user targeted a Node without targeting a specific Port.
|
||||
// Drag the Node around the screen.
|
||||
let (x, y) = widget.get_node_position(&target);
|
||||
Some((target, x, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
});
|
||||
obj.add_controller(&motion_controller);
|
||||
}));
|
||||
drag_controller.connect_drag_update(
|
||||
clone!(@strong drag_state => move |drag_controller, x, y| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.expect("drag-update event has no widget")
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.expect("drag-update event is not on the GraphView");
|
||||
let drag_state = drag_state.borrow();
|
||||
if let Some((ref node, x1, y1)) = *drag_state {
|
||||
widget.move_node(node, x1 + x as f32, y1 + y as f32);
|
||||
}
|
||||
}
|
||||
),
|
||||
);
|
||||
obj.add_controller(&drag_controller);
|
||||
}
|
||||
|
||||
fn dispose(&self, _obj: &Self::Type) {
|
||||
@@ -249,8 +272,20 @@ impl GraphView {
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
pub(super) fn set_dragged(&self, widget: Option<gtk::Widget>) {
|
||||
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
|
||||
pub(super) fn get_node_position(&self, node: >k::Widget) -> (f32, f32) {
|
||||
let layout_manager = self
|
||||
.layout_manager()
|
||||
.expect("Failed to get layout manager")
|
||||
.dynamic_cast::<gtk::FixedLayout>()
|
||||
.expect("Failed to cast to FixedLayout");
|
||||
|
||||
let node = layout_manager
|
||||
.layout_child(node)
|
||||
.expect("Could not get layout child")
|
||||
.dynamic_cast::<gtk::FixedLayoutChild>()
|
||||
.expect("Could not cast to FixedLayoutChild");
|
||||
let transform = node.transform().unwrap_or_default();
|
||||
transform.to_translate()
|
||||
}
|
||||
|
||||
pub(super) fn move_node(&self, node: >k::Widget, x: f32, y: f32) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use super::graph_view::GraphView;
|
||||
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
@@ -34,35 +32,6 @@ mod imp {
|
||||
|
||||
grid.attach(&label, 0, 0, 2, 1);
|
||||
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
motion_controller.connect_enter(|controller, _, _| {
|
||||
// Tell the graphview that the Node is the target of a drag when the mouse enters its label
|
||||
let widget = controller
|
||||
.widget()
|
||||
.expect("Controller with enter event has no widget")
|
||||
.ancestor(super::Node::static_type())
|
||||
.expect("Node label does not have a node ancestor widget");
|
||||
widget
|
||||
.ancestor(GraphView::static_type())
|
||||
.expect("Node with enter event is not on graph")
|
||||
.dynamic_cast::<GraphView>()
|
||||
.unwrap()
|
||||
.set_dragged(Some(widget));
|
||||
});
|
||||
motion_controller.connect_leave(|controller| {
|
||||
// Tell the graphview that the Node is no longer the target of a drag when the mouse leaves.
|
||||
// FIXME: Check that we are the current target before setting none.
|
||||
controller
|
||||
.widget()
|
||||
.expect("Controller with leave event has no widget")
|
||||
.ancestor(GraphView::static_type())
|
||||
.expect("Node with leave event is not on graph")
|
||||
.dynamic_cast::<GraphView>()
|
||||
.unwrap()
|
||||
.set_dragged(None);
|
||||
});
|
||||
label.add_controller(&motion_controller);
|
||||
|
||||
// Display a grab cursor when the mouse is over the label so the user knows the node can be dragged.
|
||||
label.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use gtk::{
|
||||
use log::warn;
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
use crate::application::MediaType;
|
||||
use crate::MediaType;
|
||||
|
||||
mod imp {
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
|
||||
Reference in New Issue
Block a user