75 Commits
0.2.0 ... 0.3.4

Author SHA1 Message Date
Tom A. Wagner
094681637e Release v0.3.4 2022-02-02 10:08:12 +01:00
Tom A. Wagner
6f92fbdb8f readme: Add flathub badge 2022-01-31 13:32:03 +01:00
Tom A. Wagner
e38426c09f Update dependencies, fix segmentation fault
Updating glib to 0.15.4 fixes a segmentation fault that could occur when logging a message with glib structured logging.
2022-01-31 13:17:55 +01:00
Tom A. Wagner
85e249cb32 Release 0.3.3 2022-01-28 13:49:49 +01:00
Tom A. Wagner
c54aed2e14 Update dependencies 2022-01-28 13:49:49 +01:00
Tom A. Wagner
6da232debf meson: Fix incorrect output file name
This fixes a typo from 96c61e43 that caused the binary to be generated with the wrong name.
2022-01-28 13:48:46 +01:00
Tom A. Wagner
96c61e43d2 meson: Remove custom build scripts 2022-01-28 13:01:35 +01:00
Tom A. Wagner
872ef7890d readme: Credit gtk-rust-template 2022-01-28 12:11:32 +01:00
Tom A. Wagner
76ad8d11d7 Change application id to org.pipewire.Helvum 2022-01-28 10:14:21 +01:00
Tom A. Wagner
4075b66865 deps: Update gtk-rs to latest release 2022-01-17 12:00:00 +01:00
Tom A. Wagner
96182826e4 logging: Use glib as log backend instead of env_logger.
This makes log output use the same logger as gtk itself and as most other gtk applications
2022-01-11 12:15:14 +01:00
Mihai Fufezan
e1fbb0cf49 Use gtk4-update-icon-cache instead of gtk3 one 2021-12-18 19:50:39 +00:00
Alireza Haghshenas
3653f2bb11 Emphasized that the flatpak-builder command needs be run inside a local clone of the project 2021-12-10 04:34:49 +00:00
Tom A. Wagner
56523f1b30 docs: Add documentation for making a release 2021-12-01 19:56:17 +01:00
Tom A. Wagner
7818bed159 Add appstream metadata file 2021-12-01 19:56:17 +01:00
Tom A. Wagner
c1ec56e115 Release 0.3.2 2021-11-30 18:25:58 +01:00
Tom A. Wagner
110e9ef67f meson: Add dist script to vendor cargo dependencies for offline builds 2021-11-30 09:59:56 +01:00
Jan Beich
3c507683b7 build-aux: relax shebang in cargo.sh after 7b1b5ea336
/bin/sh: /bin/bash: not found
2021-11-27 21:57:03 +00:00
Thomas Rosendal
1d1f8bd3d7 Add instruction to add the flatpak remote 'flathub' 2021-11-25 07:59:45 +00:00
Sebastian Grabowski
b25f6f9abb Update the extension versions in flatpak build instructions to 21.08
The extensions need to match the SDK version, that was updated to
org.gnome.Sdk//41 in Commit e5e02b13. (Version 41 is based on
org.freedesktop.Sdk//21.08)
2021-11-24 17:03:23 +01:00
Tom A. Wagner
2d51ea677e ci: use more recent fd.o ci-templates 2021-11-24 16:11:16 +01:00
Tom A. Wagner
beb03d8b09 Update CI container for rust 1.56 2021-11-24 16:11:16 +01:00
Tom A. Wagner
502cf4476b gtk4dep: bump to 4.4 for fixed gtk_pick when using affine transform
This will be needed in a later commit for zooming in on the canvas using an affine (scaling) transformation matrix
2021-11-24 16:11:16 +01:00
Tom A. Wagner
eac973da15 Swap to rust 2021 edition and move rustc version check from meson.build to Cargo.toml 2021-11-24 16:11:16 +01:00
Tom A. Wagner
82a3e4f900 graphview: draw background automatically
This removes the manual painting of the background via cairo and adds the correct color to CSS instead,
which should hopefully improve performance as we do less cpu painting like this.
2021-11-24 16:11:16 +01:00
Suchipi
2cfc8e2e6f Update build instructions to match changes in e5e02b1387 2021-11-19 01:10:14 +00:00
Tom A. Wagner
e5e02b1387 flatpak: Update to gnome runtime 41 2021-11-17 19:49:52 +01:00
Tom A. Wagner
396363cef1 README: Update screenshot 2021-11-17 19:49:52 +01:00
Tom A. Wagner
c887d77f64 graphview: Use #808080 as link color
For good contrast in both dark and light mode, the link color is now a semi-light gray instead of complete black, which had bad contrast in dark mode and good constrast in light mode.

Later, we can seperate color palettes for light and dark mode, but only together with a dark mode toggle button or system-wide darkmode toggle.
2021-11-13 20:32:40 +01:00
Tom A. Wagner
54d7ca83ae graphview: Define link and grid colors in style.css
Previously, these were defined directly in the code,
but defining them in the css helps seperating theming and behaviour
and makes the colors easier to tweak.
2021-11-13 20:08:47 +01:00
Tom A. Wagner
7b1b5ea336 build: fix and improve cargo.sh
cargo.sh previously used bash features but only used `sh` in the shebang,
and also did not properly quote some variables to avoid splitting/globbing from happening.

Also, `-euo pilefail` is now set to avoid other errors that might come up.
2021-10-26 10:04:07 +02:00
Tom A. Wagner
729d4e1555 CI: Update container 2021-10-13 12:11:06 +02:00
Tom A. Wagner
ce6cab8134 Run rustfmt 2021-10-13 12:01:22 +02:00
Tom A. Wagner
8a552d0712 graphview: Add missing }; to fix build. 2021-10-13 11:56:46 +02:00
halfbro
f76235674c Add small control point offset to links that connect from right to left (loopbacks) 2021-10-13 09:11:10 +00:00
Tom A. Wagner
92dcfd61a1 Add gpl snippets to the start of each rust source file. 2021-10-03 11:27:36 +02:00
Tom A. Wagner
02e58e9bfa LICENSE: Replace outdated and edited GPL with the lastest stock version.
The old license file contained project specific edits, but editing the GPL text is disallowed by the fsf.
The new file also contains updated links.
2021-10-03 11:14:30 +02:00
Tom A. Wagner
958fa15230 Release 0.3.1 2021-09-30 08:45:11 +02:00
Tom A. Wagner
e9753dd078 Update dependencies 2021-09-30 08:45:00 +02:00
Tom A. Wagner
dfb1b754c7 Add and install icons.
This adds one new app icon and one new symbolic icon, and makes meson install these to the appropriate
directories, and adds an appropriate entry in the .desktop file.
2021-09-30 08:28:06 +02:00
Tom A. Wagner
497da8b953 graphview: Do not crash when the position of a node not on the graph is requested
While the position of a node not on the graph should never be requested, this seems to occur sometimes,
so instead of panicking, we only log an error now, or ignore that node if it wasn't
important.
2021-09-11 15:03:03 +02:00
Tom A. Wagner
da5da90352 Add .desktop file
This adds a .desktop file (without icon for now), which will automatically be installed when
`meson install` is run.
2021-08-29 18:41:09 +02:00
Tom A. Wagner
a8bfd8383e buildsystem: Move to meson for building the project
Meson will allow us to:
- Verify the used rust compiler is recent enough
- Install ressources such as a .desktop files, icons, etc.
2021-08-29 17:28:55 +02:00
Tom A. Wagner
7ef8677c4c readme: move screenshot.png into docs/ folder 2021-08-29 16:56:56 +02:00
Roger Roger
487dc3b2d3 Group nodes into columns by major type 2021-08-18 07:23:50 +00:00
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
Tom A. Wagner
46b2175a78 Release v0.2.1 2021-06-06 20:44:10 +02:00
Tom A. Wagner
9aeea6b108 Update dependencies 2021-06-06 20:42:00 +02:00
Tom A. Wagner
c8f94ae302 Use link info callback instead of properties to get ids.
While the properties for a links node ids are not always set, they always are in the link info.

