19 Commits
0.2.1 ... 0.3.0

Author SHA1 Message Date
Tom A. Wagner
2ee7bca68a Release 0.3.0 2021-08-08 09:51:55 +02:00
Tom A. Wagner
494d3a383f Update dependencies 2021-08-08 09:51:42 +02:00
Tom A. Wagner
b719e0d2ec Add simple flatpak manifest 2021-07-22 11:35:12 +02:00
Tom A. Wagner
f64a936dd9 Update dependencies 2021-07-18 09:58:39 +02:00
Tom A. Wagner
179665778d view: Draw a dashed line for links that are not active 2021-07-08 13:37:37 +02:00
er888kh
be9339472e First nodes that GraphView::add_node creates, get pushed
to the left side of the screen. So by adding a constant offset,
we can avoid having possible links that are partly out of the view
just after starting the program
2021-07-06 09:39:34 +00:00
Tom A. Wagner
e29ffdeea8 view: Port: Do not inherit from gtk::Button
We do not need any button functionality, so it is more appropriate to inherit directly from gtk::Widget
2021-07-06 11:01:20 +02:00
Tom A. Wagner
add1a96b75 Deduplicate port dragging code and add some extra logging 2021-07-06 10:23:20 +02:00
Tom A. Wagner
9445e173f9 view: ports: Improve linking between ports.
Links can now created by dragging in both directions, so now from an input port to an output port, too.
The drag-n-drop handlers now use dedicated types for each direction, so that no mismatched things can be dropped on each other.
2021-06-28 13:54:53 +02:00
Tom A. Wagner
58794fe123 Remove unneeded Rc, Port is now a refcounted gobject 2021-06-28 13:29:46 +02:00
Tom A. Wagner
8d6fbe2997 Remove outdated doc comment 2021-06-28 13:29:46 +02:00
Tom A. Wagner
b0bf5e5281 Slightly improve logging 2021-06-28 13:29:46 +02:00
Tom A. Wagner
edc4064009 graphview: Place link curve control points more dynamically
Control points are now offset by half the x distance of the start and end points instead of a constant,
which makes the curve scale better with varying distance.
2021-06-28 10:48:09 +02:00
Tom A. Wagner
58cdcd859b graphview: Draw links in front of nodes, instead of behind them. 2021-06-26 12:34:48 +02:00
Tom A. Wagner
7977481689 Fix some pedantic clippy warnings 2021-06-26 12:10:36 +02:00
Tom A. Wagner
f09fd596c8 Update dependencies§ 2021-06-25 15:40:17 +02:00
Tom A. Wagner
1247b29bae Use pipewire from crates.io
The newest release contains everything we need, so we can use the crates.io version again.
2021-06-25 15:38:54 +02:00
Tom A. Wagner
74ffb06b40 Use gtk4-rs from crates.io instead from their git.
The gtk4 crate finally had its first release, so we no longer have to use their git repository directly.
2021-06-25 15:06:41 +02:00
Tom A. Wagner
81467154d9 Readme.md: Replace "Packages" section with repology badge, add license section 2021-06-07 16:03:50 +02:00
12 changed files with 459 additions and 240 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/.flatpak-builder
/.vscode /.vscode
/target /target

262
Cargo.lock generated
View File

