Compare commits

...

27 Commits

Author SHA1 Message Date
Tarasov Aleksandr 3d4b59761b packages(rpm): add required dependencies 2026-05-13 23:47:25 +03:00
arabianq ca9b5dd517 ci: add missing deps 2026-05-13 23:21:22 +03:00
arabianq 6863c9a6f8 deps: update cargo sources 2026-05-13 23:06:08 +03:00
arabianq 958a3efde5 change version to 1.8.0 2026-05-13 23:06:08 +03:00
Tarasov Aleksandr 30e75e924c fix: Opus audio does not work in mkv files (#95)
* change rodio to the fork with symphonia-adapter-libopus-v0.2.8

* update flatpak sources

* deps: update arabianq/rodio rev
2026-05-13 23:02:31 +03:00
arabianq 2b4b7ea730 cargo update
Adding audio_thread_priority v0.35.1
    Updating cc v1.2.61 -> v1.2.62
    Updating color v0.3.2 -> v0.3.3
    Updating coreaudio-rs v0.14.1 -> v0.14.2
    Updating cpal v0.18.0 (https://github.com/RustAudio/cpal#f938e338) -> #2c7acf8e
      Adding dbus v0.6.5
    Updating egui_extras v0.34.1 -> v0.34.2
    Updating hashbrown v0.17.0 -> v0.17.1
    Updating js-sys v0.3.97 -> v0.3.98
    Updating kurbo v0.13.0 -> v0.13.1
      Adding libdbus-sys v0.2.7
      Adding mach2 v0.4.3
    Updating naga v29.0.1 -> v29.0.3
    Updating no_std_io2 v0.9.3 -> v0.9.4
    Updating normpath v1.5.0 -> v1.5.1
    Updating opusic-sys v0.6.0 -> v0.7.3
    Updating orbclient v0.3.53 -> v0.3.54
    Updating pin-project v1.1.11 -> v1.1.13
    Updating pin-project-internal v1.1.11 -> v1.1.13
      Adding polycool v0.4.0
    Updating profiling v1.0.17 -> v1.0.18
    Updating quick-xml v0.39.2 -> v0.39.4
    Updating redox_syscall v0.7.4 -> v0.7.5
    Updating siphasher v1.0.2 -> v1.0.3
    Updating symphonia-adapter-libopus v0.2.7 -> v0.2.9
    Updating wasm-bindgen v0.2.120 -> v0.2.121
    Updating wasm-bindgen-futures v0.4.70 -> v0.4.71
    Updating wasm-bindgen-macro v0.2.120 -> v0.2.121
    Updating wasm-bindgen-macro-support v0.2.120 -> v0.2.121
    Updating wasm-bindgen-shared v0.2.120 -> v0.2.121
    Updating web-sys v0.3.97 -> v0.3.98
    Updating wgpu v29.0.1 -> v29.0.3
    Updating wgpu-core v29.0.1 -> v29.0.3
    Updating wgpu-core-deps-windows-linux-android v29.0.0 -> v29.0.3
    Updating wgpu-hal v29.0.1 -> v29.0.3
    Updating wgpu-naga-bridge v29.0.1 -> v29.0.3
    Updating wgpu-types v29.0.1 -> v29.0.3
    Updating zerofrom v0.1.7 -> v0.1.8
    Updating zvariant v5.10.1 -> v5.11.0
    Updating zvariant_derive v5.10.1 -> v5.11.0
2026-05-13 23:00:10 +03:00
arabianq dafe67f35f assets: update screenshot.png 2026-05-13 22:59:03 +03:00
arabianq 8fa22ca5b0 docs: update README.md 2026-05-13 22:56:37 +03:00
arabianq d72eaabf54 feat: load system fonts 2026-05-13 22:02:24 +03:00
arabianq 377b218592 deps: update cargo-sources.json for flatpak 2026-05-13 21:45:50 +03:00
dependabot[bot] 911417af40 chore(deps): bump tokio from 1.52.1 to 1.52.3 (#99)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.1 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.1...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 21:44:25 +03:00
dependabot[bot] 573958c05b chore(deps): bump eframe from 0.34.1 to 0.34.2 (#98)
Bumps [eframe](https://github.com/emilk/egui) from 0.34.1 to 0.34.2.
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.34.1...0.34.2)

---
updated-dependencies:
- dependency-name: eframe
  dependency-version: 0.34.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 21:44:09 +03:00
dependabot[bot] 0bb7ef3f33 chore(deps): bump egui from 0.34.1 to 0.34.2 (#97)
Bumps [egui](https://github.com/emilk/egui) from 0.34.1 to 0.34.2.
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.34.1...0.34.2)

---
updated-dependencies:
- dependency-name: egui
  dependency-version: 0.34.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 21:43:54 +03:00
Tarasov Aleksandr 10f07cd895 ci: Add Cargo package ecosystem to Dependabot config (#96)
Configured Dependabot to update Cargo packages weekly.
2026-05-12 23:19:45 +03:00
Tarasov Aleksandr b2f2894aa1 perf(gui): remove O(N) allocation in hotkeys table render (#94)
This optimization removes an unnecessary `.cloned()` call inside `draw_hotkeys_table`
which previously forced a clone of every filtered `HotkeySlot` on every frame render.
Instead, we now hold a `Vec<&HotkeySlot>` and only clone `slot.slot` exactly when
a user interaction requires ownership to dispatch a `HotkeyAction`.

This eliminates constant heap allocations of `String` and `Request` components
while scrolling or idling in the hotkeys view.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-05-01 01:48:43 +03:00
Tarasov Aleksandr e6c8d720d5 update deps and change version to 1.7.6 (#92)
* change version to 1.7.6

* cargo update

cpal v0.18.0 #e5d618c6 -> #f938e338
fax v0.2.6 -> v0.2.7
fax_derive -
idna_adapter v1.2.1 -> v1.2.2
js-sys v0.3.95 -> v0.3.97
wasm-bindgen v0.2.118 -> v0.2.120
wasm-bindgen-futures v0.4.68 -> v0.4.70
wasm-bindgen-macro v0.2.118 -> v0.2.120
wasm-bindgen-macro-support v0.2.118 -> v0.2.120
wasm-bindgen-shared v0.2.118 -> v0.2.120
web-sys v0.3.95 -> v0.3.97
winnow -
zbus v5.14.0 -> v5.15.0
zbus_macros v5.14.0 -> v5.15.0
zbus_names v4.3.1 -> v4.3.2
zvariant v5.10.0 -> v5.10.1
zvariant_derive v5.10.0 -> v5.10.1
zvariant_utils v3.3.0 -> v3.3.1

* deps: update cargo-sources.json
2026-04-28 14:31:27 +03:00
RiDDiX a6d93ff528 fix clippy lints under rust 1.95 (#90) 2026-04-28 14:13:11 +03:00
RiDDiX bcf791d84c fix(packages): add cmake makedepend to aur source pkgbuild (#91) 2026-04-28 14:10:17 +03:00
Tarasov Aleksandr e4b0b10393 refactor(gui): refactor draw_hotkeys to improve code health (#86)
- Break down the monolithic `draw_hotkeys` method into smaller,
  focused component functions: `draw_hotkeys_header`,
  `draw_hotkeys_search`, `draw_hotkeys_table`, and
  `handle_hotkey_action`.
- Improve readability and maintainability of the `src/gui/draw.rs` file
  while preserving identical behavior.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-27 23:07:13 +03:00
Tarasov Aleksandr 11de96db58 refactor: simplify draw_track_control by extracting helper functions (#87)
Extracted distinct UI sections (playback controls, position slider, volume controls, stop button) into their own well-scoped helper functions within `SoundpadGui`. This significantly improves the maintainability and readability of `draw_track_control` while preserving the existing layout structure and state mutation behavior.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-27 23:05:39 +03:00
Tarasov Aleksandr 7396c0aef8 perf(gui): Optimize UI rendering loop by iterating over tracks by reference (#88)
* perf: optimize UI rendering loop by removing unnecessary Vec clone\n\n- Removed `clone()` on `self.audio_player_state.tracks` in `draw_header`\n- Iterated by reference instead of using an owned collection\n- Benchmarked and showed a significant performance improvement (7us -> 87ns)

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* build(flatpak): update cargo-sources.json to include criterion\n\nThe CI failed during the offline flatpak build because the newly added `criterion` dev-dependency was missing from `cargo-sources.json`. Regenerated `packages/flatpak/cargo-sources.json` to fix it.

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* Delete benches/ui_benchmark.rs

* refactor: remove garbage

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-27 23:03:37 +03:00
Tarasov Aleksandr fc2cd5e2da fix(daemon): Remove .expect() panics from PipeWire initialization (#89)
This commit addresses a security and stability vulnerability where failures in PipeWire context setup, connection, or registry acquisition would crash the entire thread (or daemon) via `.expect()` panics.

We now gracefully capture and propagate initialization errors up the call stack. A `sync_channel(0)` is used to signal the success or failure of the initial pipewire setup back to the calling functions (`get_all_devices`, `create_virtual_mic`, `create_link`). This prevents unexpected crashes and improves error resilience.

Also removed unneeded `pw_sender` panics on channel termination by simply dropping/ignoring the result.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-27 22:54:17 +03:00
Tarasov Aleksandr 1a37729cf1 fix: rpm builds
Added cmake as a build requirement for the package.
2026-04-26 19:52:03 +03:00
Tarasov Aleksandr 86b38a250e change version to 1.7.5 (#83)
* deps: rodio v0.22.2 -> 57ad9d8a9f30398f634fbf8e4e1d53dde7243c21 with symphonia-libopus

* change version to 1.7.5

* deps: update cargo-sources.json
2026-04-26 19:32:12 +03:00
Tarasov Aleksandr 54fa278cea feat: opus support (#82)
* deps: rodio v0.22.2 -> 57ad9d8a9f30398f634fbf8e4e1d53dde7243c21 with symphonia-libopus

* deps: update cargo-sources.json

* feat(gui): add .opus file extension support
2026-04-26 19:27:36 +03:00
Tarasov Aleksandr db040aa820 fix(ci): update input descriptions in workflow for clarity (#81) 2026-04-25 19:31:20 +03:00
Tarasov Aleksandr 04449e7525 fix(ci): update workflow inputs for tag and build branch selection (#80) 2026-04-25 19:28:48 +03:00
19 changed files with 1457 additions and 1025 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
+3 -1
View File
@@ -23,7 +23,9 @@ jobs:
zip jq \
libpipewire-0.3-dev \
libclang-dev \
libasound2-dev
libasound2-dev \
libdbus-1-dev \
pkg-config
- name: Checkout code
uses: actions/checkout@v4
+18
View File
@@ -6,6 +6,19 @@ on:
release:
types: [ published ]
workflow_dispatch:
inputs:
tag_name:
description: "TAG (empty to build from current branch)"
required: false
type: string
build_branch:
description: "Flatpak branch to build (stable/nightly)"
required: true
type: choice
options:
- stable
- nightly
default: "stable"
jobs:
flatter:
@@ -20,6 +33,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag_name || github.ref }}
- name: Setup GPG
id: gpg
@@ -33,12 +48,15 @@ jobs:
run: |
if [ "${{ github.event_name }}" == "release" ]; then
echo "branch=stable" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "branch=${{ inputs.build_branch }}" >> $GITHUB_OUTPUT
else
echo "branch=nightly" >> $GITHUB_OUTPUT
fi
- name: Modify Manifest
run: |
echo "branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
echo "default-branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
- name: Install SDK Extensions
+3 -1
View File
@@ -74,7 +74,9 @@ jobs:
zip jq \
libpipewire-0.3-dev \
libclang-dev \
libasound2-dev
libasound2-dev \
libdbus-1-dev \
pkg-config
- name: Checkout code at tag
uses: actions/checkout@v4
Generated
+280 -142
View File
@@ -289,6 +289,20 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "audio_thread_priority"
version = "0.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1b4a0adbf971420cca66a75361a1190f34a6762d66361e5cde03dda35d1ed3"
dependencies = [
"cfg-if",
"dbus",
"libc",
"log",
"mach2 0.4.3",
"windows-sys 0.61.2",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -486,9 +500,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -601,6 +615,15 @@ dependencies = [
"error-code",
]
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]]
name = "codespan-reporting"
version = "0.13.1"
@@ -612,9 +635,9 @@ dependencies = [
[[package]]
name = "color"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb"
checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf"
dependencies = [
"bytemuck",
]
@@ -714,10 +737,19 @@ dependencies = [
]
[[package]]
name = "coreaudio-rs"
version = "0.14.1"
name = "core_maths"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586"
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
dependencies = [
"libm",
]
[[package]]
name = "coreaudio-rs"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58"
dependencies = [
"bitflags 2.11.1",
"libc",
@@ -729,17 +761,18 @@ dependencies = [
[[package]]
name = "cpal"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7"
version = "0.18.0"
source = "git+https://github.com/RustAudio/cpal#2c7acf8ed42b6523f319145a8be256c446df5939"
dependencies = [
"alsa",
"audio_thread_priority",
"block2 0.6.2",
"coreaudio-rs",
"dasp_sample",
"jni 0.21.1",
"js-sys",
"libc",
"mach2",
"mach2 0.6.0",
"ndk",
"ndk-context",
"num-derive",
@@ -751,10 +784,9 @@ dependencies = [
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows",
"windows-core",
]
[[package]]
@@ -815,6 +847,16 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "dbus"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819"
dependencies = [
"libc",
"libdbus-sys",
]
[[package]]
name = "dirs"
version = "6.0.0"
@@ -897,9 +939,9 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "ecolor"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "137c0ce4ce4152ff7e223a7ce22ee1057cdff61fce0a45c32459c3ccec64868d"
checksum = "55f6cc0cb3b84a21232c468db972ebcddd34decbf1ff02cdebffd807c13bbd81"
dependencies = [
"bytemuck",
"emath",
@@ -907,9 +949,9 @@ dependencies = [
[[package]]
name = "eframe"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6e995b8e434d65aefd12c4519221be3e8f38efd77804ef39ca10553f4ad7063"
checksum = "fea3080bfd001aee2223dcb2228d9de7d31f41d7cfb99e8cd70df3ae8083a42f"
dependencies = [
"ahash",
"bytemuck",
@@ -942,9 +984,9 @@ dependencies = [
[[package]]
name = "egui"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f34aaf627da598dfadd64b0fee6101d22e9c451d1e5348157312720b7f459f0f"
checksum = "3cbe28ac1a9c0761319aafb9ad37737720cc49d99c13a4a6b990768fa01ffe67"
dependencies = [
"accesskit",
"ahash",
@@ -960,9 +1002,9 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71033ff78b041c9c363450f4498ff95468ef3ecbcc71a62f67036a6207d98fa4"
checksum = "2f311c0b9cdd8a38821ae6f245fbe6e1a3d7d157c77b7d22344f205b317232c8"
dependencies = [
"ahash",
"bytemuck",
@@ -980,9 +1022,9 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a2881b2bf1a305e413e644af63f836737a33d85077705ff808e88f902ff742"
checksum = "e8e97625c2fe0fadc8b92ec690fbf515e15e2efd67ca50e063ad3302ec8ee626"
dependencies = [
"arboard",
"bytemuck",
@@ -1024,9 +1066,9 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bfc6870c68d3f254e33aca8200095d422e09edacb0f365f79fe23a5ba10963"
checksum = "8c609fc87f6c70ffd3afd679cbb294985096d2fc0be33e762ad5614bde4925bc"
dependencies = [
"ahash",
"egui",
@@ -1038,9 +1080,9 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3b28d39ab6c0cac238190e6cb1e8c9047d02cb470ab942a7a3302e4cb3a8e74"
checksum = "6caa4eca47cc2358e2c5ae60843a94118e338f87099c6af4170e6e968e8d77cb"
dependencies = [
"bytemuck",
"egui",
@@ -1071,9 +1113,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "emath"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a05cd8bdf3b598488c627ca97c7fe8909448ffa26278dd3c7e535cdb554d721"
checksum = "a74fbbf7501c430b89df62d102b6bfa02162faaf3e155512c677c9d20f5708d1"
dependencies = [
"bytemuck",
]
@@ -1136,9 +1178,9 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f3017dd67f147a697ee0c8484fb568fd9553e2a0c114be5020dbbc11962841"
checksum = "92b452e348c2758115288802ca25f86ee286ce2cfae6643711ce116662311310"
dependencies = [
"ahash",
"bytemuck",
@@ -1159,9 +1201,9 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.34.1"
version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3b85a2bb775a3ab02d077a65cc31575c11b2584581913253cc11ce49f48bba"
checksum = "1644e25dbe3d663fd9c2a4181772c64b23361e377e550c10422ecc3a7de1c3c2"
[[package]]
name = "equivalent"
@@ -1242,23 +1284,9 @@ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "fax"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
dependencies = [
"fax_derive",
]
[[package]]
name = "fax_derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
[[package]]
name = "fdeflate"
@@ -1315,6 +1343,29 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
dependencies = [
"roxmltree",
]
[[package]]
name = "fontdb"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"slotmap",
"tinyvec",
"ttf-parser",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -1575,9 +1626,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.17.0"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
@@ -1714,9 +1765,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1776,7 +1827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
@@ -1891,9 +1942,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.95"
version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
dependencies = [
"cfg-if",
"futures-util",
@@ -1909,12 +1960,13 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kurbo"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb"
checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2"
dependencies = [
"arrayvec",
"euclid",
"polycool",
"smallvec",
]
@@ -1936,6 +1988,15 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libdbus-sys"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
dependencies = [
"pkg-config",
]
[[package]]
name = "libflate"
version = "2.3.0"
@@ -1985,7 +2046,7 @@ dependencies = [
"bitflags 2.11.1",
"libc",
"plain",
"redox_syscall 0.7.4",
"redox_syscall 0.7.5",
]
[[package]]
@@ -2063,13 +2124,19 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "mach2"
version = "0.5.0"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
[[package]]
name = "mach2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "memchr"
version = "2.8.0"
@@ -2151,9 +2218,9 @@ dependencies = [
[[package]]
name = "naga"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647"
checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079"
dependencies = [
"arrayvec",
"bit-set",
@@ -2230,9 +2297,9 @@ dependencies = [
[[package]]
name = "no_std_io2"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550"
checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
dependencies = [
"memchr",
]
@@ -2264,9 +2331,9 @@ dependencies = [
[[package]]
name = "normpath"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
checksum = "b9985ef7269fa99f3b12437bb698381da2428743ab90f20393f399fa14cab21a"
dependencies = [
"windows-sys 0.61.2",
]
@@ -2420,6 +2487,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"bitflags 2.11.1",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
@@ -2706,10 +2774,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "orbclient"
version = "0.3.53"
name = "opusic-sys"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd"
checksum = "2804e694ef0de3b4cbb254de565053b7cb48d3398df7fd60c6c62bed40c5372a"
dependencies = [
"cmake",
]
[[package]]
name = "orbclient"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a570f6bca41d29acb2139229a7c873ec99bc9a313bd10804081d89bfac8ff329"
dependencies = [
"libc",
"libredox",
@@ -2819,18 +2896,18 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.1.11"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.11"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
@@ -2927,6 +3004,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "polycool"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6"
dependencies = [
"arrayvec",
]
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -3003,13 +3089,13 @@ dependencies = [
[[package]]
name = "profiling"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
[[package]]
name = "pwsp"
version = "1.7.4"
version = "1.8.0"
dependencies = [
"async-trait",
"clap",
@@ -3027,6 +3113,7 @@ dependencies = [
"rodio",
"serde",
"serde_json",
"system-fonts",
"tokio",
]
@@ -3044,9 +3131,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.2"
version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [
"memchr",
]
@@ -3149,9 +3236,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
dependencies = [
"bitflags 2.11.1",
]
@@ -3235,16 +3322,22 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rodio"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb"
source = "git+https://github.com/arabianq/rodio.git?rev=1a08f281c352622bd82b87b8731585245802d9cf#1a08f281c352622bd82b87b8731585245802d9cf"
dependencies = [
"cpal",
"dasp_sample",
"num-rational",
"symphonia",
"symphonia-adapter-libopus",
"thiserror 2.0.18",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -3440,9 +3533,9 @@ checksum = "c82d449ab1bccfeec125893c6875008206f038d4eb8a09e1e10caf86f44d574e"
[[package]]
name = "siphasher"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "skrifa"
@@ -3598,6 +3691,17 @@ dependencies = [
"symphonia-metadata",
]
[[package]]
name = "symphonia-adapter-libopus"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bfc8e95f95c23ed1b5328eb66920ad28d9968c797f9c7aa755d4b45a5f47a41"
dependencies = [
"log",
"opusic-sys",
"symphonia-core",
]
[[package]]
name = "symphonia-bundle-flac"
version = "0.5.5"
@@ -3792,6 +3896,15 @@ dependencies = [
"syn",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"libc",
]
[[package]]
name = "system-deps"
version = "7.0.8"
@@ -3805,6 +3918,16 @@ dependencies = [
"version-compare",
]
[[package]]
name = "system-fonts"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f369feb844d5e08bb4e938c3f88ff359261bc94c7553ddb34e174447120b64d6"
dependencies = [
"fontdb",
"sys-locale",
]
[[package]]
name = "tap"
version = "1.0.1"
@@ -3895,10 +4018,25 @@ dependencies = [
]
[[package]]
name = "tokio"
version = "1.52.1"
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
@@ -3934,7 +4072,7 @@ dependencies = [
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow 1.0.2",
"winnow",
]
[[package]]
@@ -3955,7 +4093,7 @@ dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow 1.0.2",
"winnow",
]
[[package]]
@@ -3964,7 +4102,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow 1.0.2",
"winnow",
]
[[package]]
@@ -4005,6 +4143,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
dependencies = [
"core_maths",
]
[[package]]
name = "type-map"
version = "0.5.1"
@@ -4159,9 +4306,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.118"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [
"cfg-if",
"once_cell",
@@ -4172,9 +4319,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.68"
version = "0.4.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4182,9 +4329,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.118"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4192,9 +4339,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.118"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -4205,9 +4352,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.118"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [
"unicode-ident",
]
@@ -4383,9 +4530,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.95"
version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4425,9 +4572,9 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "wgpu"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837"
checksum = "bb3feacc458f7bee8bc1737149b42b6c731aa461039a4264a67bb6681646b250"
dependencies = [
"arrayvec",
"bitflags 2.11.1",
@@ -4453,9 +4600,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e"
checksum = "02da3ad1b568337f25513b317870960ef87073ea0945502e44b864b67a8c77b7"
dependencies = [
"arrayvec",
"bit-set",
@@ -4484,18 +4631,18 @@ dependencies = [
[[package]]
name = "wgpu-core-deps-windows-linux-android"
version = "29.0.0"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28"
checksum = "1bfb01076d0aa08b0ba9bd741e178b5cc440f5abe99d9581323a4c8b5d1a1916"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-hal"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e"
checksum = "31f8e1a9e7a8512f276f7c62e018c7fa8d60954303fed2e5750114332049193f"
dependencies = [
"bitflags 2.11.1",
"cfg-if",
@@ -4514,9 +4661,9 @@ dependencies = [
[[package]]
name = "wgpu-naga-bridge"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51"
checksum = "59c654c483f058800972c3645e95388a7eca31bf9fe1933bc20e036588a0be02"
dependencies = [
"naga",
"wgpu-types",
@@ -4524,9 +4671,9 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "29.0.1"
version = "29.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6"
checksum = "a9bcc31518a0e9735aefebedb5f7a9ef3ed1c42549c9f4c882fa9060ceaac639"
dependencies = [
"bitflags 2.11.1",
"bytemuck",
@@ -4937,15 +5084,6 @@ dependencies = [
"xkbcommon-dl",
]
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "1.0.2"
@@ -5152,9 +5290,9 @@ dependencies = [
[[package]]
name = "zbus"
version = "5.14.0"
version = "5.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc"
checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1"
dependencies = [
"async-broadcast",
"async-executor",
@@ -5179,7 +5317,7 @@ dependencies = [
"uds_windows",
"uuid",
"windows-sys 0.61.2",
"winnow 0.7.15",
"winnow",
"zbus_macros",
"zbus_names",
"zvariant",
@@ -5187,9 +5325,9 @@ dependencies = [
[[package]]
name = "zbus_macros"
version = "5.14.0"
version = "5.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222"
checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -5202,12 +5340,12 @@ dependencies = [
[[package]]
name = "zbus_names"
version = "4.3.1"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
dependencies = [
"serde",
"winnow 0.7.15",
"winnow",
"zvariant",
]
@@ -5233,9 +5371,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -5336,24 +5474,24 @@ dependencies = [
[[package]]
name = "zvariant"
version = "5.10.0"
version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b"
checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee"
dependencies = [
"endi",
"enumflags2",
"serde",
"url",
"winnow 0.7.15",
"winnow",
"zvariant_derive",
"zvariant_utils",
]
[[package]]
name = "zvariant_derive"
version = "5.10.0"
version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c"
checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -5364,13 +5502,13 @@ dependencies = [
[[package]]
name = "zvariant_utils"
version = "3.3.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691"
dependencies = [
"proc-macro2",
"quote",
"serde",
"syn",
"winnow 0.7.15",
"winnow",
]
+7 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "pwsp"
version = "1.7.4"
version = "1.8.0"
edition = "2024"
authors = ["arabian"]
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
@@ -12,7 +12,7 @@ keywords = ["soundpad", "pipewire", "linux", "cli", "gui"]
[dependencies]
tokio = { version = "1.52.1", features = ["full"] }
tokio = { version = "1.52.3", features = ["full"] }
async-trait = "0.1.89"
serde = { version = "1.0.228", features = ["derive"] }
@@ -29,8 +29,9 @@ clap = { version = "4.6.1", default-features = false, features = [
dirs = "6.0.0"
itertools = "0.14.0"
rodio = { version = "0.22.2", default-features = false, features = [
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "1a08f281c352622bd82b87b8731585245802d9cf", default-features = false, features = [
"symphonia-all",
"symphonia-libopus",
"playback",
] }
pipewire = "0.9.2"
@@ -39,12 +40,13 @@ rfd = { version = "0.17.2", default-features = false, features = [
"xdg-portal",
] }
opener = { version = "0.8.4", features = ["reveal"] }
system-fonts = "0.1.0"
egui = { version = "0.34.1", default-features = false, features = [
egui = { version = "0.34.2", default-features = false, features = [
"default_fonts",
"rayon",
] }
eframe = { version = "0.34.1", default-features = false, features = [
eframe = { version = "0.34.2", default-features = false, features = [
"default_fonts",
"glow",
"x11",
+71 -188
View File
@@ -1,237 +1,120 @@
# **🎵 Pipewire Soundpad (PWSP)**
<div align="center">
<h1>🎵 PipeWire Soundpad (PWSP)</h1>
<p><b>A simple, modern, and powerful soundboard for Linux, written in Rust.</b></p>
<img src="assets/screenshot.png" alt="PWSP Screenshot" width="700"/>
</div>
**PipeWire Soundpad (PWSP)** is a simple yet powerful **soundboard application** written in **Rust**. It provides a
user-friendly graphical interface for **managing and playing audio files, directing their output directly to the virtual
microphone.** This makes it an ideal tool for gamers, streamers, and anyone looking to inject sound effects into voice
chats on platforms like **Discord, Zoom, or Teamspeak**.
## 🌟 Overview
**PipeWire Soundpad (PWSP)** is a graphical soundboard application that routes audio directly to your virtual microphone using **PipeWire**. It provides an intuitive interface for managing your audio collection, making it an ideal tool for gamers, streamers, and anyone looking to inject sound effects into voice chats on platforms like Discord, Zoom, or TeamSpeak.
![screenshot.png](assets/screenshot.png)
## ✨ Key Features
* **🎙️ Virtual Microphone Output:** Seamlessly mixes your microphone input with sound effects by automatically managing PipeWire virtual devices.
* **🎵 Multi-Format Support:** Plays popular audio formats including `mp3`, `wav`, `ogg`, `flac`, `mp4`, and `aac`.
* **⚡ Global Hotkeys:** Trigger sounds instantly from anywhere, even when the app is running in the background.
* **📂 Smart Collection Management:** Drag-and-drop folders, quick search, and collapsible tracks to keep your library organized.
* **🎛️ Advanced Playback Controls:** Individual volume sliders, play/pause, position scrubbing, and concurrent multi-track playback.
* **🔌 Plug & Play:** Automatically detects when an input device is connected or disconnected and handles linking/unlinking on the fly.
* **🖥️ Modern GUI:** Clean, responsive, and lightweight interface powered by [egui](https://egui.rs/).
# **🌟 Key Features**
## ⚙️ Architecture
PWSP is built with a client-server model to ensure stability and separation of concerns:
* **`pwsp-daemon`**: The background engine. It runs silently, managing PipeWire virtual devices, audio routing, and playback.
* **`pwsp-gui`**: The graphical interface. Communicates with the daemon via a Unix socket to control playback and settings.
* **`pwsp-cli`**: The command-line tool. Perfect for scripting, hotkey binding, or quick terminal-based control.
* **Multi-Format Support**: Play audio files in popular formats, including _**mp3**_, _**wav**_, _**ogg**_, _**flac**_,
_**mp4**_, and _**aac**_.
* **Virtual Microphone Output**: The application routes audio through a virtual device created by PipeWire, allowing
other users to hear the sounds as if you were speaking into your microphone.
* **Modern and Clean GUI**: The interface is built with the [egui](https://egui.rs) library, ensuring an intuitive and
responsive user experience.
* **Sound Collection Management**: Easily add and remove directories containing your audio files. The application scans
these folders and displays all supported files for quick access.
* **Quick Search**: Use the built-in search bar to instantly find any sound file within your library.
* **Detailed Playback Controls**:
* **Play/Pause button**.
* **Volume slider** for individual sound adjustment.
* **Position slider** to fast-forward or rewind the audio.
* **Persistent Configuration**: The list of added directories and your selected audio output device are saved
automatically, so you won't need to reconfigure them every time you launch the application.
* **Collapsible Audio Tracks**: You can collapse every audio track to save space.
* **Drag and Drop Directories**: Reorder your sound directories easily using drag and drop.
* **Automatic Device Detection**: PWSP automatically detects when an input device is connected or disconnected and handles linking/unlinking.
* **Global Hotkeys**: Assign custom keyboard shortcuts to any sound file (or action) to trigger playback instantly, even when the application is not in focus.
---
## 🚀 Installation
# **⚙️ How It Works**
PWSP is designed with a clear separation of concerns, operating through a client-server architecture. It consists of
three main components:
* **pwsp-daemon**: This is the core of the application. It runs silently in the background, managing all the
heavy-lifting tasks. The daemon is responsible for:
* Creating and managing virtual audio devices.
* Linking these devices within the PipeWire graph.
* Handling all audio playback.
* **UnixSocket**. This is how you interact with your sound collection, control playback, and configure settings.
* **pwsp-gui**: This is the graphical user interface. It acts as a client that communicates with pwsp-daemon via a
* **pwsp-cli**: This is the command-line interface, also acting as a client. It provides a way to control the daemon
without a GUI, allowing for scripting or quick command-based actions.
# **🚀 Installation**
## **Pre-built Packages**
You can download pre-built binaries and .deb packages from
the [releases page](https://github.com/arabianq/pipewire-soundpad/releases).
## **Flatpak**
You can install PWSP via Flatpak from our custom repository hosted on GitHub Pages.
Add the repository:
### 📦 Flatpak (Recommended)
Install PWSP via Flatpak from our custom repository:
```bash
flatpak remote-add --user --if-not-exists arabianq-repo https://arabianq.github.io/pipewire-soundpad/index.flatpakrepo
```
flatpak remote-add --user --if-not-exists pwsp-repo https://arabianq.github.io/pipewire-soundpad/index.flatpakrepo
Install the stable version:
```bash
# Install stable version
flatpak install --user arabianq-repo ru.arabianq.pwsp//stable
```
Or install the nightly version (latest commit to `main`):
```bash
# Or install the nightly version (latest commit)
flatpak install --user arabianq-repo ru.arabianq.pwsp//nightly
```
## **Fedora Linux (and derivatives)**
If you're using Fedora, you can install PWSP from a dedicated repository using DNF.
Add the repository:
### 🐧 Linux Packages
**Fedora (and derivatives):**
```bash
sudo dnf copr enable arabianq/pwsp
```
Update cache:
```bash
sudo dnf makecache
```
Install PWSP:
```bash
sudo dnf install pwsp
```
## **Arch Linux**
There is pwsp package in AUR.
You can install it using yay, paru or any other AUR helper.
**Arch Linux (AUR):**
```bash
paru pwsp-bin # or paru pwsp to build it locally
paru -S pwsp-bin # or 'pwsp' to build from source
```
## **Installing using cargo**
**Debian / Ubuntu:**
Download pre-built `.deb` packages or standalone binaries from the [Releases page](https://github.com/arabianq/pipewire-soundpad/releases).
### 🦀 Cargo / Source Build
```bash
cargo install pwsp
```
## **Building from source**
#### **Requirements**
* **Rust**: Install [Rust](https://www.rust-lang.org/tools/install) (using rustup is recommended).
* **PipeWire**: Ensure that [PipeWire](https://pipewire.org/) is installed and running on your system.
#### **Build Instructions**
Clone the repository:
```bash
# OR clone and build manually:
git clone https://github.com/arabianq/pipewire-soundpad.git
cd pipewire-soundpad
```
Build the project:
```bash
cargo build --release
```
*(Note: Requires Rust toolchain and PipeWire running on your system).*
Now you have three binary files inside ./target/release/:
---
- **pwsp-gui**
- **pwsp-cli**
- **pwsp-daemon**
## 🎮 Usage
# **🎮 Usage**
Before using pwsp-gui or pwsp-cli, you **must** first run the pwsp-daemon in the background.
### **Running the Daemon**
You can start the daemon from the terminal or enable the systemd service for automatic startup.
* **Manual Start:**
### 1. Start the Daemon
Before using the GUI or CLI, the daemon must be running in the background.
```bash
/path/to/your/pwsp-daemon &
```
* **Using systemd (recommended):**
If you installed PWSP using prebuilt packages, the systemd service is added automatically.
1. **Start the service:**
```bash
systemctl --user start pwsp-daemon
```
2. **Enable autostart (starts on login):**
```bash
# Recommended: Start and enable via systemd (starts on login)
systemctl --user enable --now pwsp-daemon
# Manual start (if not using systemd):
pwsp-daemon &
```
### **Using the GUI**
1. **Add Sounds**: Click the **"+"** button and select a folder containing your audio files. The application
will automatically list all supported files.
2. **Select Microphone**: In the main application window, select your microphone. PWSP will automatically
create a virtual microphone and feed it sound from two sources: **your microphone** and the **audio files**.
3. **Playback**: Click on a file in the list to load it, then use the **"Play"** and **"Pause"** buttons to control
playback. You can also play single file once using **"Play File"** button.
### **Using the CLI**
The pwsp-cli tool allows you to control the daemon from the command line.
* **General Help**: To see a list of all available commands, run:
```bash
pwsp-cli --help
```
* **Example Commands**:
* **Play a file**:
```bash
pwsp-cli action play <file_path>
```
* **Get the current volume**:
### 2. Using the GUI
1. **Add Sounds:** Click the **"+"** button to add a directory containing your audio files.
2. **Select Mic:** Choose your physical microphone from the dropdown. PWSP will instantly create a virtual microphone combining your voice and the soundboard.
3. **Play:** Click any sound to play it, adjust its volume, or assign a hotkey for quick access.
### 3. Using the CLI
Control the daemon directly from your terminal:
```bash
pwsp-cli action play /path/to/sound.mp3
pwsp-cli get volume
```
* **Set playback position to 20 seconds**:
```bash
pwsp-cli set position 20
pwsp-cli --help # View all commands
```
### **Hotkeys & Controls**
---
#### **Keyboard Shortcuts**
## ⌨️ Shortcuts & Controls
| Key | Action |
| :----------------------- | :--------------------------------------------------- |
| **Space** | Pause / Resume audio |
| **Backspace** | Stop all audio tracks |
| **Enter** | Play selected file (stops all other tracks) |
| **Ctrl + Enter** | Add selected file to playback (plays simultaneously) |
| **Shift + Enter** | Replace the last added track with the selected one |
| **I** | Open / Close settings |
| **/** | Focus search field |
| **Ctrl + ↑ / ↓** | Navigate through files |
| **Ctrl + Shift + ↑ / ↓** | Navigate through directories |
| Action | Keyboard | Mouse |
| :----------------------------------- | :--------------------- | :------------------- |
| **Play Track** (Stops others) | `Enter` | `Left Click` |
| **Add Track** (Plays simultaneously) | `Ctrl + Enter` | `Ctrl + Left Click` |
| **Replace Last Track** | `Shift + Enter` | `Shift + Left Click` |
| **Pause / Resume** | `Space` | |
| **Stop All Tracks** | `Backspace` | |
| **Open / Close Settings** | `I` | |
| **Search** | `/` | |
| **Navigate Files** | `Ctrl + ↑ / ↓` | |
| **Navigate Directories** | `Ctrl + Shift + ↑ / ↓` | |
#### **Mouse Controls**
---
* **Left Click**: Play track (stops all other tracks).
* **Ctrl + Left Click**: Add track (plays simultaneously with current tracks).
* **Shift + Left Click**: Replace the last added track with the selected one.
## 🤝 Contributing
Contributions, issues, and feature requests are welcome! Feel free to check out the [issues page](https://github.com/arabianq/pipewire-soundpad/issues).
# **🤝 Contributing**
Contributions are welcome\! If you have ideas for improvements or find a bug, feel free to create
an [issue](https://github.com/arabianq/pipewire-soundpad/issues) or submit
a [pull request](https://github.com/arabianq/pipewire-soundpad/pulls).
# **📜 License**
This project is licensed under
the [MIT License](https://github.com/arabianq/pipewire-soundpad/blob/main/LICENSE).
# **🤖 AI Wiki**
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/arabianq/pipewire-soundpad)
## 📜 License
This project is licensed under the [MIT License](LICENSE).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 94 KiB

+4 -4
View File
@@ -1,7 +1,7 @@
pkgbase = pwsp-bin
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
pkgver = 1.7.4
pkgrel = 2
pkgver = 1.8.0
pkgrel = 1
url = https://github.com/arabianq/pipewire-soundpad
arch = x86_64
license = MIT
@@ -9,8 +9,8 @@ depends = pipewire
depends = alsa-lib
provides = pwsp
conflicts = pwsp
source = pwsp-bin-1.7.4.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.7.4/pwsp-v1.7.4-linux-x64.zip
source = pipewire-soundpad-1.7.4.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.4.tar.gz
source = pwsp-bin-1.8.0.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.8.0/pwsp-v1.8.0-linux-x64.zip
source = pipewire-soundpad-1.8.0.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.8.0.tar.gz
sha256sums = SKIP
sha256sums = SKIP
+2 -2
View File
@@ -1,8 +1,8 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgname=pwsp-bin
_pkgname=pipewire-soundpad
pkgver=1.7.4
pkgrel=2
pkgver=1.8.0
pkgrel=1
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
arch=('x86_64')
url="https://github.com/arabianq/pipewire-soundpad"
+3 -2
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp
pkgdesc = Lets you play audio files through your microphone
pkgver = 1.7.4
pkgver = 1.8.0
pkgrel = 1
url = https://github.com/arabianq/pipewire-soundpad
arch = any
@@ -8,9 +8,10 @@ pkgbase = pwsp
makedepends = clang
makedepends = rust
makedepends = cargo
makedepends = cmake
makedepends = pipewire
makedepends = alsa-lib
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.4.tar.gz
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.8.0.tar.gz
sha256sums = SKIP
pkgname = pwsp
+2 -2
View File
@@ -1,13 +1,13 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgsubn=pwsp
pkgname=pwsp
pkgver=1.7.4
pkgver=1.8.0
pkgrel=1
pkgdesc="Lets you play audio files through your microphone"
arch=('any')
url="https://github.com/arabianq/pipewire-soundpad"
license=('MIT')
makedepends=(clang rust cargo pipewire alsa-lib)
makedepends=(clang rust cargo cmake pipewire alsa-lib)
source=("$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('SKIP')
File diff suppressed because one or more lines are too long
@@ -25,7 +25,7 @@
<name>arabian</name>
</developer>
<releases>
<release version="1.7.4" date="2026-04-25" />
<release version="1.8.0" date="2026-05-13" />
</releases>
<content_rating type="oars-1.1" />
</component>
+4 -1
View File
@@ -4,7 +4,7 @@
%global cargo_install_lib 0
Name: pwsp
Version: 1.7.4
Version: 1.8.0
Release: %autorelease
Summary: Lets you play audio files through your microphone
@@ -18,6 +18,9 @@ BuildRequires: cargo
BuildRequires: pipewire-devel
BuildRequires: alsa-lib-devel
BuildRequires: clang-devel
BuildRequires: cmake
BuildRequires: dbus-devel
BuildRequires: pkgconf-pkg-config
%global _description %{expand:
PWSP lets you play audio files through your microphone. Has both CLI and
+133 -113
View File
@@ -9,10 +9,7 @@ use egui_material_icons::icons::*;
use pwsp::types::socket::Request;
use pwsp::types::{audio_player::TrackInfo, gui::AppState};
use pwsp::utils::gui::{format_time_pair, make_request_async};
use std::{
path::{Path, PathBuf},
time::Instant,
};
use std::{path::Path, time::Instant};
enum TrackAction {
Pause(u32),
@@ -137,7 +134,22 @@ impl SoundpadGui {
ui.vertical(|ui| {
ui.spacing_mut().item_spacing.y = 5.0;
// --- Header ---
self.draw_hotkeys_header(ui);
ui.separator();
self.draw_hotkeys_search(ui);
ui.separator();
ui.add_space(5.0);
let action = self.draw_hotkeys_table(ui);
if let Some(action) = action {
self.handle_hotkey_action(action);
}
});
}
fn draw_hotkeys_header(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
let back_button = Button::new(ICON_ARROW_BACK).frame(false);
if ui.add(back_button).clicked() {
@@ -148,10 +160,9 @@ impl SoundpadGui {
ui.label(RichText::new("Hotkeys").color(Color32::WHITE).monospace());
});
});
}
ui.separator();
// --- Search and Add Command ---
fn draw_hotkeys_search(&mut self, ui: &mut Ui) {
ui.horizontal(|ui| {
ui.menu_button(format!("{} Add Command", ICON_ADD.codepoint), |ui| {
let mut selected_cmd = None;
@@ -190,10 +201,9 @@ impl SoundpadGui {
.desired_width(f32::INFINITY),
);
});
}
ui.separator();
ui.add_space(5.0);
fn draw_hotkeys_table(&mut self, ui: &mut Ui) -> Option<HotkeyAction> {
let conflicts = self.app_state.hotkey_config.find_conflicts();
let conflict_slots: std::collections::HashSet<&str> =
conflicts.into_iter().flat_map(|(a, b)| [a, b]).collect();
@@ -218,7 +228,6 @@ impl SoundpadGui {
.to_lowercase()
.contains(&search)
})
.cloned()
.collect();
let available_width = ui.available_width();
@@ -270,8 +279,7 @@ impl SoundpadGui {
row.col(|_| {});
row.col(|ui| {
ui.label(
RichText::new("No hotkey slots configured.")
.color(Color32::GRAY),
RichText::new("No hotkey slots configured.").color(Color32::GRAY),
);
});
row.col(|_| {});
@@ -293,8 +301,7 @@ impl SoundpadGui {
.on_hover_text("Key chord conflict");
}
ui.add(
Label::new(RichText::new(&slot.slot).monospace())
.truncate(),
Label::new(RichText::new(&slot.slot).monospace()).truncate(),
);
});
});
@@ -303,9 +310,7 @@ impl SoundpadGui {
row.col(|ui| {
let action_name = match slot.action.name.as_str() {
"play" => {
if let Some(file_path_str) =
slot.action.args.get("file_path")
{
if let Some(file_path_str) = slot.action.args.get("file_path") {
Path::new(file_path_str)
.file_name()
.unwrap_or_default()
@@ -322,9 +327,7 @@ impl SoundpadGui {
"toggle_loop" => "Toggle Loop".to_string(),
other => other.to_string(),
};
ui.add(
Label::new(RichText::new(action_name).monospace()).truncate(),
);
ui.add(Label::new(RichText::new(action_name).monospace()).truncate());
});
// Column 3: Key Chord
@@ -380,7 +383,10 @@ impl SoundpadGui {
}
});
if let Some(action) = action {
action
}
fn handle_hotkey_action(&mut self, action: HotkeyAction) {
match action {
HotkeyAction::Remove(slot) => {
make_request_async(Request::clear_hotkey(&slot));
@@ -399,8 +405,6 @@ impl SoundpadGui {
}
}
}
});
}
fn draw_header(&mut self, ui: &mut Ui) {
ui.vertical_centered_justified(|ui| {
@@ -409,10 +413,9 @@ impl SoundpadGui {
return;
}
let tracks = self.audio_player_state.tracks.clone();
let mut action = None;
for track in tracks {
for track in &self.audio_player_state.tracks {
CollapsingHeader::new(
RichText::new(
track
@@ -427,7 +430,7 @@ impl SoundpadGui {
)
.default_open(true)
.show(ui, |ui| {
if let Some(act) = Self::draw_track_control(ui, &mut self.app_state, &track) {
if let Some(act) = Self::draw_track_control(ui, &mut self.app_state, track) {
action = Some(act);
}
});
@@ -445,6 +448,99 @@ impl SoundpadGui {
});
}
fn draw_playback_controls(ui: &mut Ui, track: &TrackInfo) -> Option<TrackAction> {
let mut action = None;
let play_button = Button::new(if track.paused {
ICON_PLAY_ARROW
} else {
ICON_PAUSE
})
.corner_radius(15.0);
if ui.add_sized([30.0, 30.0], play_button).clicked() {
action = Some(if track.paused {
TrackAction::Resume(track.id)
} else {
TrackAction::Pause(track.id)
});
}
let loop_button = Button::new(
RichText::new(if track.looped {
ICON_REPEAT_ONE
} else {
ICON_REPEAT
})
.size(18.0),
)
.frame(false);
if ui.add_sized([15.0, 30.0], loop_button).clicked() {
action = Some(TrackAction::ToggleLoop(track.id));
}
action
}
fn draw_position_control(
ui: &mut Ui,
ui_state: &mut pwsp::types::gui::TrackUiState,
track: &TrackInfo,
default_slider_width: f32,
) {
let duration = track.duration.unwrap_or(1.0);
let position_slider = Slider::new(&mut ui_state.position_slider_value, 0.0..=duration)
.show_value(false)
.step_by(0.01);
let position_slider_width = ui.available_width()
- (30.0 * 3.0)
- default_slider_width
- (ui.spacing().item_spacing.x * 6.0);
ui.spacing_mut().slider_width = position_slider_width;
if ui.add_sized([30.0, 30.0], position_slider).drag_stopped() {
ui_state.position_dragged = true;
}
let time_label =
Label::new(RichText::new(format_time_pair(track.position, duration)).monospace());
ui.add_sized([30.0, 30.0], time_label);
}
fn draw_volume_control(
ui: &mut Ui,
ui_state: &mut pwsp::types::gui::TrackUiState,
track: &TrackInfo,
default_slider_width: f32,
) {
let volume_icon = Self::get_volume_icon(track.volume);
let volume_label = Label::new(RichText::new(volume_icon).size(18.0));
ui.add_sized([30.0, 30.0], volume_label)
.on_hover_text(format!("Volume: {:.0}%", track.volume * 100.0));
let volume_slider = Slider::new(&mut ui_state.volume_slider_value, 0.0..=1.0)
.show_value(false)
.step_by(0.01);
ui.spacing_mut().slider_width = default_slider_width - 30.0;
ui.spacing_mut().item_spacing.x = 0.0;
if ui.add_sized([30.0, 30.0], volume_slider).drag_stopped() {
ui_state.volume_dragged = true;
}
}
fn draw_stop_control(ui: &mut Ui, track: &TrackInfo) -> Option<TrackAction> {
let stop_button = Button::new(ICON_CLOSE).frame(false);
if ui.add_sized([30.0, 30.0], stop_button).clicked() {
Some(TrackAction::Stop(track.id))
} else {
None
}
}
fn draw_track_control(
ui: &mut Ui,
app_state: &mut AppState,
@@ -475,93 +571,17 @@ impl SoundpadGui {
let mut action = None;
ui.horizontal_top(|ui| {
// ---------- Play Button ----------
let play_button = Button::new(if track.paused {
ICON_PLAY_ARROW
} else {
ICON_PAUSE
})
.corner_radius(15.0);
let play_button_response = ui.add_sized([30.0, 30.0], play_button);
if play_button_response.clicked() {
if track.paused {
action = Some(TrackAction::Resume(track.id));
} else {
action = Some(TrackAction::Pause(track.id));
if let Some(act) = Self::draw_playback_controls(ui, track) {
action = Some(act);
}
}
// --------------------------------
// ---------- Loop Button ----------
let loop_button = Button::new(
RichText::new(if track.looped {
ICON_REPEAT_ONE
} else {
ICON_REPEAT
})
.size(18.0),
)
.frame(false);
let loop_button_response = ui.add_sized([15.0, 30.0], loop_button);
if loop_button_response.clicked() {
action = Some(TrackAction::ToggleLoop(track.id));
}
// --------------------------------
// ---------- Position Slider ----------
let duration = track.duration.unwrap_or(1.0);
let position_slider = Slider::new(&mut ui_state.position_slider_value, 0.0..=duration)
.show_value(false)
.step_by(0.01);
let default_slider_width = ui.spacing().slider_width;
let position_slider_width = ui.available_width()
- (30.0 * 3.0)
- default_slider_width
- (ui.spacing().item_spacing.x * 6.0);
ui.spacing_mut().slider_width = position_slider_width;
let position_slider_response = ui.add_sized([30.0, 30.0], position_slider);
if position_slider_response.drag_stopped() {
ui_state.position_dragged = true;
Self::draw_position_control(ui, ui_state, track, default_slider_width);
Self::draw_volume_control(ui, ui_state, track, default_slider_width);
if let Some(act) = Self::draw_stop_control(ui, track) {
action = Some(act);
}
// --------------------------------
// ---------- Time Label ----------
let time_label =
Label::new(RichText::new(format_time_pair(track.position, duration)).monospace());
ui.add_sized([30.0, 30.0], time_label);
// --------------------------------
// ---------- Volume Icon ----------
let volume_icon = Self::get_volume_icon(track.volume);
let volume_label = Label::new(RichText::new(volume_icon).size(18.0));
ui.add_sized([30.0, 30.0], volume_label)
.on_hover_text(format!("Volume: {:.0}%", track.volume * 100.0));
// --------------------------------
// ---------- Volume Slider ----------
let volume_slider = Slider::new(&mut ui_state.volume_slider_value, 0.0..=1.0)
.show_value(false)
.step_by(0.01);
ui.spacing_mut().slider_width = default_slider_width - 30.0;
ui.spacing_mut().item_spacing.x = 0.0;
let volume_slider_response = ui.add_sized([30.0, 30.0], volume_slider);
if volume_slider_response.drag_stopped() {
ui_state.volume_dragged = true;
}
// --------------------------------
// ---------- Stop Button ---------
let stop_button = Button::new(ICON_CLOSE).frame(false);
let stop_button_response = ui.add_sized([30.0, 30.0], stop_button);
if stop_button_response.clicked() {
action = Some(TrackAction::Stop(track.id));
}
// --------------------------------
});
action
@@ -836,11 +856,11 @@ impl SoundpadGui {
});
}
fn get_hotkey_badge(&self, path: &PathBuf) -> Option<String> {
fn get_hotkey_badge(&self, path: &Path) -> Option<String> {
for slot in &self.app_state.hotkey_config.slots {
if slot.action.name == "play"
&& let Some(file_path_str) = slot.action.args.get("file_path")
&& Path::new(file_path_str) == path.as_path()
&& Path::new(file_path_str) == path
{
if let Some(chord) = &slot.key_chord {
return Some(format!("[{}]", chord));
+8 -2
View File
@@ -9,7 +9,10 @@ use std::path::PathBuf;
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
let key_name = key.name();
let is_valid = (key_name.len() == 1
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
&& key_name
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphanumeric()))
|| (key_name.starts_with('F')
&& key_name.len() > 1
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
@@ -60,7 +63,10 @@ pub fn parse_chord(chord: &str) -> Option<(Modifiers, Key)> {
let key_name = parts[parts.len() - 1];
let is_valid = (key_name.len() == 1
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
&& key_name
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphanumeric()))
|| (key_name.starts_with('F')
&& key_name.len() > 1
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
+60 -5
View File
@@ -3,7 +3,7 @@ mod input;
mod update;
use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native};
use egui::{Context, Vec2, ViewportBuilder};
use egui::{Context, FontData, FontDefinitions, FontFamily, FontTweak, Vec2, ViewportBuilder};
use itertools::Itertools;
use pwsp::{
types::{
@@ -21,12 +21,14 @@ use pwsp::{
use rfd::FileDialog;
use std::{
error::Error,
path::PathBuf,
fs,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use system_fonts::{FontStyle, FoundFontSource, find_for_locale};
const SUPPORTED_EXTENSIONS: [&str; 12] = [
"mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", "avi",
const SUPPORTED_EXTENSIONS: [&str; 13] = [
"mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", "avi", "opus",
];
struct SoundpadGui {
@@ -120,7 +122,7 @@ impl SoundpadGui {
}
}
pub fn play_file(&mut self, path: &PathBuf, concurrent: bool) {
pub fn play_file(&mut self, path: &Path, concurrent: bool) {
make_request_async(Request::play(&path.to_string_lossy(), concurrent));
}
@@ -196,6 +198,54 @@ impl SoundpadGui {
}
}
fn add_font(
font_name: &str,
font_bytes: &[u8],
fonts: &mut FontDefinitions,
) -> Result<(), Box<dyn Error>> {
let font_data = FontData::from_owned(font_bytes.to_vec()).tweak(FontTweak {
scale: 1.0,
hinting_override: Some(true),
..Default::default()
});
fonts
.font_data
.insert(font_name.to_owned(), font_data.into());
fonts
.families
.entry(FontFamily::Proportional)
.or_default()
.insert(0, font_name.to_owned());
fonts
.families
.entry(FontFamily::Monospace)
.or_default()
.insert(0, font_name.to_owned());
Ok(())
}
fn load_system_fonts(fonts: &mut FontDefinitions) -> Result<(), Box<dyn Error>> {
let (_, en_sans) = find_for_locale("en", FontStyle::Sans);
let (_, en_serif) = find_for_locale("en", FontStyle::Serif);
let (_, ja_sans) = find_for_locale("ja", FontStyle::Sans);
let system_fonts = [en_sans, en_serif, ja_sans].concat();
for font in system_fonts.iter().rev() {
let font_bytes = match &font.source {
FoundFontSource::Path(path) => fs::read(path)?,
FoundFontSource::Bytes(bytes) => bytes.to_vec(),
};
add_font(&font.key, &font_bytes, fonts)?;
}
Ok(())
}
pub async fn run() -> Result<(), Box<dyn Error>> {
const ICON: &[u8] = include_bytes!("../../assets/icon.png");
@@ -218,6 +268,11 @@ pub async fn run() -> Result<(), Box<dyn Error>> {
options,
Box::new(|cc| {
egui_material_icons::initialize(&cc.egui_ctx);
let mut fonts = FontDefinitions::default();
load_system_fonts(&mut fonts).ok();
cc.egui_ctx.set_fonts(fonts);
Ok(Box::new(SoundpadGui::new(&cc.egui_ctx)))
}),
) {
+114 -35
View File
@@ -9,11 +9,11 @@ use tokio::{
time::{Duration, timeout},
};
pub fn setup_pipewire_context() -> (MainLoopRc, ContextRc) {
pub fn setup_pipewire_context() -> Result<(MainLoopRc, ContextRc), String> {
pipewire::init();
let main_loop = MainLoopRc::new(None).expect("Failed to initialize pipewire main loop");
let context = ContextRc::new(&main_loop, None).expect("Failed to create pipewire context");
(main_loop, context)
let main_loop = MainLoopRc::new(None).map_err(|e| e.to_string())?;
let context = ContextRc::new(&main_loop, None).map_err(|e| e.to_string())?;
Ok((main_loop, context))
}
fn parse_global_object(
@@ -85,8 +85,15 @@ fn parse_global_object(
async fn pw_get_global_objects_thread(
main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>,
pw_receiver: pipewire::channel::Receiver<Terminate>,
init_sender: std::sync::mpsc::SyncSender<Result<(), String>>,
) {
let (main_loop, context) = setup_pipewire_context();
let (main_loop, context) = match setup_pipewire_context() {
Ok(res) => res,
Err(e) => {
let _ = init_sender.send(Err(e));
return;
}
};
// Stop main loop on Terminate message
let _receiver = pw_receiver.attach(main_loop.loop_(), {
@@ -94,12 +101,24 @@ async fn pw_get_global_objects_thread(
move |_| _main_loop.quit()
});
let core = context
.connect(None)
.expect("Failed to connect to pipewire context");
let registry = core
.get_registry()
.expect("Failed to get registry from pipewire context");
let core = match context.connect(None) {
Ok(core) => core,
Err(e) => {
let _ = init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
return;
}
};
let registry = match core.get_registry() {
Ok(registry) => registry,
Err(e) => {
let _ = init_sender.send(Err(format!(
"Failed to get registry from pipewire context: {}",
e
)));
return;
}
};
let _listener = registry
.add_listener_local()
@@ -115,6 +134,11 @@ async fn pw_get_global_objects_thread(
})
.register();
// Signal successful initialization
if init_sender.send(Ok(())).is_err() {
return;
}
main_loop.run();
}
@@ -122,10 +146,17 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
// Channels to communicate with pipewire thread
let (main_sender, mut main_receiver) = mpsc::channel(10);
let (pw_sender, pw_receiver) = pipewire::channel::channel();
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
// Spawn pipewire thread in background
let _pw_thread =
tokio::spawn(async move { pw_get_global_objects_thread(main_sender, pw_receiver).await });
let _pw_thread = tokio::spawn(async move {
pw_get_global_objects_thread(main_sender, pw_receiver, init_sender).await
});
// Wait for initialization to complete
if let Err(e) = init_receiver.recv()? {
return Err(e.into());
}
let mut input_devices: HashMap<u32, AudioDevice> = HashMap::new();
let mut output_devices: HashMap<u32, AudioDevice> = HashMap::new();
@@ -150,9 +181,7 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
}
Ok(None) | Err(_) => {
// Pipewire thread is finished and we can collect our devices
pw_sender
.send(Terminate {})
.expect("Failed to terminate pipewire thread");
let _ = pw_sender.send(Terminate {});
for port in ports {
let node_id = port.node_id;
@@ -200,8 +229,8 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
let mut output_devices: Vec<AudioDevice> =
output_devices.values().cloned().collect();
input_devices.sort_by(|a, b| a.id.cmp(&b.id));
output_devices.sort_by(|a, b| a.id.cmp(&b.id));
input_devices.sort_by_key(|a| a.id);
output_devices.sort_by_key(|a| a.id);
return Ok((input_devices, output_devices));
}
@@ -226,12 +255,24 @@ pub async fn get_device(device_name: &str) -> Result<AudioDevice, Box<dyn Error>
pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
let _pw_thread = thread::spawn(move || {
let (main_loop, context) = setup_pipewire_context();
let core = context
.connect(None)
.expect("Failed to connect to pipewire context");
let (main_loop, context) = match setup_pipewire_context() {
Ok(res) => res,
Err(e) => {
let _ = init_sender.send(Err(e));
return;
}
};
let core = match context.connect(None) {
Ok(core) => core,
Err(e) => {
let _ =
init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
return;
}
};
let props = properties!(
"factory.name" => "support.null-audio-sink",
@@ -243,9 +284,13 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
"object.linger" => "false", // Destroy the node on app exit
);
let _node = core
.create_object::<pipewire::node::Node>("adapter", &props)
.expect("Failed to create virtual mic");
let _node = match core.create_object::<pipewire::node::Node>("adapter", &props) {
Ok(node) => node,
Err(e) => {
let _ = init_sender.send(Err(format!("Failed to create virtual mic: {}", e)));
return;
}
};
let _receiver = pw_receiver.attach(main_loop.loop_(), {
let _main_loop = main_loop.clone();
@@ -253,9 +298,16 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
});
println!("Virtual mic created");
if init_sender.send(Ok(())).is_err() {
return;
}
main_loop.run();
});
if let Err(e) = init_receiver.recv()? {
return Err(e.into());
}
Ok(pw_sender)
}
@@ -304,12 +356,24 @@ pub fn create_link(
input_fr: Port,
) -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
let _pw_thread = thread::spawn(move || {
let (main_loop, context) = setup_pipewire_context();
let core = context
.connect(None)
.expect("Failed to connect to pipewire context");
let (main_loop, context) = match setup_pipewire_context() {
Ok(res) => res,
Err(e) => {
let _ = init_sender.send(Err(e));
return;
}
};
let core = match context.connect(None) {
Ok(core) => core,
Err(e) => {
let _ =
init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
return;
}
};
let props_fl = properties! {
"link.output.node" => format!("{}", output_fl.node_id).as_str(),
@@ -324,12 +388,20 @@ pub fn create_link(
"link.input.port" => format!("{}", input_fr.port_id).as_str(),
};
let _link_fl = core
.create_object::<Link>("link-factory", &props_fl)
.expect("Failed to create link FL");
let _link_fr = core
.create_object::<Link>("link-factory", &props_fr)
.expect("Failed to create link FR");
let _link_fl = match core.create_object::<Link>("link-factory", &props_fl) {
Ok(link) => link,
Err(e) => {
let _ = init_sender.send(Err(format!("Failed to create link FL: {}", e)));
return;
}
};
let _link_fr = match core.create_object::<Link>("link-factory", &props_fr) {
Ok(link) => link,
Err(e) => {
let _ = init_sender.send(Err(format!("Failed to create link FR: {}", e)));
return;
}
};
let _receiver = pw_receiver.attach(main_loop.loop_(), {
let _main_loop = main_loop.clone();
@@ -340,8 +412,15 @@ pub fn create_link(
"Link created: FL: {}-{} FR: {}-{}",
output_fl.node_id, input_fl.node_id, output_fr.node_id, input_fr.node_id
);
if init_sender.send(Ok(())).is_err() {
return;
}
main_loop.run();
});
if let Err(e) = init_receiver.recv()? {
return Err(e.into());
}
Ok(pw_sender)
}