Also, the work done in this commit will easily allow to get the format of links and ports later,
so that we can get the format of them much more reliably,
and we can also get notified of changes to an existing global via the info callback.
2021-06-06 20:28:19 +02:00
Tom A. Wagner
92101d860c Update ARCHITECTURE.md to reflect state having moved to the pipewire thread 2021-06-06 09:36:57 +02:00
Tom A. Wagner
907ef328d2 Move state to pipewire thread instead of the gtk thread.
This will allow easier state-keeping later, when setting up info-listeners on structs.
2021-06-06 09:20:07 +02:00
Tom A. Wagner
118c1ca28c Get node_from, node_to ids from state instead of props when a new link appears.
This is more reliable than assuming the link carries the id of its nodes, as there have been cases where a link was created without those
properties set.
Instead, we can just pull them from the state via the port ids of the link.
2021-06-04 10:30:31 +02:00
Tom A. Wagner
a9aec985b0 pipewire: Edit fixme about port media type 2021-05-25 22:45:13 +02:00
Tom A. Wagner
dce228ff60 Update dependencies
Glib MainContext is now aquired manually because a change in gtk-rs would lead to
a panic when attaching the receiver otherwise, because gtk::init() doesn't
"leak" the default main context anymore.
2021-05-22 14:00:51 +02:00
Jan Vanmullem
24fd54affe references both AUR release & git packages 2021-05-18 16:25:49 +02:00
Mathias Rav
3cd19f2d1d When dragging a node, don't snap its top-left corner to the cursor
Revamp the node dragging implementation, moving it into the GraphView
widget.

When a drag is initiated, the node widget's current position is stored.
Whenever the drag gesture is updated, the node widget's position is set
by adding the relative drag vector to the position at the start of the
drag.

A drag gesture on the node widget rather than the GraphView widget was
considered, but this seems to lead to a weird flickering effect when the
node is moved while the drag gesture on the node is active.

To avoid interfering with the drag handlers on the ports, check if the
GraphView drag gesture targets a port, in which case the handler does
nothing.
2021-05-10 18:21:00 +00:00
Mathias Rav
6d60095da8 Move CSS to its own file 2021-05-09 21:53:27 +02:00
31 changed files with 4925 additions and 744 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,6 @@
include:
- project: 'freedesktop/ci-templates' # the project to include from
ref: '98f557799157ebb0395cf11d40f01f61fbbace20' # git ref of that project
ref: '34f4ade99434043f88e164933f570301fd18b125' # git ref of that project
file: '/templates/fedora.yml' # the actual file to include
stages:
@@ -10,14 +10,14 @@ stages:
- extras
variables:
FDO_UPSTREAM_REPO: 'ryuukyu/helvum'
FDO_UPSTREAM_REPO: 'pipewire/helvum'
# Version and tag for our current container
.fedora:
variables:
FDO_DISTRIBUTION_VERSION: '34'
FDO_DISTRIBUTION_VERSION: '35'
# Update this to trigger a container rebuild
FDO_DISTRIBUTION_TAG: '2021-05-06.0'
FDO_DISTRIBUTION_TAG: '2021-11-23.0'
build-fedora-container:
extends:

457
Cargo.lock generated
View File