@@ -1,10 +1,12 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -20,9 +22,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.40" version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@@ -49,15 +51,15 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.58.1" version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"clap", "clap",
"env_logger", "env_logger 0.8.4",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"log", "log",
@@ -90,8 +92,9 @@ dependencies = [
[[package]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.14.0" version = "0.14.1"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a408c13bbc04c3337b94194c1a4d04067097439b79dbc1dcbceba299d828b9ea"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cairo-sys-rs", "cairo-sys-rs",
@@ -103,7 +106,8 @@ dependencies = [
[[package]] [[package]]
name = "cairo-sys-rs" name = "cairo-sys-rs"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c9c3928781e8a017ece15eace05230f04b647457d170d2d9641c94a444ff80"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -112,24 +116,24 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.68" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
[[package]] [[package]]
name = "cexpr" name = "cexpr"
version = "0.4.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89"
dependencies = [ dependencies = [
"nom 5.1.2", "nom",
] ]
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.7.4" 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 = "30aa9e2ffbb838c6b451db14f3cd8e63ed622bf859f9956bc93845a10fafc26a" checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e"
dependencies = [ dependencies = [
"smallvec", "smallvec",
] ]
@@ -186,9 +190,22 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.8.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [ dependencies = [
"atty", "atty",
"humantime", "humantime",
@@ -236,24 +253,24 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
dependencies = [ dependencies = [
"futures-core", "futures-core",
] ]
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@@ -262,21 +279,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.15" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"futures-core", "futures-core",
@@ -295,7 +312,8 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]] [[package]]
name = "gdk-pixbuf" name = "gdk-pixbuf"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534192cb8f01daeb8fab2c8d4baa8f9aae5b7a39130525779f5c2608e235b10f"
dependencies = [ dependencies = [
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
"gio", "gio",
@@ -306,7 +324,8 @@ dependencies = [
[[package]] [[package]]
name = "gdk-pixbuf-sys" name = "gdk-pixbuf-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f097c0704201fbc8f69c1762dc58c6947c8bb188b8ed0bc7e65259f1894fe590"
dependencies = [ dependencies = [
"gio-sys", "gio-sys",
"glib-sys", "glib-sys",
@@ -317,8 +336,9 @@ dependencies = [
[[package]] [[package]]
name = "gdk4" name = "gdk4"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce41092cc569129a0afa34926e6dd1cf8411e25652d87febdea36859f7ff7ba"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cairo-rs", "cairo-rs",
@@ -332,8 +352,9 @@ dependencies = [
[[package]] [[package]]
name = "gdk4-sys" name = "gdk4-sys"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce39c71861b5bcde319fd4711a74e1bd6f4f474911170d51096597fef0b56011"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
@@ -349,7 +370,8 @@ dependencies = [
[[package]] [[package]]
name = "gio" name = "gio"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86c6823b39d46d22cac2466de261f28d7f049ebc18f7b35296a42c7ed8a88325"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"futures-channel", "futures-channel",
@@ -365,7 +387,8 @@ dependencies = [
[[package]] [[package]]
name = "gio-sys" name = "gio-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
@@ -376,8 +399,9 @@ dependencies = [
[[package]] [[package]]
name = "glib" name = "glib"
version = "0.14.0" version = "0.14.2"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbecad7a3a898ee749d491ce2ae0decb0bce9e736f9747bc49159b1cea5d37f4"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"futures-channel", "futures-channel",
@@ -394,8 +418,9 @@ dependencies = [
[[package]] [[package]]
name = "glib-macros" name = "glib-macros"
version = "0.14.0" version = "0.14.1"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"heck", "heck",
@@ -409,7 +434,8 @@ dependencies = [
[[package]] [[package]]
name = "glib-sys" name = "glib-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae"
dependencies = [ dependencies = [
"libc", "libc",
"system-deps", "system-deps",
@@ -424,7 +450,8 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "gobject-sys" name = "gobject-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -434,7 +461,8 @@ dependencies = [
[[package]] [[package]]
name = "graphene-rs" name = "graphene-rs"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1460a39f06e491e6112f27e71e51435c833ba370723224dd1743dfd1f201f19"
dependencies = [ dependencies = [
"glib", "glib",
"graphene-sys", "graphene-sys",
@@ -444,7 +472,8 @@ dependencies = [
[[package]] [[package]]
name = "graphene-sys" name = "graphene-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d23fb7a9547e5f072a7e0cd49cd648fedeb786d122b106217511980cbb8962"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"libc", "libc",
@@ -454,8 +483,9 @@ dependencies = [
[[package]] [[package]]
name = "gsk4" name = "gsk4"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64932b730eaad3340378a03d633616eeed6d6705b59b81c9f579c88be8932475"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cairo-rs", "cairo-rs",
@@ -469,8 +499,9 @@ dependencies = [
[[package]] [[package]]
name = "gsk4-sys" name = "gsk4-sys"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685ffc776bedd91d68f47b41239525778b669432889721d7050d045270549b9a"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk4-sys", "gdk4-sys",
@@ -484,8 +515,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4" name = "gtk4"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c49e0311dac847a8ebc05e31f5c44c596314ee3b16c5f638ccfe24086d24bf1b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cairo-rs", "cairo-rs",
@@ -506,8 +538,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4-macros" name = "gtk4-macros"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe4b77996bcf1ef20208c00043edda854ca2091b4be5e6a7c367f0f3846fa67"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"heck", "heck",
@@ -521,8 +554,9 @@ dependencies = [
[[package]] [[package]]
name = "gtk4-sys" name = "gtk4-sys"
version = "0.1.0" version = "0.2.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3737e91619cf4257d8a07834f7a2c035d4daeaf9ad8e3958e56b2c411dbdca18"
dependencies = [ dependencies = [
"cairo-sys-rs", "cairo-sys-rs",
"gdk-pixbuf-sys", "gdk-pixbuf-sys",
@@ -548,9 +582,9 @@ dependencies = [
[[package]] [[package]]
name = "helvum" name = "helvum"
version = "0.2.1" version = "0.3.0"
dependencies = [ dependencies = [
"env_logger", "env_logger 0.9.0",
"gtk4", "gtk4",
"log", "log",
"once_cell", "once_cell",
@@ -559,9 +593,9 @@ dependencies = [
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.18" version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -574,9 +608,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [ dependencies = [
"either", "either",
] ]
@@ -608,9 +642,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.95" version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -624,8 +658,9 @@ dependencies = [
[[package]] [[package]]
name = "libspa" name = "libspa"
version = "0.3.0" version = "0.4.1"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb373e8b03740369c5fe48a557c6408b6898982d57e17940de144375d472743"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
@@ -633,14 +668,15 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"libspa-sys", "libspa-sys",
"nom 6.1.2", "nom",
"system-deps", "system-deps",
] ]
[[package]] [[package]]
name = "libspa-sys" name = "libspa-sys"
version = "0.3.0" version = "0.4.1"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301a2fc2fed0a97c13836408a4d98f419af0c2695ecf74e634a214c17beefa6"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"system-deps", "system-deps",
@@ -657,9 +693,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.0" version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@@ -685,19 +721,9 @@ dependencies = [
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "nom"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"funty", "funty",
@@ -708,14 +734,15 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.7.2" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "415823a4fb9f1789785cd6e2d2413816f2ecff92380382969aaca9c400e13a19"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"glib", "glib",
@@ -727,7 +754,8 @@ dependencies = [
[[package]] [[package]]
name = "pango-sys" name = "pango-sys"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2367099ca5e761546ba1d501955079f097caa186bb53ce0f718dca99ac1942fe"
dependencies = [ dependencies = [
"glib-sys", "glib-sys",
"gobject-sys", "gobject-sys",
@@ -752,9 +780,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.6" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@@ -764,8 +792,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pipewire" name = "pipewire"
version = "0.3.0" version = "0.4.1"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de050d879e7b8d9313429ec314b88b26fe48ba29a6ecc3bc8289d3673fee6c8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags",
@@ -781,8 +810,9 @@ dependencies = [
[[package]] [[package]]
name = "pipewire-sys" name = "pipewire-sys"
version = "0.3.0" version = "0.4.1"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4aa5ef9f3afef7dbb335106f69bd6bb541259e8796c693810cde20db1eb949"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"libspa-sys", "libspa-sys",
@@ -831,9 +861,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.27" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@@ -855,9 +885,9 @@ checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.5.4" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -911,9 +941,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.126" version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
[[package]] [[package]]
name = "shlex" name = "shlex"
@@ -933,9 +963,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@@ -957,15 +987,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.20.1" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -975,9 +1005,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.72" version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -986,9 +1016,9 @@ dependencies = [
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "3.1.1" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c248107ad7bc1ac07066a4d003cae9e9a7bc2e27d3418f7a9cdcdc8699dbea70" checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cfg-expr", "cfg-expr",
@@ -1028,18 +1058,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.25" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.25" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1063,9 +1093,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.7.1" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "helvum" name = "helvum"
version = "0.2.1" version = "0.3.0"
authors = ["Tom A. Wagner <tom.a.wagner@protonmail.com>"] authors = ["Tom A. Wagner <tom.a.wagner@protonmail.com>"]
edition = "2018" edition = "2018"
license = "GPL-3.0-only" license = "GPL-3.0-only"
@@ -13,10 +13,10 @@ 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]
gtk = { git = "https://github.com/gtk-rs/gtk4-rs/", package = "gtk4" } pipewire = "0.4"
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs", branch = "main" } gtk = { version = "0.2", package = "gtk4" }
log = "0.4.11" log = "0.4.11"
env_logger = "0.8.2" env_logger = "0.9.0"
once_cell = "1.7.2" once_cell = "1.7.2"

View File

@@ -2,6 +2,9 @@ Helvum is a GTK-based patchbay for pipewire, inspired by the JACK tool [catia](h
![Screenshot](screenshot.png) ![Screenshot](screenshot.png)
[![Packaging status](https://repology.org/badge/vertical-allrepos/helvum.svg)](https://repology.org/project/helvum/versions)
# Features planned # Features planned
- Volume control - Volume control
@@ -9,13 +12,27 @@ Helvum is a GTK-based patchbay for pipewire, inspired by the JACK tool [catia](h
More suggestions are welcome! More suggestions are welcome!
# Distribution packages
- ArchLinux:
- [aur/helvum](https://aur.archlinux.org/packages/helvum)
- [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
# Building # Building
## Via flatpak (recommended)
The recommended way to build is using flatpak, which will take care of all dependencies and avoid any problems that may come from different system configurations.
First, install the required flatpak platform and SDK, if you dont have them already:
```shell
$ flatpak install org.gnome.{Platform,Sdk}//40 org.freedesktop.Sdk.Extension.rust-stable//20.08
```
To compile and install as a flatpak, run
```shell
$ flatpak-builder --install flatpak-build/ org.freedesktop.ryuukyu.Helvum.json
```
You can then run the app via
```shell
flatpak run org.freedesktop.ryuukyu.Helvum
```
## Manually
For compilation, you will need: For compilation, you will need:
- An up-to-date rust toolchain - An up-to-date rust toolchain
@@ -28,3 +45,7 @@ To compile, run
in the repository root. in the repository root.
The resulting binary will be at `target/release/helvum`. The resulting binary will be at `target/release/helvum`.
# License
Helvum is distributed under the terms of the GPL3 license.
See LICENSE for more information.

View File

@@ -0,0 +1,36 @@
{
"app-id": "org.freedesktop.ryuukyu.Helvum",
"runtime": "org.gnome.Platform",
"runtime-version": "40",
"sdk": "org.gnome.Sdk",
"sdk-extensions": ["org.freedesktop.Sdk.Extension.rust-stable"],
"command": "helvum",
"finish-args" : [
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
"--share=ipc",
"--filesystem=xdg-run/pipewire-0"
],
"build-options" : {
"append-path" : "/usr/lib/sdk/rust-stable/bin",
"build-args" : [
"--share=network"
]
},
"modules": [
{
"name": "Helvum",
"buildsystem": "simple",
"build-commands": [
"cargo install --path . --root /app --no-track"
],
"sources": [
{
"type": "dir",
"path": "./"
}
]
}
]
}

View File

@@ -83,7 +83,7 @@ impl Application {
pw_sender: Sender<GtkMessage>, pw_sender: Sender<GtkMessage>,
) -> Self { ) -> Self {
let app: Application = let app: Application =
glib::Object::new(&[("application-id", &"org.freedesktop.ryuukyu.helvum")]) glib::Object::new(&[("application-id", &"org.freedesktop.ryuukyu.Helvum")])
.expect("Failed to create new Application"); .expect("Failed to create new Application");
let imp = imp::Application::from_instance(&app); let imp = imp::Application::from_instance(&app);
@@ -108,9 +108,10 @@ impl Application {
@weak app => @default-return Continue(true), @weak app => @default-return Continue(true),
move |msg| { move |msg| {
match msg { match msg {
PipewireMessage::NodeAdded{ id, name } => app.add_node(id,name), PipewireMessage::NodeAdded{ id, name } => app.add_node(id, name.as_str()),
PipewireMessage::PortAdded{ id, node_id, name, direction, media_type} => app.add_port(id,name,node_id,direction,media_type), PipewireMessage::PortAdded{ id, node_id, name, direction, media_type } => app.add_port(id, name.as_str(), 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::LinkAdded{ id, node_from, port_from, node_to, port_to, active} => app.add_link(id, node_from, port_from, node_to, port_to, active),
PipewireMessage::LinkStateChanged { id, active } => app.link_state_changed(id, active), // TODO
PipewireMessage::NodeRemoved { id } => app.remove_node(id), PipewireMessage::NodeRemoved { id } => app.remove_node(id),
PipewireMessage::PortRemoved { id, node_id } => app.remove_port(id, node_id), PipewireMessage::PortRemoved { id, node_id } => app.remove_port(id, node_id),
PipewireMessage::LinkRemoved { id } => app.remove_link(id) PipewireMessage::LinkRemoved { id } => app.remove_link(id)
@@ -124,19 +125,19 @@ impl Application {
} }
/// Add a new node to the view. /// Add a new node to the view.
pub fn add_node(&self, id: u32, name: String) { fn add_node(&self, id: u32, name: &str) {
info!("Adding node to graph: id {}", id); info!("Adding node to graph: id {}", id);
imp::Application::from_instance(self) imp::Application::from_instance(self)
.graphview .graphview
.add_node(id, view::Node::new(name.as_str())); .add_node(id, view::Node::new(name));
} }
/// Add a new port to the view. /// Add a new port to the view.
pub fn add_port( fn add_port(
&self, &self,
id: u32, id: u32,
name: String, name: &str,
node_id: u32, node_id: u32,
direction: Direction, direction: Direction,
media_type: Option<MediaType>, media_type: Option<MediaType>,
@@ -145,7 +146,7 @@ impl Application {
let imp = imp::Application::from_instance(self); let imp = imp::Application::from_instance(self);
let port = view::Port::new(id, name.as_str(), direction, media_type); let port = view::Port::new(id, name, direction, media_type);
// Create or delete a link if the widget emits the "port-toggled" signal. // Create or delete a link if the widget emits the "port-toggled" signal.
if let Err(e) = port.connect_local( if let Err(e) = port.connect_local(
@@ -168,7 +169,15 @@ impl Application {
} }
/// Add a new link to the view. /// Add a new link to the view.
pub fn add_link(&self, id: u32, node_from: u32, port_from: u32, node_to: u32, port_to: u32) { fn add_link(
&self,
id: u32,
node_from: u32,
port_from: u32,
node_to: u32,
port_to: u32,
active: bool,
) {
info!("Adding link to graph: id {}", id); info!("Adding link to graph: id {}", id);
// FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are. // FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are.
@@ -182,9 +191,22 @@ impl Application {
node_to, node_to,
port_to, port_to,
}, },
active,
); );
} }
fn link_state_changed(&self, id: u32, active: bool) {
info!(
"Link state changed: Link (id={}) is now {}",
id,
if active { "active" } else { "inactive" }
);
imp::Application::from_instance(self)
.graphview
.set_link_state(id, active);
}
// Toggle a link between the two specified ports on the remote pipewire server. // Toggle a link between the two specified ports on the remote pipewire server.
fn toggle_link(&self, port_from: u32, port_to: u32) { fn toggle_link(&self, port_from: u32, port_to: u32) {
let imp = imp::Application::from_instance(self); let imp = imp::Application::from_instance(self);

View File

@@ -37,6 +37,11 @@ enum PipewireMessage {
port_from: u32, port_from: u32,
node_to: u32, node_to: u32,
port_to: u32, port_to: u32,
active: bool,
},
LinkStateChanged {
id: u32,
active: bool,
}, },
NodeRemoved { NodeRemoved {
id: u32, id: u32,

View File

@@ -5,7 +5,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
use gtk::glib::{self, clone}; use gtk::glib::{self, clone};
use log::{debug, info, warn}; use log::{debug, info, warn};
use pipewire::{ use pipewire::{
link::{Link, LinkListener}, link::{Link, LinkChangeMask, LinkListener, LinkState},
prelude::*, prelude::*,
properties, properties,
registry::{GlobalObject, Registry}, registry::{GlobalObject, Registry},
@@ -100,20 +100,17 @@ fn handle_node(
); );
// FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead. // FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead.
let media_type = props let media_type = props.get("media.class").and_then(|class| {
.get("media.class") if class.contains("Audio") {
.map(|class| { Some(MediaType::Audio)
if class.contains("Audio") { } else if class.contains("Video") {
Some(MediaType::Audio) Some(MediaType::Video)
} else if class.contains("Video") { } else if class.contains("Midi") {
Some(MediaType::Video) Some(MediaType::Midi)
} else if class.contains("Midi") { } else {
Some(MediaType::Midi) None
} else { }
None });
}
})
.flatten();
state.borrow_mut().insert( state.borrow_mut().insert(
node.id, node.id,
@@ -196,7 +193,13 @@ fn handle_link(
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
if let Some(Item::Link { .. }) = state.get(id) { if let Some(Item::Link { .. }) = state.get(id) {
// 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
// TODO if info.change_mask().contains(LinkChangeMask::STATE) {
sender.send(PipewireMessage::LinkStateChanged {
id,
active: matches!(info.state(), LinkState::Active)
}).expect("Failed to send message");
}
// TODO -- check other values that might have changed
} else { } else {
// First time we get info. We can now notify the gtk thread of a new link. // First time we get info. We can now notify the gtk thread of a new link.
let node_from = info.output_node_id(); let node_from = info.output_node_id();
@@ -213,7 +216,8 @@ fn handle_link(
node_from, node_from,
port_from, port_from,
node_to, node_to,
port_to port_to,
active: matches!(info.state(), LinkState::Active)
}).expect( }).expect(
"Failed to send message" "Failed to send message"
); );

View File

@@ -34,7 +34,7 @@ pub(super) struct State {
impl State { impl State {
/// Create a new, empty state. /// Create a new, empty state.
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Self::default()
} }
/// Add a new item under the specified id. /// Add a new item under the specified id.

View File

@@ -6,6 +6,7 @@ use gtk::{
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
}; };
use log::{error, warn};
use std::collections::HashMap; use std::collections::HashMap;
@@ -19,7 +20,8 @@ mod imp {
#[derive(Default)] #[derive(Default)]
pub struct GraphView { pub struct GraphView {
pub(super) nodes: RefCell<HashMap<u32, Node>>, pub(super) nodes: RefCell<HashMap<u32, Node>>,
pub(super) links: RefCell<HashMap<u32, crate::PipewireLink>>, /// Stores the link and whether it is currently active.
pub(super) links: RefCell<HashMap<u32, (crate::PipewireLink, bool)>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@@ -96,7 +98,7 @@ mod imp {
let alloc = widget.allocation(); let alloc = widget.allocation();
let cr = snapshot let background_cr = snapshot
.append_cairo(&graphene::Rect::new( .append_cairo(&graphene::Rect::new(
0.0, 0.0,
0.0, 0.0,
@@ -107,51 +109,79 @@ mod imp {
// Try to replace the background color with a darker one from the theme. // Try to replace the background color with a darker one from the theme.
if let Some(rgba) = widget.style_context().lookup_color("text_view_bg") { if let Some(rgba) = widget.style_context().lookup_color("text_view_bg") {
cr.set_source_rgb(rgba.red.into(), rgba.green.into(), rgba.blue.into()); background_cr.set_source_rgb(rgba.red.into(), rgba.green.into(), rgba.blue.into());
if let Err(e) = cr.paint() { if let Err(e) = background_cr.paint() {
warn!("Failed to paint graphview background: {}", e); warn!("Failed to paint graphview background: {}", e);
}; };
} // TODO: else log colour not found } // TODO: else log colour not found
// Draw a nice grid on the background. // Draw a nice grid on the background.
cr.set_source_rgb(0.18, 0.18, 0.18); background_cr.set_source_rgb(0.18, 0.18, 0.18);
cr.set_line_width(0.2); // TODO: Set to 1px background_cr.set_line_width(0.2); // TODO: Set to 1px
let mut y = 0.0; let mut y = 0.0;
while y < alloc.height.into() { while y < alloc.height.into() {
cr.move_to(0.0, y); background_cr.move_to(0.0, y);
cr.line_to(alloc.width as f64, y); background_cr.line_to(alloc.width.into(), y);
y += 20.0; // TODO: Change to em; y += 20.0; // TODO: Change to em;
} }
let mut x = 0.0; let mut x = 0.0;
while x < alloc.width as f64 { while x < alloc.width.into() {
cr.move_to(x, 0.0); background_cr.move_to(x, 0.0);
cr.line_to(x, alloc.height as f64); background_cr.line_to(x, alloc.height.into());
x += 20.0; // TODO: Change to em; x += 20.0; // TODO: Change to em;
} }
if let Err(e) = cr.stroke() { if let Err(e) = background_cr.stroke() {
warn!("Failed to draw graphview grid: {}", e); warn!("Failed to draw graphview grid: {}", e);
}; };
// Draw all links
cr.set_line_width(2.0);
cr.set_source_rgb(0.0, 0.0, 0.0);
for link in self.links.borrow().values() {
if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) {
cr.move_to(from_x, from_y);
cr.curve_to(from_x + 75.0, from_y, to_x - 75.0, to_y, to_x, to_y);
if let Err(e) = cr.stroke() {
warn!("Failed to draw graphview links: {}", e);
};
} else {
log::warn!("Could not get allocation of ports of link: {:?}", link);
}
}
// Draw all children // Draw all children
self.nodes self.nodes
.borrow() .borrow()
.values() .values()
.for_each(|node| self.instance().snapshot_child(node, snapshot)); .for_each(|node| self.instance().snapshot_child(node, snapshot));
// Draw all links
let link_cr = snapshot
.append_cairo(&graphene::Rect::new(
0.0,
0.0,
alloc.width as f32,
alloc.height as f32,
))
.expect("Failed to get cairo context");
link_cr.set_line_width(2.0);
link_cr.set_source_rgb(0.0, 0.0, 0.0);
for (link, active) in self.links.borrow().values() {
if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) {
link_cr.move_to(from_x, from_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);
}
// 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,
// especially when the output port is farther right than the input port.
let half_x_dist = f64::abs(from_x - to_x) / 2.0;
link_cr.curve_to(
from_x + half_x_dist,
from_y,
to_x - half_x_dist,
to_y,
to_x,
to_y,
);
if let Err(e) = link_cr.stroke() {
warn!("Failed to draw graphview links: {}", e);
};
} else {
warn!("Could not get allocation of ports of link: {:?}", link);
}
}
} }
} }
@@ -159,7 +189,7 @@ mod imp {
/// Get coordinates for the drawn link to start at and to end at. /// Get coordinates for the drawn link to start at and to end at.
/// ///
/// # Returns /// # Returns
/// Some((from_x, from_y, to_x, to_y)) if all objects the links refers to exist as widgets. /// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets.
fn get_link_coordinates(&self, link: &crate::PipewireLink) -> Option<(f64, f64, f64, f64)> { fn get_link_coordinates(&self, link: &crate::PipewireLink) -> Option<(f64, f64, f64, f64)> {
let nodes = self.nodes.borrow(); let nodes = self.nodes.borrow();
@@ -194,7 +224,7 @@ mod imp {
tx += tnx; tx += tnx;
ty += tny + (th / 2); ty += tny + (th / 2);
Some((fx as f64, fy as f64, tx as f64, ty as f64)) Some((fx.into(), fy.into(), tx.into(), ty.into()))
} }
} }
} }
@@ -215,8 +245,8 @@ impl GraphView {
// Place widgets in colums of 4, growing down, then right. // Place widgets in colums of 4, growing down, then right.
// TODO: Make a better positioning algorithm. // TODO: Make a better positioning algorithm.
let x = (private.nodes.borrow().len() / 4) as f32 * 400.0; // This relies on integer division rounding down. let x = ((private.nodes.borrow().len() / 4) as f32 * 400.0) + 20.0; // This relies on integer division rounding down.
let y = private.nodes.borrow().len() as f32 % 4.0 * 100.0; let y = (private.nodes.borrow().len() as f32 % 4.0 * 100.0) + 20.0;
self.move_node(&node.clone().upcast(), x, y); self.move_node(&node.clone().upcast(), x, y);
@@ -228,6 +258,8 @@ impl GraphView {
let mut nodes = private.nodes.borrow_mut(); let mut nodes = private.nodes.borrow_mut();
if let Some(node) = nodes.remove(&id) { if let Some(node) = nodes.remove(&id) {
node.unparent(); node.unparent();
} else {
warn!("Tried to remove non-existant node (id={}) from graph", id);
} }
} }
@@ -237,11 +269,9 @@ impl GraphView {
if let Some(node) = private.nodes.borrow_mut().get_mut(&node_id) { if let Some(node) = private.nodes.borrow_mut().get_mut(&node_id) {
node.add_port(port_id, port); node.add_port(port_id, port);
} else { } else {
// FIXME: Log this instead error!(
log::error!(
"Node with id {} not found when trying to add port with id {} to graph", "Node with id {} not found when trying to add port with id {} to graph",
node_id, node_id, port_id
port_id
); );
} }
} }
@@ -254,16 +284,22 @@ impl GraphView {
} }
} }
/// Add a link to the graph. pub fn add_link(&self, link_id: u32, link: crate::PipewireLink, active: bool) {
///
/// `add_link` takes three arguments: `link_id` is the id of the link as assigned by the pipewire server,
/// `from` and `to` are the id's of the ingoing and outgoing port, respectively.
pub fn add_link(&self, link_id: u32, link: crate::PipewireLink) {
let private = imp::GraphView::from_instance(self); let private = imp::GraphView::from_instance(self);
private.links.borrow_mut().insert(link_id, link); private.links.borrow_mut().insert(link_id, (link, active));
self.queue_draw(); self.queue_draw();
} }
pub fn set_link_state(&self, link_id: u32, active: bool) {
let private = imp::GraphView::from_instance(self);
if let Some((_, state)) = private.links.borrow_mut().get_mut(&link_id) {
*state = active;
self.queue_draw();
} else {
warn!("Link state changed on unknown link (id={})", link_id);
}
}
pub fn remove_link(&self, id: u32) { pub fn remove_link(&self, id: u32) {
let private = imp::GraphView::from_instance(self); let private = imp::GraphView::from_instance(self);
let mut links = private.links.borrow_mut(); let mut links = private.links.borrow_mut();

View File

@@ -1,7 +1,7 @@
use gtk::{glib, prelude::*, subclass::prelude::*}; use gtk::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::Direction; use pipewire::spa::Direction;
use std::{collections::HashMap, rc::Rc}; use std::collections::HashMap;
mod imp { mod imp {
use super::*; use super::*;
@@ -11,9 +11,9 @@ mod imp {
pub struct Node { pub struct Node {
pub(super) grid: gtk::Grid, pub(super) grid: gtk::Grid,
pub(super) label: gtk::Label, pub(super) label: gtk::Label,
pub(super) ports: RefCell<HashMap<u32, Rc<crate::view::port::Port>>>, pub(super) ports: RefCell<HashMap<u32, crate::view::port::Port>>,
pub(super) num_ports_in: Cell<u32>, pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<u32>, pub(super) num_ports_out: Cell<i32>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@@ -81,21 +81,21 @@ impl Node {
Direction::Input => { Direction::Input => {
private private
.grid .grid
.attach(&port, 0, private.num_ports_in.get() as i32 + 1, 1, 1); .attach(&port, 0, private.num_ports_in.get() + 1, 1, 1);
private.num_ports_in.set(private.num_ports_in.get() + 1); private.num_ports_in.set(private.num_ports_in.get() + 1);
} }
Direction::Output => { Direction::Output => {
private private
.grid .grid
.attach(&port, 1, private.num_ports_out.get() as i32 + 1, 1, 1); .attach(&port, 1, private.num_ports_out.get() + 1, 1, 1);
private.num_ports_out.set(private.num_ports_out.get() + 1); private.num_ports_out.set(private.num_ports_out.get() + 1);
} }
} }
private.ports.borrow_mut().insert(id, Rc::new(port)); private.ports.borrow_mut().insert(id, port);
} }
pub fn get_port(&self, id: u32) -> Option<Rc<super::port::Port>> { pub fn get_port(&self, id: u32) -> Option<super::port::Port> {
let private = imp::Node::from_instance(self); let private = imp::Node::from_instance(self);
private.ports.borrow_mut().get(&id).cloned() private.ports.borrow_mut().get(&id).cloned()
} }

View File

@@ -1,14 +1,26 @@
use gtk::{ use gtk::{
gdk, gdk,
glib::{self, subclass::Signal}, glib::{self, clone, subclass::Signal},
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
}; };
use log::warn; use log::{trace, warn};
use pipewire::spa::Direction; use pipewire::spa::Direction;
use crate::MediaType; use crate::MediaType;
/// A helper struct for linking a output port to an input port.
/// It carries the output ports id.
#[derive(Clone, Debug, glib::GBoxed)]
#[gboxed(type_name = "HelvumForwardLink")]
struct ForwardLink(u32);
/// A helper struct for linking an input to an output port.
/// It carries the input ports id.
#[derive(Clone, Debug, glib::GBoxed)]
#[gboxed(type_name = "HelvumReversedLink")]
struct ReversedLink(u32);
mod imp { mod imp {
use once_cell::{sync::Lazy, unsync::OnceCell}; use once_cell::{sync::Lazy, unsync::OnceCell};
use pipewire::spa::Direction; use pipewire::spa::Direction;
@@ -18,6 +30,7 @@ mod imp {
/// Graphical representation of a pipewire port. /// Graphical representation of a pipewire port.
#[derive(Default)] #[derive(Default)]
pub struct Port { pub struct Port {
pub(super) label: OnceCell<gtk::Label>,
pub(super) id: OnceCell<u32>, pub(super) id: OnceCell<u32>,
pub(super) direction: OnceCell<Direction>, pub(super) direction: OnceCell<Direction>,
} }
@@ -26,10 +39,23 @@ mod imp {
impl ObjectSubclass for Port { impl ObjectSubclass for Port {
const NAME: &'static str = "Port"; const NAME: &'static str = "Port";
type Type = super::Port; type Type = super::Port;
type ParentType = gtk::Button; type ParentType = gtk::Widget;
fn class_init(klass: &mut Self::Class) {
klass.set_layout_manager_type::<gtk::BinLayout>();
// Make it look like a GTK button.
klass.set_css_name("button");
}
} }
impl ObjectImpl for Port { impl ObjectImpl for Port {
fn dispose(&self, _obj: &Self::Type) {
if let Some(label) = self.label.get() {
label.unparent()
}
}
fn signals() -> &'static [Signal] { fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| { static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder( vec![Signal::builder(
@@ -46,18 +72,18 @@ mod imp {
} }
} }
impl WidgetImpl for Port {} impl WidgetImpl for Port {}
impl ButtonImpl for Port {}
} }
glib::wrapper! { glib::wrapper! {
pub struct Port(ObjectSubclass<imp::Port>) pub struct Port(ObjectSubclass<imp::Port>)
@extends gtk::Button, gtk::Widget; @extends gtk::Widget;
} }
impl Port { impl Port {
pub fn new(id: u32, name: &str, direction: Direction, media_type: Option<MediaType>) -> Self { pub fn new(id: u32, name: &str, direction: Direction, media_type: Option<MediaType>) -> Self {
// Create the widget and initialize needed fields // Create the widget and initialize needed fields
let res: Self = glib::Object::new(&[]).expect("Failed to create Port"); let res: Self = glib::Object::new(&[]).expect("Failed to create Port");
let private = imp::Port::from_instance(&res); let private = imp::Port::from_instance(&res);
private.id.set(id).expect("Port id already set"); private.id.set(id).expect("Port id already set");
private private
@@ -65,47 +91,85 @@ impl Port {
.set(direction) .set(direction)
.expect("Port direction already set"); .expect("Port direction already set");
res.set_child(Some(&gtk::Label::new(Some(name)))); let label = gtk::Label::new(Some(name));
label.set_parent(&res);
private
.label
.set(label)
.expect("Port label was already set");
// Add either a drag source or drop target controller depending on direction, // Add a drag source and drop target controller with the type depending on direction,
// they will be responsible for link creation by dragging an output port onto an input port. // they will be responsible for link creation by dragging an output port onto an input port or the other way around.
//
// FIXME: The type used for dragging is simply a u32. // FIXME: We should protect against different media types, e.g. it should not be possible to drop a video port on an audio port.
// This means that anything that provides a u32 could be dragged onto a input port,
// leading to that port trying to create a link to an invalid output port. // The port will simply provide its pipewire id to the drag target.
// We should use a newtype instead of a plain u32. let drag_src = gtk::DragSourceBuilder::new()
// Additionally, this does not protect against e.g. dropping an outgoing audio port on an ingoing video port. .content(&gdk::ContentProvider::for_value(&match direction {
Direction::Input => ReversedLink(id).to_value(),
Direction::Output => ForwardLink(id).to_value(),
}))
.build();
drag_src.connect_drag_begin(move |_, _| {
trace!("Drag started from port {}", id);
});
drag_src.connect_drag_cancel(move |_, _, _| {
trace!("Drag from port {} was cancelled", id);
false
});
res.add_controller(&drag_src);
// The drop target will accept either a `ForwardLink` or `ReversedLink` depending in its own direction,
// and use it to emit its `port-toggled` signal.
let drop_target = gtk::DropTarget::new(
match direction {
Direction::Input => ForwardLink::static_type(),
Direction::Output => ReversedLink::static_type(),
},
gdk::DragAction::COPY,
);
match direction { match direction {
Direction::Input => { Direction::Input => {
let drop_target = gtk::DropTarget::new(u32::static_type(), gdk::DragAction::COPY); drop_target.connect_drop(
let this = res.clone(); clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
drop_target.connect_drop(move |drop_target, val, _, _| { if let Ok(ForwardLink(source_id)) = val.get::<ForwardLink>() {
if let Ok(source_id) = val.get::<u32>() { // Get the callback registered in the widget and call it
// Get the callback registered in the widget and call it drop_target
drop_target .widget()
.widget() .expect("Drop target has no widget")
.expect("Drop target has no widget") .emit_by_name("port-toggled", &[&source_id, &this.id()])
.emit_by_name("port-toggled", &[&source_id, &this.id()]) .expect("Failed to send signal");
.expect("Failed to send signal"); } else {
} else { warn!("Invalid type dropped on ingoing port");
warn!("Invalid type dropped on ingoing port"); }
}
true true
}); }),
res.add_controller(&drop_target); );
} }
Direction::Output => { Direction::Output => {
// The port will simply provide its pipewire id to the drag target. drop_target.connect_drop(
let drag_src = gtk::DragSourceBuilder::new() clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
.content(&gdk::ContentProvider::for_value(&(id.to_value()))) if let Ok(ReversedLink(target_id)) = val.get::<ReversedLink>() {
.build(); // Get the callback registered in the widget and call it
res.add_controller(&drag_src); drop_target
.widget()
.expect("Drop target has no widget")
.emit_by_name("port-toggled", &[&this.id(), &target_id])
.expect("Failed to send signal");
} else {
warn!("Invalid type dropped on outgoing port");
}
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port. true
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); }),
);
} }
} }
res.add_controller(&drop_target);
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port.
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
// Color the port according to its media type. // Color the port according to its media type.
match media_type { match media_type {