@@ -1,28 +1,30 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.40"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
[[package]]
name = "arrayvec"
@@ -49,9 +51,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bindgen"
version = "0.58.1"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags",
"cexpr",
@@ -72,15 +74,15 @@ dependencies = [
[[package]]
name = "bitflags"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "0.19.5"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33"
dependencies = [
"funty",
"radium",
@@ -90,8 +92,9 @@ dependencies = [
[[package]]
name = "cairo-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b869e97a87170f96762f9f178eae8c461147e722ba21dd8814105bf5716bf14a"
dependencies = [
"bitflags",
"cairo-sys-rs",
@@ -102,34 +105,44 @@ dependencies = [
[[package]]
name = "cairo-sys-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8"
dependencies = [
"glib-sys",
"libc",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
name = "cc"
version = "1.0.67"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cexpr"
version = "0.4.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom 5.1.2",
"nom 7.1.0",
]
[[package]]
name = "cfg-expr"
version = "0.7.4"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30aa9e2ffbb838c6b451db14f3cd8e63ed622bf859f9956bc93845a10fafc26a"
checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e"
dependencies = [
"smallvec",
]
[[package]]
name = "cfg-expr"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7"
dependencies = [
"smallvec",
]
@@ -148,9 +161,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
dependencies = [
"glob",
"libc",
@@ -159,9 +172,9 @@ dependencies = [
[[package]]
name = "clap"
version = "2.33.3"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
@@ -186,9 +199,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "env_logger"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
dependencies = [
"atty",
"humantime",
@@ -199,9 +212,9 @@ dependencies = [
[[package]]
name = "errno"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
@@ -210,19 +223,19 @@ dependencies = [
[[package]]
name = "errno-dragonfly"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"gcc",
"cc",
"libc",
]
[[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 +249,24 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures-channel"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
[[package]]
name = "futures-executor"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
dependencies = [
"futures-core",
"futures-task",
@@ -262,21 +275,21 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
[[package]]
name = "futures-task"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
[[package]]
name = "futures-util"
version = "0.3.14"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
dependencies = [
"futures-core",
"futures-task",
@@ -285,17 +298,13 @@ dependencies = [
"slab",
]
[[package]]
name = "gcc"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]]
name = "gdk-pixbuf"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73aa2f5de1b45710da90a55863276667dc3a3264aaf6a2aeace62bb015244d49"
dependencies = [
"bitflags",
"gdk-pixbuf-sys",
"gio",
"glib",
@@ -304,20 +313,22 @@ dependencies = [
[[package]]
name = "gdk-pixbuf-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413424d9818621fa3cfc8a3a915cdb89a7c3c507d56761b4ec83a9a98e587171"
dependencies = [
"gio-sys",
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
name = "gdk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df40006277ff44538fe758400fc671146f6f2665978b6b57d2408db3c2becf"
dependencies = [
"bitflags",
"cairo-rs",
@@ -331,24 +342,26 @@ dependencies = [
[[package]]
name = "gdk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48a39e34abe35ee2cf54a1e29dd983accecd113ad30bdead5050418fa92f2a1b"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gio-sys",
"glib-sys",
"gobject-sys",
"graphene-sys",
"libc",
"pango-sys",
"system-deps",
"pkg-config",
"system-deps 6.0.1",
]
[[package]]
name = "gio"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f906022fe89505b4e7b6c59a62d4ca3c090904f286cf61f1b245afcb20897d"
dependencies = [
"bitflags",
"futures-channel",
@@ -363,20 +376,22 @@ dependencies = [
[[package]]
name = "gio-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97225e1b9c7c48ed7fec4377fdc72702965bfbfd3a944b928ccbb5d8ed82ccc9"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"system-deps 6.0.1",
"winapi",
]
[[package]]
name = "glib"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e385b6c17a1add7d0fbc64d38e2e742346d3e8b22e5fa3734e5cdca2be24028d"
dependencies = [
"bitflags",
"futures-channel",
@@ -387,17 +402,20 @@ dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"log",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e58b262ff65ef771003873cea8c10e0fe854f1c508d48d62a4111a1ff163f7d1"
dependencies = [
"anyhow",
"heck",
"heck 0.4.0",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
@@ -407,11 +425,12 @@ dependencies = [
[[package]]
name = "glib-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4f08dd67f74b223fedbbb30e73145b9acd444e67cc4d77d0598659b7eebe7e"
dependencies = [
"libc",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
@@ -422,18 +441,20 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gobject-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6edb1f0b3e4c08e2a0a490d1082ba9e902cdff8ff07091e85c6caec60d17e2ab"
dependencies = [
"glib-sys",
"libc",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
name = "graphene-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c54f9fbbeefdb62c99f892dfca35f83991e2cb5b46a8dc2a715e58612f85570"
dependencies = [
"glib",
"graphene-sys",
@@ -442,19 +463,21 @@ dependencies = [
[[package]]
name = "graphene-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03f311acb023cf7af5537f35de028e03706136eead7f25a31e8fd26f5011e0b3"
dependencies = [
"glib-sys",
"libc",
"pkg-config",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
name = "gsk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf63d454e2f75abd92ee6de0ac9fc5aaf1018cd9c458aaf9de296c5cbab6bb9"
dependencies = [
"bitflags",
"cairo-rs",
@@ -468,8 +491,9 @@ dependencies = [
[[package]]
name = "gsk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31d21d7ce02ba261bb24c50c4ab238a10b41a2c97c32afffae29471b7cca69b"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@@ -478,13 +502,14 @@ dependencies = [
"graphene-sys",
"libc",
"pango-sys",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
name = "gtk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e841556e3fe55d8a43ada76b7b08a5f65570bbdfe3b8f72c333053b8832c626"
dependencies = [
"bitflags",
"cairo-rs",
@@ -505,12 +530,11 @@ dependencies = [
[[package]]
name = "gtk4-macros"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573db42bb64973a4d5f718b73caa7204285a1a665308a23b11723d0ee56ec305"
dependencies = [
"anyhow",
"heck",
"itertools",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
@@ -520,8 +544,9 @@ dependencies = [
[[package]]
name = "gtk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c47c075e8f795c38f6e9a47b51a73eab77b325f83c0154979ed4d4245c36490d"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -533,23 +558,29 @@ dependencies = [
"gsk4-sys",
"libc",
"pango-sys",
"system-deps",
"system-deps 6.0.1",
]
[[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 = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "helvum"
version = "0.2.0"
version = "0.3.4"
dependencies = [
"env_logger",
"glib",
"gtk4",
"log",
"once_cell",
@@ -558,9 +589,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.18"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
@@ -573,9 +604,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "itertools"
version = "0.10.0"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
@@ -607,15 +638,15 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.94"
version = "0.2.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74"
[[package]]
name = "libloading"
version = "0.7.0"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if 1.0.0",
"winapi",
@@ -623,8 +654,9 @@ dependencies = [
[[package]]
name = "libspa"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb373e8b03740369c5fe48a557c6408b6898982d57e17940de144375d472743"
dependencies = [
"bitflags",
"cc",
@@ -632,17 +664,18 @@ dependencies = [
"errno",
"libc",
"libspa-sys",
"nom 6.1.2",
"system-deps",
"nom 6.2.1",
"system-deps 3.2.0",
]
[[package]]
name = "libspa-sys"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301a2fc2fed0a97c13836408a4d98f419af0c2695ecf74e634a214c17beefa6"
dependencies = [
"bindgen",
"system-deps",
"system-deps 3.2.0",
]
[[package]]
@@ -656,19 +689,25 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.14.1"
@@ -684,19 +723,9 @@ dependencies = [
[[package]]
name = "nom"
version = "5.1.2"
version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "nom"
version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
dependencies = [
"bitvec",
"funty",
@@ -706,15 +735,27 @@ dependencies = [
]
[[package]]
name = "once_cell"
version = "1.7.2"
name = "nom"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "pango"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79211eff430c29cc38c69e0ab54bc78fa1568121ca9737707eee7f92a8417a94"
dependencies = [
"bitflags",
"glib",
@@ -725,13 +766,14 @@ dependencies = [
[[package]]
name = "pango-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7022c2fb88cd2d9d55e1a708a8c53a3ae8678234c4a54bf623400aeb7f31fac2"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"system-deps 6.0.1",
]
[[package]]
@@ -751,9 +793,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.6"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
@@ -763,8 +805,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pipewire"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de050d879e7b8d9313429ec314b88b26fe48ba29a6ecc3bc8289d3673fee6c8"
dependencies = [
"anyhow",
"bitflags",
@@ -780,25 +823,26 @@ dependencies = [
[[package]]
name = "pipewire-sys"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4aa5ef9f3afef7dbb335106f69bd6bb541259e8796c693810cde20db1eb949"
dependencies = [
"bindgen",
"libspa-sys",
"system-deps",
"system-deps 3.2.0",
]
[[package]]
name = "pkg-config"
version = "0.3.19"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-crate"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
dependencies = [
"thiserror",
"toml",
@@ -830,18 +874,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.26"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
@@ -854,9 +898,9 @@ checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]]
name = "regex"
version = "1.5.4"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
dependencies = [
"aho-corasick",
"memchr",
@@ -886,9 +930,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.5"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "semver"
@@ -910,15 +954,15 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.125"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
[[package]]
name = "shlex"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal"
@@ -932,15 +976,15 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.3"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.6.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "static_assertions"
@@ -956,17 +1000,17 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strum"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
[[package]]
name = "strum_macros"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"heck 0.3.3",
"proc-macro2",
"quote",
"syn",
@@ -974,9 +1018,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.72"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
@@ -985,20 +1029,33 @@ dependencies = [
[[package]]
name = "system-deps"
version = "3.1.1"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c248107ad7bc1ac07066a4d003cae9e9a7bc2e27d3418f7a9cdcdc8699dbea70"
checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6"
dependencies = [
"anyhow",
"cfg-expr",
"heck",
"cfg-expr 0.8.1",
"heck 0.3.3",
"itertools",
"pkg-config",
"strum",
"strum_macros",
"thiserror",
"toml",
"version-compare",
"version-compare 0.0.11",
]
[[package]]
name = "system-deps"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad3a97fdef3daf935d929b3e97e5a6a680cd4622e40c2941ca0875d6566416f8"
dependencies = [
"cfg-expr 0.9.1",
"heck 0.4.0",
"pkg-config",
"toml",
"version-compare 0.1.0",
]
[[package]]
@@ -1027,18 +1084,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.24"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
@@ -1062,15 +1119,15 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
@@ -1091,10 +1148,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b"
[[package]]
name = "version_check"
version = "0.9.3"
name = "version-compare"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "void"
@@ -1104,10 +1167,12 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "which"
version = "3.1.1"
version = "4.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
dependencies = [
"either",
"lazy_static",
"libc",
]

View File

@@ -1,11 +1,12 @@
[package]
name = "helvum"
version = "0.2.0"
version = "0.3.4"
authors = ["Tom A. Wagner <tom.a.wagner@protonmail.com>"]
edition = "2018"
edition = "2021"
rust-version = "1.56"
license = "GPL-3.0-only"
description = "A GTK patchbay for pipewire"
repository = "https://gitlab.freedesktop.org/ryuukyu/helvum"
repository = "https://gitlab.freedesktop.org/pipewire/helvum"
readme = "README.md"
keywords = ["pipewire", "gtk", "patchbay", "gui", "utility"]
categories = ["gui", "multimedia"]
@@ -13,10 +14,10 @@ categories = ["gui", "multimedia"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk = { git = "https://github.com/gtk-rs/gtk4-rs/", package = "gtk4" }
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs", branch = "main" }
pipewire = "0.4"
gtk = { version = "0.4.1", package = "gtk4" }
glib = { version = "0.15.1", features = ["log"] }
log = "0.4.11"
env_logger = "0.8.2"
once_cell = "1.7.2"

14
LICENSE
View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Helvum
Copyright (C) 2020 Ryuukyu
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -645,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Helvum Copyright (C) 2020 Ryuukyu
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,6 +1,10 @@
Helvum is a GTK-based patchbay for pipewire, inspired by the JACK tool [catia](https://kx.studio/Applications:Catia).
![Screenshot](screenshot.png)
![Screenshot](docs/screenshot.png)
<a href="https://flathub.org/apps/details/org.pipewire.Helvum"><img src="https://flathub.org/assets/badges/flathub-badge-en.png" width="300"/></a>
<a href="https://repology.org/project/helvum/versions"><img src="https://repology.org/badge/vertical-allrepos/helvum.svg" width="300"/></a>
# Features planned
@@ -9,20 +13,51 @@ Helvum is a GTK-based patchbay for pipewire, inspired by the JACK tool [catia](h
More suggestions are welcome!
# Distribution packages
- ArchLinux: [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
# Building
## Via flatpak
If you don't have the flathub repo in your remote-list for flatpak you will need to add that first:
```shell
$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
```
Then install the required flatpak platform and SDK, if you dont have them already:
```shell
$ flatpak install org.gnome.{Platform,Sdk}//41 org.freedesktop.Sdk.Extension.rust-stable//21.08 org.freedesktop.Sdk.Extension.llvm12//21.08
```
To compile and install as a flatpak, clone the project, change to the project directory, and run:
```shell
$ flatpak-builder --install flatpak-build/ build-aux/org.pipewire.Helvum.json
```
You can then run the app via
```shell
$ flatpak run org.pipewire.Helvum
```
## Manually
For compilation, you will need:
- Meson
- An up-to-date rust toolchain
- `libclang-3.7` or higher
- `gtk-4.0` and `pipewire-0.3` development headers
To compile, run
To compile and install, run
$ cargo build --release
```shell
$ meson setup build && cd build
$ meson compile
$ meson install
```
in the repository root.
The resulting binary will be at `target/release/helvum`.
This will install the compiled project files into `/usr/local`.
# License and Credits
Helvum is distributed under the terms of the GPL3 license.
See LICENSE for more information.
Parts of the build system were taken from the [gtk-rust-template](https://gitlab.gnome.org/World/Rust/gtk-rust-template) project,
which is provided under the terms of the [MIT license](https://gitlab.gnome.org/World/Rust/gtk-rust-template/-/blob/master/LICENSE.md).

12
build-aux/dist-vendor.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
export DIST="$1"
export SOURCE_ROOT="$2"
cd "$SOURCE_ROOT"
mkdir "$DIST"/.cargo
cargo vendor > $DIST/.cargo/config
# Move vendor into dist tarball directory
mv vendor "$DIST"

View File

@@ -0,0 +1,40 @@
{
"app-id": "org.pipewire.Helvum",
"runtime": "org.gnome.Platform",
"runtime-version": "41",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable",
"org.freedesktop.Sdk.Extension.llvm12"
],
"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:/usr/lib/sdk/llvm12/bin",
"prepend-ld-library-path": "/usr/lib/sdk/llvm12/lib",
"build-args": [
"--share=network"
]
},
"modules": [
{
"name": "Helvum",
"buildsystem": "meson",
"sources": [
{
"type": "dir",
"path": "../"
}
],
"config-opts": [
"-Dprofile=development"
]
}
]
}

9
data/icons/meson.build Normal file
View File

@@ -0,0 +1,9 @@
install_data(
'@0@.svg'.format(base_id),
install_dir: iconsdir / 'hicolor' / 'scalable' / 'apps'
)
install_data(
'@0@-symbolic.svg'.format(base_id),
install_dir: iconsdir / 'hicolor' / 'symbolic' / 'apps',
)

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 3.5 2.5 h 9 c 1.378906 0 2.5 1.121094 2.5 2.5 v 5 c 0 1.378906 -1.121094 2.5 -2.5 2.5 h -9 c -1.378906 0 -2.5 -1.121094 -2.5 -2.5 v -5 c 0 -1.378906 1.121094 -2.5 2.5 -2.5 z m 0 0" fill="#241f31" fill-rule="evenodd"/>
<g fill="none" stroke="#8a8891">
<path d="m 11 7.5 h -6"/>
<path d="m 5 7.5 c 0 -4.15625 -1.382812 -10.855469 -3.90625 -13.25" stroke-linecap="round" stroke-linejoin="bevel" stroke-width="4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 589 B

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<linearGradient id="a" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#9a9996"/>
<stop offset="0.5" stop-color="#c0bfbc"/>
<stop offset="1" stop-color="#deddda"/>
</linearGradient>
<linearGradient id="b" x1="26.263471" x2="26.263586" xlink:href="#a" y1="24.848538" y2="37.1125"/>
<linearGradient id="c" gradientUnits="userSpaceOnUse" x1="7.39555839647" x2="120.60350567947" y1="82.86737386462" y2="82.86737386462">
<stop offset="0" stop-color="#5e5c64"/>
<stop offset="0.0384615" stop-color="#77767b"/>
<stop offset="0.0768555" stop-color="#5e5c64"/>
<stop offset="0.923077" stop-color="#5e5c64"/>
<stop offset="0.961538" stop-color="#77767b"/>
<stop offset="1" stop-color="#5e5c64"/>
</linearGradient>
<linearGradient id="d" gradientTransform="matrix(2.571428 0 0 2.454545 22.856596 -228.048061)" x1="16" x2="16" xlink:href="#a" y1="121.582512" y2="127.082512"/>
<linearGradient id="e" gradientTransform="matrix(2.571428 0 0 2.454545 22.856596 -253.563246)" x1="16" x2="16" xlink:href="#a" y1="121.582512" y2="127.082512"/>
<linearGradient id="f" gradientTransform="matrix(2.571428 0 0 2.454545 60.592569 -253.563246)" x1="16" x2="16" xlink:href="#a" y1="121.582512" y2="127.082512"/>
<linearGradient id="g" gradientTransform="matrix(2.571428 0 0 2.454545 60.592569 -228.048061)" x1="16" x2="16" xlink:href="#a" y1="121.582512" y2="127.082512"/>
<linearGradient id="h" gradientTransform="matrix(2.358499 0 0 2.251294 -11.472502 -204.652927)" gradientUnits="userSpaceOnUse" x1="12.5" x2="19.5" y1="113.832512" y2="113.832512">
<stop offset="0" stop-color="#c0bfbc"/>
<stop offset="0.5" stop-color="#9a9996"/>
<stop offset="1" stop-color="#c0bfbc"/>
</linearGradient>
<linearGradient id="i" gradientTransform="matrix(2.571428 0 0 2.454545 -14.879853 -228.048061)" x1="16" x2="16" xlink:href="#a" y1="121.582512" y2="127.082512"/>
<path d="m 34.519531 30.980469 c 0 3.386719 -3.695312 6.132812 -8.257812 6.132812 c -4.558594 0 -8.253907 -2.746093 -8.253907 -6.132812 s 3.695313 -6.132813 8.253907 -6.132813 c 4.5625 0 8.257812 2.746094 8.257812 6.132813 z m 0 0" fill="url(#b)" fill-rule="evenodd"/>
<path d="m 120.601562 82.867188 v 18.867187 c 0 5.226563 -4.207031 9.433594 -9.433593 9.433594 h -94.339844 c -5.226563 0 -9.433594 -4.207031 -9.433594 -9.433594 v -18.867187 z m 0 0" fill="url(#c)" fill-rule="evenodd"/>
<path d="m 16.828125 35.699219 c -5.226563 0 -9.433594 4.207031 -9.433594 9.433593 v 37.734376 c 0 5.226562 4.207031 9.433593 9.433594 9.433593 h 94.339844 c 5.226562 0 9.4375 -4.207031 9.4375 -9.433593 v -37.734376 c 0 -5.226562 -4.210938 -9.433593 -9.4375 -9.433593 h -76.648438 v 2.355469 h -16.511719 v -2.355469 z m 0 0" fill="#77767b" fill-rule="evenodd"/>
<path d="m 93.480469 76.378906 l -30.660157 -24.761718" fill="none" stroke="#77767b" stroke-linecap="square" stroke-width="3.74412"/>
<path d="m 64 58.691406 v 10.613282" fill="none" stroke="#999999" stroke-width="1.5"/>
<g fill-rule="evenodd">
<path d="m 75 77.132812 c 0 4.554688 -4.925781 8.25 -11 8.25 s -11 -3.695312 -11 -8.25 c 0 -4.558593 4.925781 -8.25 11 -8.25 s 11 3.691407 11 8.25 z m 0 0" fill="#e01b24"/>
<path d="m 73 77.132812 c 0 3.726563 -4.03125 6.75 -9 6.75 c -4.972656 0 -9 -3.023437 -9 -6.75 c 0 -3.730468 4.027344 -6.75 9 -6.75 c 4.96875 0 9 3.019532 9 6.75 z m 0 0" fill="url(#d)"/>
<path d="m 71 77.132812 c 0 2.898438 -3.132812 5.25 -7 5.25 s -7 -2.351562 -7 -5.25 c 0 -2.902343 3.132812 -5.25 7 -5.25 s 7 2.347657 7 5.25 z m 0 0" fill="#3d3846"/>
<path d="m 75 51.617188 c 0 4.554687 -4.925781 8.25 -11 8.25 s -11 -3.695313 -11 -8.25 c 0 -4.558594 4.925781 -8.25 11 -8.25 s 11 3.691406 11 8.25 z m 0 0" fill="#1c71d8"/>
<path d="m 73 51.617188 c 0 3.726562 -4.03125 6.75 -9 6.75 c -4.972656 0 -9 -3.023438 -9 -6.75 c 0 -3.730469 4.027344 -6.75 9 -6.75 c 4.96875 0 9 3.019531 9 6.75 z m 0 0" fill="url(#e)"/>
<path d="m 71 51.617188 c 0 2.898437 -3.132812 5.25 -7 5.25 s -7 -2.351563 -7 -5.25 c 0 -2.898438 3.132812 -5.25 7 -5.25 s 7 2.351562 7 5.25 z m 0 0" fill="#3d3846"/>
<path d="m 112.734375 51.617188 c 0 4.554687 -4.921875 8.25 -11 8.25 c -6.074219 0 -11 -3.695313 -11 -8.25 c 0 -4.558594 4.925781 -8.25 11 -8.25 c 6.078125 0 11 3.691406 11 8.25 z m 0 0" fill="#1c71d8"/>
<path d="m 110.734375 51.617188 c 0 3.726562 -4.027344 6.75 -9 6.75 c -4.96875 0 -9 -3.023438 -9 -6.75 c 0 -3.730469 4.03125 -6.75 9 -6.75 c 4.972656 0 9 3.019531 9 6.75 z m 0 0" fill="url(#f)"/>
<path d="m 108.734375 51.617188 c 0 2.898437 -3.132813 5.25 -7 5.25 c -3.863281 0 -7 -2.351563 -7 -5.25 c 0 -2.898438 3.136719 -5.25 7 -5.25 c 3.867187 0 7 2.351562 7 5.25 z m 0 0" fill="#3d3846"/>
</g>
<path d="m 101.734375 58.691406 v 10.613282" fill="none" stroke="#999999" stroke-width="1.5"/>
<path d="m 112.734375 77.132812 c 0 4.554688 -4.921875 8.25 -11 8.25 c -6.074219 0 -11 -3.695312 -11 -8.25 c 0 -4.558593 4.925781 -8.25 11 -8.25 c 6.078125 0 11 3.691407 11 8.25 z m 0 0" fill="#e01b24" fill-rule="evenodd"/>
<path d="m 110.734375 77.132812 c 0 3.726563 -4.027344 6.75 -9 6.75 c -4.96875 0 -9 -3.023437 -9 -6.75 c 0 -3.730468 4.03125 -6.75 9 -6.75 c 4.972656 0 9 3.019532 9 6.75 z m 0 0" fill="url(#g)" fill-rule="evenodd"/>
<path d="m 108.734375 77.132812 c 0 2.898438 -3.132813 5.25 -7 5.25 c -3.863281 0 -7 -2.351562 -7 -5.25 c 0 -2.902343 3.136719 -5.25 7 -5.25 c 3.867187 0 7 2.347657 7 5.25 z m 0 0" fill="#3d3846" fill-rule="evenodd"/>
<path d="m 26.261719 69.339844 v -10.648438" fill="none" stroke="#999999" stroke-width="1.5"/>
<path d="m 37.261719 52.515625 c 0 4.238281 -4.921875 7.671875 -11 7.671875 c -6.074219 0 -11 -3.433594 -11 -7.671875 c 0 -4.234375 4.925781 -7.671875 11 -7.671875 c 6.078125 0 11 3.4375 11 7.671875 z m 0 0" fill="#1c71d8" fill-rule="evenodd"/>
<path d="m 18.007812 30.980469 v 20.636719 c 0.003907 3.417968 3.699219 6.191406 8.253907 6.191406 c 4.554687 0 8.25 -2.765625 8.253906 -6.183594 c 0 0 0 -0.003906 0 -0.007812 v -20.636719 c -1.308594 2.597656 -4.589844 6.132812 -8.253906 6.132812 c -3.660157 0 -6.941407 -3.535156 -8.253907 -6.132812 z m 0 0" fill="url(#h)" fill-rule="evenodd"/>
<path d="m 37.261719 77.132812 c 0 4.554688 -4.921875 8.25 -11 8.25 c -6.074219 0 -11 -3.695312 -11 -8.25 c 0 -4.558593 4.925781 -8.25 11 -8.25 c 6.078125 0 11 3.691407 11 8.25 z m 0 0" fill="#e01b24" fill-rule="evenodd"/>
<path d="m 35.261719 77.132812 c 0 3.726563 -4.027344 6.75 -9 6.75 c -4.96875 0 -9 -3.023437 -9 -6.75 c 0 -3.730468 4.03125 -6.75 9 -6.75 c 4.972656 0 9 3.019532 9 6.75 z m 0 0" fill="url(#i)" fill-rule="evenodd"/>
<path d="m 33.261719 77.132812 c 0 2.898438 -3.132813 5.25 -7 5.25 c -3.863281 0 -7 -2.351562 -7 -5.25 c 0 -2.902343 3.136719 -5.25 7 -5.25 c 3.867187 0 7 2.347657 7 5.25 z m 0 0" fill="#3d3846" fill-rule="evenodd"/>
<path d="m 34.519531 30.980469 c 0 3.386719 -3.695312 6.132812 -8.257812 6.132812 c -4.558594 0 -8.253907 -2.746093 -8.253907 -6.132812 s 3.695313 -6.132813 8.253907 -6.132813 c 4.5625 0 8.257812 2.746094 8.257812 6.132813 z m 0 0" fill="#c0bfbc" fill-rule="evenodd"/>
<path d="m 30.980469 30.980469 c 0 1.953125 -2.113281 3.539062 -4.71875 3.539062 c -2.601563 0 -4.714844 -1.585937 -4.714844 -3.539062 s 2.113281 -3.539063 4.714844 -3.539063 c 2.605469 0 4.71875 1.585938 4.71875 3.539063 z m 0 0" fill="#1a5fb4" fill-rule="evenodd"/>
<path d="m 26.261719 30.980469 c 0 -7.074219 0.628906 -18.371094 -10.285157 -21.847657 c -11.828124 -3.765624 -33.882812 3 -33.882812 3" fill="none" stroke="#1a5fb4" stroke-width="9.434"/>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

49
data/meson.build Normal file
View File

@@ -0,0 +1,49 @@
subdir('icons')
desktop_conf = configuration_data()
desktop_conf.set('icon', base_id)
desktop_file = configure_file(
input: '@0@.desktop.in'.format(base_id),
output: '@BASENAME@',
configuration: desktop_conf
)
if desktop_file_validate.found()
test(
'validate-desktop',
desktop_file_validate,
args: [
desktop_file
],
)
endif
install_data(
desktop_file,
install_dir: datadir / 'applications'
)
appdata_conf = configuration_data()
appdata_conf.set('app-id', base_id)
appdata_file = configure_file(
input: '@0@.metainfo.xml.in'.format(base_id),
output: '@BASENAME@',
configuration: appdata_conf
)
# Validate Appdata
if appstream_util.found()
test(
'validate-appdata',
appstream_util,
args: [
'validate', '--nonet', appdata_file
],
)
endif
install_data(
appdata_file,
install_dir: datadir / 'metainfo'
)

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Name=Helvum
GenericName=Patchbay
Comment=A patchbay for pipewire
Type=Application
Exec=helvum
Terminal=false
Categories=AudioVideo;Audio;Video;Midi;Settings;GNOME;GTK;
Icon=@icon@

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com> -->
<component type="desktop-application">
<id>@app-id@</id>
<metadata_license>CC-BY-SA-4.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<name>Helvum</name>
<summary>Patchbay for PipeWire</summary>
<description>
<p>
Helvum is a graphical patchbay for PipeWire.
It allows creating and removing connections between applications and/or devices to reroute
flow of audio, video and MIDI data to where it is needed.
</p>
</description>
<screenshots>
<screenshot type="default">
<image>https://gitlab.freedesktop.org/pipewire/helvum/-/raw/main/docs/screenshot.png</image>
</screenshot>
</screenshots>
<launchable type="desktop-id">@app-id@.desktop</launchable>
<url type="homepage">https://gitlab.freedesktop.org/pipewire/helvum</url>
<url type="bugtracker">https://gitlab.freedesktop.org/pipewire/helvum/-/issues</url>
<content_rating type="oars-1.0" />
<releases>
<release version="0.3.4" date="2022-02-02" />
<release version="0.3.3" date="2022-01-28" />
<release version="0.3.2" date="2021-11-30" />
<release version="0.3.1" date="2021-09-30" />
<release version="0.3.0" date="2021-08-08" />
<release version="0.2.1" date="2021-06-06" />
<release version="0.2.0" date="2021-05-21" />
<release version="0.1.0" date="2021-01-12" />
</releases>
<kudos>
<kodu>HiDpiIcon</kodu>
<kudo>ModernToolkit</kudo>
</kudos>
<developer_name>Tom A. Wagner</developer_name>
<update_contact>tom.a.wagner@protonmail.com</update_contact>
</component>

View File

@@ -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

22
docs/making_a_release.md Normal file
View File

@@ -0,0 +1,22 @@
# Making a release
The following describes the process of making a new release:
1. In `data/org.pipewire.Helvum.metainfo.xml.in`,
add a new `<release>` tag to the releases section with the appropriate version and date.
2. In `meson.build` and `Cargo.toml`, bumb the projects version to the new version.
3. Ensure cargo dependencies are up-to-date by running `cargo outdated` (may require running `cargo install cargo-outdated`) and updating outdated dependencies (including the versions specified in `Cargo.lock`).
4. Commit the changes with the a message of the format "Release x.y.z"
5. Add a tag to the release with the new version and a description from describing the changes as a message (run `git tag -a x.y.z`, then write the message)
6. Make a **new** meson build directory and run `meson dist`.
Two files should be created in a `meson-dist` subdirectory:
`helvum-x.y.z.tar.xz` and
`helvum-x.y.z.tar.xz.sha256sum`
7. Push the new commit and tag to upstream, then create a new release on gitlab from the new tag, the description from the tags message formatted as markdown, and also add the two files from step 6 to the description.

BIN
docs/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

38
meson.build Normal file
View File

@@ -0,0 +1,38 @@
project(
'helvum',
'rust',
version: '0.3.4',
license: 'GPL-3.0',
meson_version: '>=0.59.0'
)
gnome = import('gnome')
base_id = 'org.pipewire.Helvum'
dependency('glib-2.0', version: '>= 2.66')
dependency('gtk4', version: '>= 4.4.0')
dependency('libpipewire-0.3')
desktop_file_validate = find_program('desktop-file-validate', required: false)
appstream_util = find_program('appstream-util', required: false)
cargo = find_program('cargo', required: true)
prefix = get_option('prefix')
bindir = prefix / get_option('bindir')
datadir = prefix / get_option('datadir')
iconsdir = datadir / 'icons'
meson.add_dist_script(
'build-aux/dist-vendor.sh',
meson.project_build_root() / 'meson-dist' / meson.project_name() + '-' + meson.project_version(),
meson.project_source_root()
)
subdir('src')
subdir('data')
gnome.post_install(
gtk_update_icon_cache: true,
update_desktop_database: true,
)

11
meson_options.txt Normal file
View File

@@ -0,0 +1,11 @@
option(
'profile',
type: 'combo',
choices: [
'default',
'development'
],
value: 'default',
description: 'The build profile for Helvum. One of "default" or "development".'
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,4 +1,23 @@
use std::{cell::RefCell, collections::HashMap};
// application.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use std::cell::RefCell;
use gtk::{
gio,
@@ -6,38 +25,15 @@ use gtk::{
prelude::*,
subclass::prelude::*,
};
use log::{info, warn};
use log::info;
use pipewire::{channel::Sender, spa::Direction};
use crate::{
view::{self},
GtkMessage, PipewireLink, PipewireMessage,
GtkMessage, MediaType, NodeType, 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 +43,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>>>,
}
@@ -61,10 +56,10 @@ mod imp {
impl ObjectImpl for Application {}
impl ApplicationImpl for Application {
fn activate(&self, app: &Self::Type) {
let scrollwindow = gtk::ScrolledWindowBuilder::new()
let scrollwindow = gtk::ScrolledWindow::builder()
.child(&self.graphview)
.build();
let window = gtk::ApplicationWindowBuilder::new()
let window = gtk::ApplicationWindow::builder()
.application(app)
.default_width(1280)
.default_height(720)
@@ -106,9 +101,8 @@ impl Application {
gtk_receiver: Receiver<PipewireMessage>,
pw_sender: Sender<GtkMessage>,
) -> Self {
let app: Application =
glib::Object::new(&[("application-id", &"org.freedesktop.ryuukyu.helvum")])
.expect("Failed to create new Application");
let app: Application = glib::Object::new(&[("application-id", &"org.pipewire.Helvum")])
.expect("Failed to create new Application");
let imp = imp::Application::from_instance(&app);
imp.pw_sender
@@ -132,19 +126,13 @@ 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, node_type } => app.add_node(id, name.as_str(), node_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, 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::PortRemoved { id, node_id } => app.remove_port(id, node_id),
PipewireMessage::LinkRemoved { id } => app.remove_link(id)
};
Continue(true)
}
@@ -155,44 +143,33 @@ impl Application {
}
/// Add a new node to the view.
pub fn add_node(&self, id: u32, name: String, media_type: Option<MediaType>) {
fn add_node(&self, id: u32, name: &str, node_type: Option<NodeType>) {
info!("Adding node to graph: id {}", id);
let imp = imp::Application::from_instance(self);
imp.state.borrow_mut().insert(
imp::Application::from_instance(self).graphview.add_node(
id,
Item::Node {
// widget: node_widget,
media_type,
},
view::Node::new(name),
node_type,
);
imp.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) {
fn add_port(
&self,
id: u32,
name: &str,
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);
let port = view::Port::new(id, name, direction, media_type);
// Create or delete a link if the widget emits the "port-toggled" signal.
if let Err(e) = port.connect_local(
port.connect_local(
"port_toggled",
false,
clone!(@weak self as app => @default-return None, move |args| {
@@ -204,85 +181,57 @@ impl Application {
None
}),
) {
warn!("Failed to connect to \"port-toggled\" signal: {}", e);
}
);
imp.graphview.add_port(node_id, id, port);
}
/// Add a new link to the view.
pub fn add_link(&self, id: u32, link: PipewireLink) {
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);
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,
},
active,
);
}
fn link_state_changed(&self, id: u32, active: bool) {
info!(
"Link state changed: Link (id={}) is now {}",
id,
if active { "active" } else { "inactive" }
);
// Update graph to contain the new link.
imp.graphview.add_link(id, link);
imp::Application::from_instance(self)
.graphview
.set_link_state(id, active);
}
// 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))
.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
);
}
sender
.send(GtkMessage::ToggleLink { port_from, port_to })
.expect("Failed to send message");
}
/// Remove the node with the specified id from the view.
@@ -310,83 +259,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
}
}
}

View File

@@ -1,45 +1,89 @@
// main.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
mod application;
mod pipewire_connection;
mod view;
use application::MediaType;
use gtk::{
glib::{self, PRIORITY_DEFAULT},
prelude::*,
};
use glib::PRIORITY_DEFAULT;
use gtk::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>,
node_type: Option<NodeType>,
},
/// 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,
active: bool,
},
LinkStateChanged {
id: u32,
active: bool,
},
NodeRemoved {
id: u32,
},
PortRemoved {
id: u32,
node_id: u32,
},
LinkRemoved {
id: u32,
},
}
#[derive(Debug, Clone)]
pub enum NodeType {
Input,
Output,
}
#[derive(Debug, Copy, Clone)]
pub enum MediaType {
Audio,
Video,
Midi,
}
#[derive(Debug, Clone)]
@@ -50,10 +94,26 @@ pub struct PipewireLink {
pub port_to: u32,
}
static GLIB_LOGGER: glib::GlibLogger = glib::GlibLogger::new(
glib::GlibLoggerFormat::Structured,
glib::GlibLoggerDomain::CrateTarget,
);
fn init_glib_logger() {
log::set_logger(&GLIB_LOGGER).expect("Failed to set logger");
// Glib does not have a "Trace" log level, so only print messages "Debug" or higher priority.
log::set_max_level(log::LevelFilter::Debug);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
init_glib_logger();
gtk::init()?;
// Aquire main context so that we can attach the gtk channel later.
let ctx = glib::MainContext::default();
let _guard = ctx.acquire().unwrap();
// 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();

31
src/meson.build Normal file
View File

@@ -0,0 +1,31 @@
cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ]
cargo_options += [ '--target-dir', meson.project_build_root() / 'src' ]
if get_option('profile') == 'default'
cargo_options += [ '--release' ]
rust_target = 'release'
message('Building in release mode')
else
rust_target = 'debug'
message('Building in debug mode')
endif
cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ]
custom_target(
'cargo-build',
build_by_default: true,
build_always_stale: true,
output: meson.project_name(),
console: true,
install: true,
install_dir: bindir,
command: [
'env',
cargo_env,
cargo, 'build',
cargo_options,
'&&',
'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@',
],
)

View File

@@ -1,18 +1,47 @@
use std::rc::Rc;
// pipewire_connection.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
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, LinkChangeMask, LinkListener, LinkState},
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, NodeType, 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 +50,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, &registry, &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, &gtk_sender, &state),
ObjectType::Port => handle_port(global, &gtk_sender, &state),
ObjectType::Link => handle_link(global, &gtk_sender, &registry, &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,33 +118,63 @@ 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.
let media_type = props
.get("media.class")
.map(|class| {
if class.contains("Audio") {
Some(MediaType::Audio)
} else if class.contains("Video") {
Some(MediaType::Video)
} else if class.contains("Midi") {
Some(MediaType::Midi)
} else {
// FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead.
let media_type = props.get("media.class").and_then(|class| {
if class.contains("Audio") {
Some(MediaType::Audio)
} else if class.contains("Video") {
Some(MediaType::Video)
} else if class.contains("Midi") {
Some(MediaType::Midi)
} else {
None
}
});
let media_class = |class: &str| {
if class.contains("Sink") || class.contains("Input") {
Some(NodeType::Input)
} else if class.contains("Source") || class.contains("Output") {
Some(NodeType::Output)
} else {
None
}
};
let node_type = props
.get("media.category")
.and_then(|class| {
if class.contains("Duplex") {
None
} else {
props.get("media.class").and_then(media_class)
}
})
.flatten();
.or_else(|| props.get("media.class").and_then(media_class));
state.borrow_mut().insert(
node.id,
Item::Node {
// widget: node_widget,
media_type,
},
);
sender
.send(PipewireMessage::NodeAdded {
id: node.id,
name,
media_type,
node_type,
})
.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 +191,131 @@ 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 {
node_from,
port_from,
node_to,
port_to,
},
})
.expect("Failed to send message");
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
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 {
// 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,
active: matches!(info.state(), LinkState::Active)
}).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);
}
}
}

View File

@@ -0,0 +1,100 @@
// state.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
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 {
Self::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
}
}
}

23
src/style.css Normal file
View File

@@ -0,0 +1,23 @@
@define-color audio rgb(50,100,240);
@define-color video rgb(200,200,0);
@define-color midi rgb(200,0,50);
@define-color graphview-link #808080;
.audio {
background: @audio;
color: black;
}
.video {
background: @video;
color: black;
}
.midi {
background: @midi;
color: black;
}
graphview {
background: @text_view_bg;
}

View File

@@ -1,8 +1,35 @@
use super::Node;
// graph_view.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use gtk::{gdk, glib, graphene, gsk, prelude::*, subclass::prelude::*};
use super::{Node, Port};
use std::collections::HashMap;
use gtk::{
glib::{self, clone},
graphene, gsk,
prelude::*,
subclass::prelude::*,
};
use log::{error, warn};
use std::{cmp::Ordering, collections::HashMap};
use crate::NodeType;
mod imp {
use super::*;
@@ -14,8 +41,8 @@ mod imp {
#[derive(Default)]
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>>>,
/// Stores the link and whether it is currently active.
pub(super) links: RefCell<HashMap<u32, (crate::PipewireLink, bool)>>,
}
#[glib::object_subclass]
@@ -27,6 +54,7 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
// The layout manager determines how child widgets are laid out.
klass.set_layout_manager_type::<gtk::FixedLayout>();
klass.set_css_name("graphview");
}
}
@@ -34,28 +62,46 @@ 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()
.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()
.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) {
@@ -72,63 +118,98 @@ mod imp {
Try to use relative units (em) and colours from the theme as much as possible. */
let alloc = widget.allocation();
let widget_bounds =
graphene::Rect::new(0.0, 0.0, alloc.width() as f32, alloc.height() as f32);
let 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");
// 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") {
cr.set_source_rgb(rgba.red.into(), rgba.green.into(), rgba.blue.into());
if let Err(e) = cr.paint() {
warn!("Failed to paint graphview background: {}", e);
};
} // TODO: else log colour not found
let background_cr = snapshot.append_cairo(&widget_bounds);
// Draw a nice grid on the background.
cr.set_source_rgb(0.18, 0.18, 0.18);
cr.set_line_width(0.2); // TODO: Set to 1px
background_cr.set_source_rgb(0.18, 0.18, 0.18);
background_cr.set_line_width(0.2); // TODO: Set to 1px
let mut y = 0.0;
while y < alloc.height.into() {
cr.move_to(0.0, y);
cr.line_to(alloc.width as f64, y);
while y < alloc.height().into() {
background_cr.move_to(0.0, y);
background_cr.line_to(alloc.width().into(), y);
y += 20.0; // TODO: Change to em;
}
let mut x = 0.0;
while x < alloc.width as f64 {
cr.move_to(x, 0.0);
cr.line_to(x, alloc.height as f64);
while x < alloc.width().into() {
background_cr.move_to(x, 0.0);
background_cr.line_to(x, alloc.height().into());
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);
};
// 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
self.nodes
.borrow()
.values()
.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,
));
link_cr.set_line_width(2.0);
let rgba = widget
.style_context()
.lookup_color("graphview-link")
.unwrap_or_else(|| gtk::gdk::RGBA::new(0.0, 0.0, 0.0, 0.0));
link_cr.set_source_rgba(
rgba.red().into(),
rgba.green().into(),
rgba.blue().into(),
rgba.alpha().into(),
);
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);
}
// If the output port is farther right than the input port and they have
// a similar y coordinate, apply a y offset to the control points
// so that the curve sticks out a bit.
let y_control_offset = if from_x > to_x {
f64::max(0.0, 25.0 - (from_y - to_y).abs())
} else {
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 - y_control_offset,
to_x - half_x_dist,
to_y - y_control_offset,
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);
}
}
}
}
@@ -136,7 +217,7 @@ mod imp {
/// Get coordinates for the drawn link to start at and to end at.
///
/// # 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)> {
let nodes = self.nodes.borrow();
@@ -144,34 +225,26 @@ mod imp {
// so we manually calculate the needed offsets here.
let from_port = &nodes.get(&link.node_from)?.get_port(link.port_from)?;
let gtk::Allocation {
x: mut fx,
y: mut fy,
width: fw,
height: fh,
} = from_port.allocation();
let from_node = from_port
.ancestor(Node::static_type())
.expect("Port is not a child of a node");
let gtk::Allocation { x: fnx, y: fny, .. } = from_node.allocation();
fx += fnx + fw;
fy += fny + (fh / 2);
let from_x = from_node.allocation().x()
+ from_port.allocation().x()
+ from_port.allocation().width();
let from_y = from_node.allocation().y()
+ from_port.allocation().y()
+ (from_port.allocation().height() / 2);
let to_port = &nodes.get(&link.node_to)?.get_port(link.port_to)?;
let gtk::Allocation {
x: mut tx,
y: mut ty,
height: th,
..
} = to_port.allocation();
let to_node = to_port
.ancestor(Node::static_type())
.expect("Port is not a child of a node");
let gtk::Allocation { x: tnx, y: tny, .. } = to_node.allocation();
tx += tnx;
ty += tny + (th / 2);
let to_x = to_node.allocation().x() + to_port.allocation().x();
let to_y = to_node.allocation().y()
+ to_port.allocation().y()
+ (to_port.allocation().height() / 2);
Some((fx as f64, fy as f64, tx as f64, ty as f64))
Some((from_x.into(), from_y.into(), to_x.into(), to_y.into()))
}
}
}
@@ -186,14 +259,37 @@ impl GraphView {
glib::Object::new(&[]).expect("Failed to create GraphView")
}
pub fn add_node(&self, id: u32, node: Node) {
pub fn add_node(&self, id: u32, node: Node, node_type: Option<NodeType>) {
let private = imp::GraphView::from_instance(self);
node.set_parent(self);
// Place widgets in colums of 4, growing down, then right.
// 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 y = private.nodes.borrow().len() as f32 % 4.0 * 100.0;
// Place widgets in colums of 3, growing down
let x = if let Some(node_type) = node_type {
match node_type {
NodeType::Output => 20.0,
NodeType::Input => 820.0,
}
} else {
420.0
};
let y = private
.nodes
.borrow()
.values()
.map(|node| {
// Map nodes to their locations
self.get_node_position(&node.clone().upcast())
})
.filter(|(x2, _)| {
// Only look for other nodes that have a similar x coordinate
(x - x2).abs() < 50.0
})
.max_by(|y1, y2| {
// Get max in column
y1.partial_cmp(y2).unwrap_or(Ordering::Equal)
})
.map_or(20_f32, |(_x, y)| y + 100.0);
self.move_node(&node.clone().upcast(), x, y);
@@ -205,6 +301,8 @@ impl GraphView {
let mut nodes = private.nodes.borrow_mut();
if let Some(node) = nodes.remove(&id) {
node.unparent();
} else {
warn!("Tried to remove non-existant node (id={}) from graph", id);
}
}
@@ -214,11 +312,9 @@ impl GraphView {
if let Some(node) = private.nodes.borrow_mut().get_mut(&node_id) {
node.add_port(port_id, port);
} else {
// FIXME: Log this instead
log::error!(
error!(
"Node with id {} not found when trying to add port with id {} to graph",
node_id,
port_id
node_id, port_id
);
}
}
@@ -231,16 +327,22 @@ impl GraphView {
}
}
/// Add a link to the graph.
///
/// `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) {
pub fn add_link(&self, link_id: u32, link: crate::PipewireLink, active: bool) {
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();
}
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) {
let private = imp::GraphView::from_instance(self);
let mut links = private.links.borrow_mut();
@@ -249,8 +351,21 @@ impl GraphView {
self.queue_draw();
}
pub(super) fn set_dragged(&self, widget: Option<gtk::Widget>) {
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
/// Get the position of the specified node inside the graphview.
pub(super) fn get_node_position(&self, node: &gtk::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)
.dynamic_cast::<gtk::FixedLayoutChild>()
.expect("Could not cast to FixedLayoutChild");
node.transform()
.expect("Failed to obtain transform from layout child")
.to_translate()
}
pub(super) fn move_node(&self, node: &gtk::Widget, x: f32, y: f32) {
@@ -267,7 +382,6 @@ impl GraphView {
layout_manager
.layout_child(node)
.expect("Could not get layout child")
.dynamic_cast::<gtk::FixedLayoutChild>()
.expect("Could not cast to FixedLayoutChild")
.set_transform(&transform);

View File

@@ -1,3 +1,22 @@
// mod.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
//! The view presented to the user.
//!
//! This module contains gtk widgets needed to present the graphical user interface.

View File

@@ -1,9 +1,26 @@
use super::graph_view::GraphView;
// node.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use gtk::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::Direction;
use std::{collections::HashMap, rc::Rc};
use std::collections::HashMap;
mod imp {
use super::*;
@@ -13,9 +30,9 @@ mod imp {
pub struct Node {
pub(super) grid: gtk::Grid,
pub(super) label: gtk::Label,
pub(super) ports: RefCell<HashMap<u32, Rc<crate::view::port::Port>>>,
pub(super) num_ports_in: Cell<u32>,
pub(super) num_ports_out: Cell<u32>,
pub(super) ports: RefCell<HashMap<u32, crate::view::port::Port>>,
pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<i32>,
}
#[glib::object_subclass]
@@ -34,35 +51,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());
@@ -112,21 +100,21 @@ impl Node {
Direction::Input => {
private
.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);
}
Direction::Output => {
private
.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.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);
private.ports.borrow_mut().get(&id).cloned()
}

View File

@@ -1,13 +1,44 @@
// port.rs
//
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-only
use gtk::{
gdk,
glib::{self, subclass::Signal},
glib::{self, clone, subclass::Signal},
prelude::*,
subclass::prelude::*,
};
use log::warn;
use log::{trace, warn};
use pipewire::spa::Direction;
use crate::application::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::Boxed)]
#[boxed_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::Boxed)]
#[boxed_type(name = "HelvumReversedLink")]
struct ReversedLink(u32);
mod imp {
use once_cell::{sync::Lazy, unsync::OnceCell};
@@ -18,6 +49,7 @@ mod imp {
/// Graphical representation of a pipewire port.
#[derive(Default)]
pub struct Port {
pub(super) label: OnceCell<gtk::Label>,
pub(super) id: OnceCell<u32>,
pub(super) direction: OnceCell<Direction>,
}
@@ -26,10 +58,23 @@ mod imp {
impl ObjectSubclass for Port {
const NAME: &'static str = "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 {
fn dispose(&self, _obj: &Self::Type) {
if let Some(label) = self.label.get() {
label.unparent()
}
}
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![Signal::builder(
@@ -46,18 +91,18 @@ mod imp {
}
}
impl WidgetImpl for Port {}
impl ButtonImpl for Port {}
}
glib::wrapper! {
pub struct Port(ObjectSubclass<imp::Port>)
@extends gtk::Button, gtk::Widget;
@extends gtk::Widget;
}
impl Port {
pub fn new(id: u32, name: &str, direction: Direction, media_type: Option<MediaType>) -> Self {
// Create the widget and initialize needed fields
let res: Self = glib::Object::new(&[]).expect("Failed to create Port");
let private = imp::Port::from_instance(&res);
private.id.set(id).expect("Port id already set");
private
@@ -65,47 +110,81 @@ impl Port {
.set(direction)
.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,
// they will be responsible for link creation by dragging an output port onto an input port.
//
// FIXME: The type used for dragging is simply a u32.
// 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.
// We should use a newtype instead of a plain u32.
// Additionally, this does not protect against e.g. dropping an outgoing audio port on an ingoing video port.
// 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 or the other way around.
// FIXME: We should protect against different media types, e.g. it should not be possible to drop a video port on an audio port.
// The port will simply provide its pipewire id to the drag target.
let drag_src = gtk::DragSource::builder()
.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 {
Direction::Input => {
let drop_target = gtk::DropTarget::new(u32::static_type(), gdk::DragAction::COPY);
let this = res.clone();
drop_target.connect_drop(move |drop_target, val, _, _| {
if let Ok(source_id) = val.get::<u32>() {
// Get the callback registered in the widget and call it
drop_target
.widget()
.expect("Drop target has no widget")
.emit_by_name("port-toggled", &[&source_id, &this.id()])
.expect("Failed to send signal");
} else {
warn!("Invalid type dropped on ingoing port");
}
drop_target.connect_drop(
clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
if let Ok(ForwardLink(source_id)) = val.get::<ForwardLink>() {
// Get the callback registered in the widget and call it
drop_target
.widget()
.emit_by_name::<()>("port-toggled", &[&source_id, &this.id()]);
} else {
warn!("Invalid type dropped on ingoing port");
}
true
});
res.add_controller(&drop_target);
true
}),
);
}
Direction::Output => {
// The port will simply provide its pipewire id to the drag target.
let drag_src = gtk::DragSourceBuilder::new()
.content(&gdk::ContentProvider::for_value(&(id.to_value())))
.build();
res.add_controller(&drag_src);
drop_target.connect_drop(
clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
if let Ok(ReversedLink(target_id)) = val.get::<ReversedLink>() {
// Get the callback registered in the widget and call it
drop_target
.widget()
.emit_by_name::<()>("port-toggled", &[&this.id(), &target_id]);
} 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.
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
true
}),
);
}
}
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.
match media_type {