Compare commits

..

16 Commits

Author SHA1 Message Date
Tarasov Aleksandr 9f50809a99 fix(ci): add SDK extensions installation step (#79) 2026-04-25 16:57:01 +03:00
Tarasov Aleksandr 7dda4bc2b1 fix(ci): update Flatter container image to version 25.08 (#77) 2026-04-25 16:46:56 +03:00
Tarasov Aleksandr 1569955e12 chore(ci): Add flatter to host Flatpak repo on GitHub Pages (#76)
* chore(ci): Add flatter to host Flatpak repo on GitHub Pages

- Update release.yml to not upload .flatpak file to releases
- Create flatter.yml to automate building and hosting of Flatpak via GitHub pages using andyholmes/flatter
- Add nightly branch for main pushes and stable branch for releases
- Update README.md with the new Flatpak installation instructions

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

* Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-25 16:38:34 +03:00
Tarasov Aleksandr 9adc6cfbda update dependencies and change version to 1.7.4 (#75)
* deps: cargo update

cc v1.2.60 -> v1.2.61
libc v0.2.185 -> v0.2.186
winnow v1.0.1 -> v1.0.2

* deps: update cargo-sources.json for flatpak

* change version to 1.7.4
2026-04-25 15:58:49 +03:00
Tarasov Aleksandr 76b1d4f345 fix(gui): footer and hotkeys table are no longer clipped because of long filenames (#74)
* fix: truncate file button text in draw function so footer is no clipped

* fix(gui): fix hotkeys table clipping with egui_extras::TableBuilder

fully reworked hotkeys page

* deps: update flatpak cargo-sources.json
2026-04-25 15:44:50 +03:00
Tarasov Aleksandr 10f9937dc3 tests: parse_command set_volume edge cases (#73)
Add unit tests for parse_command in src/utils/commands.rs to ensure
robust handling of set_volume edge cases including missing or
invalid volume and id arguments.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-25 14:37:55 +03:00
Tarasov Aleksandr 498c09eb50 fix(gui): remove unwrap() calls in input handling to prevent potential panics (#72)
Replaced `.chars().next().unwrap()` with `.chars().next().is_some_and(...)` in `chord_from_event` and `parse_chord` functions in `src/gui/input.rs`. This ensures that even if the string is empty, the application will not panic, adhering to the project's safety guidelines and resolving a potential security vulnerability.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-25 14:36:46 +03:00
Tarasov Aleksandr 78e0a133b6 Bump version to 1.7.3 and update dependencies (#70)
* change version to 1.7.3

* deps: cargo update

orbclient -> v0.3.53
wasip2 -> v1.0.3+wasi-0.2.9
wit-bindgen + v0.57.1

* deps: update cargo-sources.json for flatpak
2026-04-21 19:41:19 +03:00
Tarasov Aleksandr 7f8b7194b6 refactor: move PipeWire initialization into a reusable helper function (#69)
Extract duplicated `pipewire::init()`, `MainLoopRc::new()`, and `ContextRc::new()` setup code from `pw_get_global_objects_thread`, `create_virtual_mic`, and `create_link` into a shared `setup_pipewire_context` helper in `src/utils/pipewire.rs`. Also ran codebase-wide linters to improve code quality.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-20 19:22:50 +03:00
Tarasov Aleksandr 302f153b91 refactor: break down handle_input into smaller methods in src/gui/input.rs (#67)
Extract sections from the long `handle_input` function into smaller,
context-specific helper methods such as `handle_hotkey_assignment`,
`handle_toggles`, `handle_playback_and_focus`, `handle_file_playback`,
`handle_navigation`, and `handle_hotkey_triggers`. This significantly
improves the maintainability and readability of `src/gui/input.rs`
while preserving original functionality.

In addition, ran `cargo clippy --fix` on the project to resolve a few
other minor health issues, like collapsing nested `if` statements and
reducing unnecessary allocations.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-20 19:21:59 +03:00
Tarasov Aleksandr f87dcb1564 refactor: remove unnecessary string cloning when finding hotkey conflicts (#68)
Changed `HotkeyConfig::find_conflicts` to return a `Vec<(&str, &str)>` rather than allocating owned `Strings`. In `src/gui/draw.rs`, the code now builds a `HashSet<&str>` directly from the borrowed strings using array-based flat-mapping, avoiding intermediate `Vec` allocations and redundant clones.

Benchmarked to be approximately 3.5x faster in scenarios involving many configured slots.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-20 19:20:54 +03:00
Tarasov Aleksandr d4d16f6ce7 Prepare Application for Flathub Submission (#64)
* chore(flatpak): prepare application for Flathub submission

- Change Flatpak filesystem permissions from `host` to `home` to comply with Flathub sandbox rules
- Remove `--share=network` build argument and implement offline Rust building using `flatpak-cargo-generator.py`
- Add generated `cargo-sources.json` to the Flatpak manifest to download dependencies
- Update `CARGO_HOME` environment variable to ensure vendored dependencies are found by `cargo build --offline`
- Update `.desktop` categories to meet Flathub specs (`AudioVideo` requirement)
- Add required `<releases>`, `bugtracker`, `vcs-browser` URLs, and valid `<developer>` tags to `metainfo.xml`

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

* chore(flatpak): prepare application for Flathub submission

- Change Flatpak filesystem permissions from `host` to `home` to comply with Flathub sandbox rules
- Remove `--share=network` build argument and implement offline Rust building using `flatpak-cargo-generator.py`
- Add generated `cargo-sources.json` to the Flatpak manifest to download dependencies
- Explicitly set `CARGO_HOME=$PWD/cargo` in build-commands to ensure vendored dependencies are found by `cargo build --offline`
- Update `.desktop` categories to meet Flathub specs (`AudioVideo` requirement)
- Add required `<releases>`, `bugtracker`, `vcs-browser` URLs, and valid `<developer>` tags to `metainfo.xml`

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

* chore(flatpak): add script to generate cargo sources

- Added `packages/flatpak/generate-sources.sh` to automate the generation of `cargo-sources.json`
- Script downloads the `flatpak-cargo-generator.py` tool from upstream, generates the offline sources map based on `Cargo.lock`, and cleans up after itself

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

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-17 19:35:36 +03:00
Tarasov Aleksandr 949307fcf8 Update dependencies and change version to 1.7.2 (#63)
* deps: bump tokio to 1.52.1

* deps: bump clap to 4.6.1

* deps: cargo update

bitflags -> v2.11.1
core2 -
dary_heap -> v0.3.9
font-types -> v0.11.3
include-flate -> v0.3.3
include-flate-codegen -> v0.3.3
include-flate-compress -> v0.3.3
libc -> v0.2.185
libflate -> v2.3.0
libflate_lz77 -> v2.3.0
no_std_io2 + v0.9.3
portable-atomic-util -> v0.2.7
pxfm -> v0.1.29
rayon -> v1.12.0
uuid -> v1.23.1
webbrowser -> v1.2.1

* change version to 1.7.2
2026-04-17 14:42:59 +03:00
Tarasov Aleksandr 2a8fcca06b Fix virtual mic audio linking (#62)
* Fix virtual mic audio linking by managing it in AudioPlayer lifecycle

- Moved `link_player_to_virtual_mic` to `src/utils/pipewire.rs` and updated it to return a termination sender.
- Added `player_link_sender` to `AudioPlayer` to manage the PipeWire link between the daemon and the virtual mic.
- Integrated linking logic into `AudioPlayer::play` and `AudioPlayer::update` to ensure the link is established when audio starts playing.
- Ensured the link is terminated in `AudioPlayer::drop_stream` when the audio sink is closed.
- Removed redundant and potentially failing startup linking loop from the daemon.
- Fixed log spam by ensuring `link_player` is only attempted when necessary and errors are handled gracefully.
- Maintained compatibility with stable Rust by avoiding unstable features.

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

* small refactor

* refactor

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-17 14:24:58 +03:00
Tarasov Aleksandr 5c4b8f4b45 refactor(gui): replace verbose key matching with egui native methods (#60)
Replaced the large match blocks in `chord_from_event` and `parse_chord`
with `egui::Key::name()` and `egui::Key::from_name()`. This drastically
reduces boilerplate code while maintaining the existing behavior that
strictly allows only single-character alphanumeric keys and 'F' keys for
chords.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-17 13:58:08 +03:00
Tarasov Aleksandr 70c7e3789b 🔒 Fix potential memory exhaustion in socket reads (#59)
Addresses a security vulnerability where the daemon or client could be
forced to allocate up to 10MB of memory per malformed socket message,
potentially leading to Out-Of-Memory (OOM) crashes.

Changes:
- Introduced a central `MAX_MESSAGE_SIZE` constant of 128KB in `src/types/socket.rs`.
- Enforced the 128KB limit on incoming requests in `src/bin/daemon.rs`.
- Enforced the 128KB limit on incoming responses in `src/utils/daemon.rs`.
- Preserved detailed `eprintln!` logging when messages are rejected.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-04-17 13:56:29 +03:00
26 changed files with 7643 additions and 559 deletions
+70
View File
@@ -0,0 +1,70 @@
name: Flatter
on:
push:
branches: [ main, master ]
release:
types: [ published ]
workflow_dispatch:
jobs:
flatter:
name: Flatter
runs-on: ubuntu-latest
permissions:
contents: read
container:
image: ghcr.io/andyholmes/flatter/freedesktop:25.08
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup GPG
id: gpg
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Set Default Branch
id: set_branch
run: |
if [ "${{ github.event_name }}" == "release" ]; then
echo "branch=stable" >> $GITHUB_OUTPUT
else
echo "branch=nightly" >> $GITHUB_OUTPUT
fi
- name: Modify Manifest
run: |
echo "default-branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
- name: Install SDK Extensions
run: flatpak install -y flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
org.freedesktop.Sdk.Extension.llvm20//25.08
- name: Build Flatpak
uses: andyholmes/flatter@main
with:
files: packages/flatpak/ru.arabianq.pwsp.yaml
gpg-sign: ${{ steps.gpg.outputs.fingerprint }}
upload-bundles: false
upload-pages-artifact: true
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: flatter
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
-7
View File
@@ -174,10 +174,3 @@ jobs:
cache: true cache: true
branch: master branch: master
build-bundle: true build-bundle: true
- name: Upload Flatpak to release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ needs.prepare.outputs.tag }}
files: ru.arabianq.pwsp.flatpak
Generated
+243 -113
View File
@@ -58,7 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [ dependencies = [
"alsa-sys", "alsa-sys",
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
dependencies = [ dependencies = [
"android-properties", "android-properties",
"bitflags 2.11.0", "bitflags 2.11.1",
"cc", "cc",
"jni 0.22.4", "jni 0.22.4",
"libc", "libc",
@@ -302,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [ dependencies = [
"annotate-snippets", "annotate-snippets",
"bitflags 2.11.0", "bitflags 2.11.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.13.0", "itertools 0.13.0",
@@ -337,9 +337,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@@ -439,7 +439,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"log", "log",
"polling", "polling",
"rustix 0.38.44", "rustix 0.38.44",
@@ -453,7 +453,7 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"polling", "polling",
"rustix 1.1.4", "rustix 1.1.4",
"slab", "slab",
@@ -486,9 +486,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.60" version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -555,9 +555,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.0" version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -576,9 +576,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.6.0" version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -713,22 +713,13 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "core2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "coreaudio-rs" name = "coreaudio-rs"
version = "0.14.1" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586" checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"libc", "libc",
"objc2-audio-toolbox", "objc2-audio-toolbox",
"objc2-core-audio", "objc2-core-audio",
@@ -814,9 +805,9 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]] [[package]]
name = "dary_heap" name = "dary_heap"
version = "0.3.8" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe"
[[package]] [[package]]
name = "dasp_sample" name = "dasp_sample"
@@ -857,7 +848,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.6.2", "block2 0.6.2",
"libc", "libc",
"objc2 0.6.4", "objc2 0.6.4",
@@ -957,7 +948,7 @@ checksum = "f34aaf627da598dfadd64b0fee6101d22e9c451d1e5348157312720b7f459f0f"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"ahash", "ahash",
"bitflags 2.11.0", "bitflags 2.11.1",
"emath", "emath",
"epaint", "epaint",
"log", "log",
@@ -1031,6 +1022,20 @@ dependencies = [
"web-time", "web-time",
] ]
[[package]]
name = "egui_extras"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bfc6870c68d3f254e33aca8200095d422e09edacb0f365f79fe23a5ba10963"
dependencies = [
"ahash",
"egui",
"enum-map",
"log",
"mime_guess2",
"profiling",
]
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.34.1" version = "0.34.1"
@@ -1088,6 +1093,26 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
[[package]]
name = "enum-map"
version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
dependencies = [
"enum-map-derive",
]
[[package]]
name = "enum-map-derive"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "enumflags2" name = "enumflags2"
version = "0.7.12" version = "0.7.12"
@@ -1283,9 +1308,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "font-types" name = "font-types"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9237c6d82152100c691fb77ea18037b402bcc7257d2c876a4ffac81bc22a1c" checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
] ]
@@ -1456,7 +1481,7 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg_aliases", "cfg_aliases",
"cgl", "cgl",
"dispatch2", "dispatch2",
@@ -1713,9 +1738,9 @@ dependencies = [
[[package]] [[package]]
name = "include-flate" name = "include-flate"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a05fb00d9abc625268e0573a519506b264a7d6965de09bac13201bfb44e723d" checksum = "23e233413926ef735f7d87024466cfda5a4b87467730846bd82ea7d504121347"
dependencies = [ dependencies = [
"include-flate-codegen", "include-flate-codegen",
"include-flate-compress", "include-flate-compress",
@@ -1723,9 +1748,9 @@ dependencies = [
[[package]] [[package]]
name = "include-flate-codegen" name = "include-flate-codegen"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92c3c319a7527668538a8530c541e74e881e94c4f41e1425622d0a41c16468af" checksum = "5e7148f24ef8922cc0e5574ebb908729ccdd3a110c440a45165733fedadd9969"
dependencies = [ dependencies = [
"include-flate-compress", "include-flate-compress",
"proc-macro-error2", "proc-macro-error2",
@@ -1736,9 +1761,9 @@ dependencies = [
[[package]] [[package]]
name = "include-flate-compress" name = "include-flate-compress"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0bd9ea81b94169d61c5a397e9faef02153d3711fc62d3270bcde3ac85380d9" checksum = "74783a9ed407e844e99d5e7a57bd650acbfa124cf6e97ffd790ba59d8ab8e7ff"
dependencies = [ dependencies = [
"libflate", "libflate",
"zstd", "zstd",
@@ -1907,31 +1932,31 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.184" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libflate" name = "libflate"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3248b8d211bd23a104a42d81b4fa8bb8ac4a3b75e7a43d85d2c9ccb6179cd74" checksum = "cd96e993e5f3368b0cb8497dae6c860c22af8ff18388c61c6c0b86c58d86b5df"
dependencies = [ dependencies = [
"adler32", "adler32",
"core2",
"crc32fast", "crc32fast",
"dary_heap", "dary_heap",
"libflate_lz77", "libflate_lz77",
"no_std_io2",
] ]
[[package]] [[package]]
name = "libflate_lz77" name = "libflate_lz77"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c" checksum = "ff7a10e427698aef6eef269482776debfef63384d30f13aad39a1a95e0e098fd"
dependencies = [ dependencies = [
"core2",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"no_std_io2",
"rle-decode-fast", "rle-decode-fast",
] ]
@@ -1957,7 +1982,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.4", "redox_syscall 0.7.4",
@@ -1969,7 +1994,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4" checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cc", "cc",
"convert_case", "convert_case",
"cookie-factory", "cookie-factory",
@@ -2069,6 +2094,24 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
dependencies = [
"mime",
"phf",
"phf_shared",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -2114,7 +2157,7 @@ checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-set", "bit-set",
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"codespan-reporting", "codespan-reporting",
@@ -2137,7 +2180,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"jni-sys 0.3.1", "jni-sys 0.3.1",
"log", "log",
"ndk-sys", "ndk-sys",
@@ -2167,7 +2210,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -2179,12 +2222,21 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
] ]
[[package]]
name = "no_std_io2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "nohash-hasher" name = "nohash-hasher"
version = "0.2.0" version = "0.2.0"
@@ -2323,7 +2375,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"libc", "libc",
"objc2 0.5.2", "objc2 0.5.2",
@@ -2339,7 +2391,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.6.2", "block2 0.6.2",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
@@ -2353,7 +2405,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"libc", "libc",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-audio", "objc2-core-audio",
@@ -2378,7 +2430,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-location", "objc2-core-location",
@@ -2415,7 +2467,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2 0.6.4", "objc2 0.6.4",
] ]
@@ -2425,7 +2477,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
@@ -2437,7 +2489,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.6.2", "block2 0.6.2",
"dispatch2", "dispatch2",
"libc", "libc",
@@ -2450,7 +2502,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"dispatch2", "dispatch2",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
@@ -2493,7 +2545,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"dispatch", "dispatch",
"libc", "libc",
@@ -2506,7 +2558,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.6.2", "block2 0.6.2",
"libc", "libc",
"objc2 0.6.4", "objc2 0.6.4",
@@ -2519,7 +2571,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2542,7 +2594,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
@@ -2554,7 +2606,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
@@ -2577,7 +2629,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-cloud-kit", "objc2-cloud-kit",
@@ -2598,7 +2650,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
@@ -2621,7 +2673,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-location", "objc2-core-location",
@@ -2655,9 +2707,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "orbclient" name = "orbclient"
version = "0.3.51" version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd"
dependencies = [ dependencies = [
"libc", "libc",
"libredox", "libredox",
@@ -2721,6 +2773,50 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn",
"unicase",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
"unicase",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.11" version = "1.1.11"
@@ -2765,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44" checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.1",
"libc", "libc",
"libspa", "libspa",
"libspa-sys", "libspa-sys",
@@ -2804,7 +2900,7 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"crc32fast", "crc32fast",
"fdeflate", "fdeflate",
"flate2", "flate2",
@@ -2839,9 +2935,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
version = "0.2.6" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [ dependencies = [
"portable-atomic", "portable-atomic",
] ]
@@ -2913,7 +3009,7 @@ checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
[[package]] [[package]]
name = "pwsp" name = "pwsp"
version = "1.7.1" version = "1.7.4"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap", "clap",
@@ -2921,6 +3017,7 @@ dependencies = [
"eframe", "eframe",
"egui", "egui",
"egui_dnd", "egui_dnd",
"egui_extras",
"egui_material_icons", "egui_material_icons",
"evdev", "evdev",
"itertools 0.14.0", "itertools 0.14.0",
@@ -2935,9 +3032,9 @@ dependencies = [
[[package]] [[package]]
name = "pxfm" name = "pxfm"
version = "0.1.28" version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
[[package]] [[package]]
name = "quick-error" name = "quick-error"
@@ -2981,6 +3078,21 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.6.2" version = "0.6.2"
@@ -2989,9 +3101,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@@ -3032,7 +3144,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
] ]
[[package]] [[package]]
@@ -3041,7 +3153,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
] ]
[[package]] [[package]]
@@ -3160,7 +3272,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
@@ -3173,7 +3285,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.12.1", "linux-raw-sys 0.12.1",
@@ -3326,6 +3438,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82d449ab1bccfeec125893c6875008206f038d4eb8a09e1e10caf86f44d574e" checksum = "c82d449ab1bccfeec125893c6875008206f038d4eb8a09e1e10caf86f44d574e"
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]] [[package]]
name = "skrifa" name = "skrifa"
version = "0.40.0" version = "0.40.0"
@@ -3363,7 +3481,7 @@ version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"calloop 0.13.0", "calloop 0.13.0",
"calloop-wayland-source 0.3.0", "calloop-wayland-source 0.3.0",
"cursor-icon", "cursor-icon",
@@ -3388,7 +3506,7 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"calloop 0.14.4", "calloop 0.14.4",
"calloop-wayland-source 0.4.1", "calloop-wayland-source 0.4.1",
"cursor-icon", "cursor-icon",
@@ -3778,9 +3896,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.51.1" version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -3816,7 +3934,7 @@ dependencies = [
"toml_datetime", "toml_datetime",
"toml_parser", "toml_parser",
"toml_writer", "toml_writer",
"winnow 1.0.1", "winnow 1.0.2",
] ]
[[package]] [[package]]
@@ -3837,7 +3955,7 @@ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime",
"toml_parser", "toml_parser",
"winnow 1.0.1", "winnow 1.0.2",
] ]
[[package]] [[package]]
@@ -3846,7 +3964,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [ dependencies = [
"winnow 1.0.1", "winnow 1.0.2",
] ]
[[package]] [[package]]
@@ -3907,6 +4025,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
@@ -3952,9 +4076,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.23.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"serde_core", "serde_core",
@@ -4017,11 +4141,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.2+wasi-0.2.9" version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.57.1",
] ]
[[package]] [[package]]
@@ -4030,7 +4154,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen 0.51.0",
] ]
[[package]] [[package]]
@@ -4116,7 +4240,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap", "indexmap",
"semver", "semver",
@@ -4142,7 +4266,7 @@ version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"rustix 1.1.4", "rustix 1.1.4",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
@@ -4154,7 +4278,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cursor-icon", "cursor-icon",
"wayland-backend", "wayland-backend",
] ]
@@ -4176,7 +4300,7 @@ version = "0.32.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-scanner", "wayland-scanner",
@@ -4188,7 +4312,7 @@ version = "20250721.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
@@ -4201,7 +4325,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
@@ -4214,7 +4338,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
@@ -4227,7 +4351,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols", "wayland-protocols",
@@ -4279,9 +4403,9 @@ dependencies = [
[[package]] [[package]]
name = "webbrowser" name = "webbrowser"
version = "1.2.0" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72"
dependencies = [ dependencies = [
"core-foundation 0.10.1", "core-foundation 0.10.1",
"jni 0.22.4", "jni 0.22.4",
@@ -4306,7 +4430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bitflags 2.11.0", "bitflags 2.11.1",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
@@ -4336,7 +4460,7 @@ dependencies = [
"arrayvec", "arrayvec",
"bit-set", "bit-set",
"bit-vec", "bit-vec",
"bitflags 2.11.0", "bitflags 2.11.1",
"bytemuck", "bytemuck",
"cfg_aliases", "cfg_aliases",
"document-features", "document-features",
@@ -4373,7 +4497,7 @@ version = "29.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libloading", "libloading",
@@ -4404,7 +4528,7 @@ version = "29.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"bytemuck", "bytemuck",
"js-sys", "js-sys",
"log", "log",
@@ -4771,7 +4895,7 @@ dependencies = [
"ahash", "ahash",
"android-activity", "android-activity",
"atomic-waker", "atomic-waker",
"bitflags 2.11.0", "bitflags 2.11.1",
"block2 0.5.1", "block2 0.5.1",
"bytemuck", "bytemuck",
"calloop 0.13.0", "calloop 0.13.0",
@@ -4824,9 +4948,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -4840,6 +4964,12 @@ dependencies = [
"wit-bindgen-rust-macro", "wit-bindgen-rust-macro",
] ]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "wit-bindgen-core" name = "wit-bindgen-core"
version = "0.51.0" version = "0.51.0"
@@ -4889,7 +5019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags 2.11.1",
"indexmap", "indexmap",
"log", "log",
"serde", "serde",
@@ -4978,7 +5108,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.1",
"dlib", "dlib",
"log", "log",
"once_cell", "once_cell",
+4 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "pwsp" name = "pwsp"
version = "1.7.1" version = "1.7.4"
edition = "2024" edition = "2024"
authors = ["arabian"] authors = ["arabian"]
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients." description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
@@ -12,13 +12,13 @@ keywords = ["soundpad", "pipewire", "linux", "cli", "gui"]
[dependencies] [dependencies]
tokio = { version = "1.51.1", features = ["full"] } tokio = { version = "1.52.1", features = ["full"] }
async-trait = "0.1.89" async-trait = "0.1.89"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
clap = { version = "4.6.0", default-features = false, features = [ clap = { version = "4.6.1", default-features = false, features = [
"std", "std",
"suggestions", "suggestions",
"help", "help",
@@ -50,6 +50,7 @@ eframe = { version = "0.34.1", default-features = false, features = [
"x11", "x11",
"wayland", "wayland",
] } ] }
egui_extras = "0.34.1"
egui_material_icons = "0.6.0" egui_material_icons = "0.6.0"
egui_dnd = "0.15.0" egui_dnd = "0.15.0"
+22
View File
@@ -52,6 +52,28 @@ three main components:
You can download pre-built binaries and .deb packages from You can download pre-built binaries and .deb packages from
the [releases page](https://github.com/arabianq/pipewire-soundpad/releases). 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:
```bash
flatpak remote-add --user --if-not-exists arabianq-repo https://arabianq.github.io/pipewire-soundpad/index.flatpakrepo
```
Install the stable version:
```bash
flatpak install --user arabianq-repo ru.arabianq.pwsp//stable
```
Or install the nightly version (latest commit to `main`):
```bash
flatpak install --user arabianq-repo ru.arabianq.pwsp//nightly
```
## **Fedora Linux (and derivatives)** ## **Fedora Linux (and derivatives)**
If you're using Fedora, you can install PWSP from a dedicated repository using DNF. If you're using Fedora, you can install PWSP from a dedicated repository using DNF.
+3 -3
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp-bin pkgbase = pwsp-bin
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries) pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
pkgver = 1.7.1 pkgver = 1.7.4
pkgrel = 2 pkgrel = 2
url = https://github.com/arabianq/pipewire-soundpad url = https://github.com/arabianq/pipewire-soundpad
arch = x86_64 arch = x86_64
@@ -9,8 +9,8 @@ depends = pipewire
depends = alsa-lib depends = alsa-lib
provides = pwsp provides = pwsp
conflicts = pwsp conflicts = pwsp
source = pwsp-bin-1.7.1.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.7.1/pwsp-v1.7.1-linux-x64.zip 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.1.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.1.tar.gz source = pipewire-soundpad-1.7.4.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.4.tar.gz
sha256sums = SKIP sha256sums = SKIP
sha256sums = SKIP sha256sums = SKIP
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru> # Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgname=pwsp-bin pkgname=pwsp-bin
_pkgname=pipewire-soundpad _pkgname=pipewire-soundpad
pkgver=1.7.1 pkgver=1.7.4
pkgrel=2 pkgrel=2
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)" pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
arch=('x86_64') arch=('x86_64')
+2 -2
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp pkgbase = pwsp
pkgdesc = Lets you play audio files through your microphone pkgdesc = Lets you play audio files through your microphone
pkgver = 1.7.1 pkgver = 1.7.4
pkgrel = 1 pkgrel = 1
url = https://github.com/arabianq/pipewire-soundpad url = https://github.com/arabianq/pipewire-soundpad
arch = any arch = any
@@ -10,7 +10,7 @@ pkgbase = pwsp
makedepends = cargo makedepends = cargo
makedepends = pipewire makedepends = pipewire
makedepends = alsa-lib makedepends = alsa-lib
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.1.tar.gz source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.4.tar.gz
sha256sums = SKIP sha256sums = SKIP
pkgname = pwsp pkgname = pwsp
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru> # Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgsubn=pwsp pkgsubn=pwsp
pkgname=pwsp pkgname=pwsp
pkgver=1.7.1 pkgver=1.7.4
pkgrel=1 pkgrel=1
pkgdesc="Lets you play audio files through your microphone" pkgdesc="Lets you play audio files through your microphone"
arch=('any') arch=('any')
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
if [ ! -f "Cargo.lock" ]; then
echo "Error: Cargo.lock not found. Please run this script from the project root."
return 1
fi
echo "Downloading flatpak-cargo-generator.py..."
curl -sLO https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py
chmod +x flatpak-cargo-generator.py
echo "Generating cargo-sources.json..."
python3 flatpak-cargo-generator.py Cargo.lock -o packages/flatpak/cargo-sources.json
echo "Cleaning up..."
rm flatpak-cargo-generator.py
echo "Successfully generated packages/flatpak/cargo-sources.json"
+1 -1
View File
@@ -5,5 +5,5 @@ Exec=pwsp-wrapper.py %u
Icon=ru.arabianq.pwsp Icon=ru.arabianq.pwsp
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Audio;Utility; Categories=AudioVideo;Audio;
Keywords=soundpad;pipewire;audio; Keywords=soundpad;pipewire;audio;
@@ -15,11 +15,17 @@
<launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable> <launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image> <image>https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image>
https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<url type="homepage">https://pwsp.arabianq.ru</url> <url type="homepage">https://pwsp.arabianq.ru</url>
<developer_name>arabian</developer_name> <url type="bugtracker">https://github.com/arabianq/pipewire-soundpad/issues</url>
<url type="vcs-browser">https://github.com/arabianq/pipewire-soundpad</url>
<developer id="ru.arabianq">
<name>arabian</name>
</developer>
<releases>
<release version="1.7.4" date="2026-04-25" />
</releases>
<content_rating type="oars-1.1" /> <content_rating type="oars-1.1" />
</component> </component>
+3 -6
View File
@@ -14,10 +14,9 @@ finish-args:
- --filesystem=xdg-run/pipewire-0 - --filesystem=xdg-run/pipewire-0
- --filesystem=xdg-run/pwsp:create - --filesystem=xdg-run/pwsp:create
- --filesystem=xdg-run/app/ru.arabianq.pwsp:create - --filesystem=xdg-run/app/ru.arabianq.pwsp:create
- --filesystem=host - --filesystem=home
- --device=all - --device=all
- --device=dri - --device=dri
- --share=network
- --talk-name=org.freedesktop.portal.Desktop - --talk-name=org.freedesktop.portal.Desktop
- --talk-name=org.freedesktop.portal.Documents - --talk-name=org.freedesktop.portal.Documents
@@ -30,11 +29,8 @@ build-options:
modules: modules:
- name: pwsp - name: pwsp
buildsystem: simple buildsystem: simple
build-options:
build-args:
- --share=network
build-commands: build-commands:
- cargo build --release - export CARGO_HOME=$PWD/cargo && cargo build --release --offline
- install -Dm755 target/release/pwsp-daemon /app/bin/pwsp-daemon - install -Dm755 target/release/pwsp-daemon /app/bin/pwsp-daemon
- install -Dm755 target/release/pwsp-cli /app/bin/pwsp-cli - install -Dm755 target/release/pwsp-cli /app/bin/pwsp-cli
- install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui - install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui
@@ -45,3 +41,4 @@ modules:
sources: sources:
- type: dir - type: dir
path: ../../ path: ../../
- cargo-sources.json
+1 -1
View File
@@ -4,7 +4,7 @@
%global cargo_install_lib 0 %global cargo_install_lib 0
Name: pwsp Name: pwsp
Version: 1.7.1 Version: 1.7.4
Release: %autorelease Release: %autorelease
Summary: Lets you play audio files through your microphone Summary: Lets you play audio files through your microphone
+6 -25
View File
@@ -1,10 +1,10 @@
use pwsp::{ use pwsp::{
types::socket::{Request, Response}, types::socket::{MAX_MESSAGE_SIZE, Request, Response},
utils::{ utils::{
commands::parse_command, commands::parse_command,
daemon::{ daemon::{
create_runtime_dir, get_audio_player, get_daemon_config, get_runtime_dir, create_runtime_dir, get_audio_player, get_daemon_config, get_runtime_dir,
is_daemon_running, link_player_to_virtual_mic, is_daemon_running,
}, },
global_hotkeys::start_global_hotkey_listener, global_hotkeys::start_global_hotkey_listener,
pipewire::create_virtual_mic, pipewire::create_virtual_mic,
@@ -32,25 +32,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
eprintln!("Failed to initialize audio player: {}", err); eprintln!("Failed to initialize audio player: {}", err);
} // Initialize audio player } // Initialize audio player
tokio::spawn(async {
let max_retries = 60;
for i in 0..=max_retries {
match link_player_to_virtual_mic().await {
Ok(_) => {
println!("Successfully linked player to virtual mic.");
break;
}
Err(e) => {
if i == 0 || i == max_retries {
eprintln!("{e} (attempt {i}/{max_retries})");
}
}
}
sleep(Duration::from_millis(1000)).await;
}
});
tokio::spawn(async { tokio::spawn(async {
start_global_hotkey_listener().await; start_global_hotkey_listener().await;
}); });
@@ -61,11 +42,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
lock_file.lock()?; lock_file.lock()?;
let socket_path = runtime_dir.join("daemon.sock"); let socket_path = runtime_dir.join("daemon.sock");
if let Err(e) = fs::remove_file(&socket_path) { if let Err(e) = fs::remove_file(&socket_path)
if e.kind() != std::io::ErrorKind::NotFound { && e.kind() != std::io::ErrorKind::NotFound
{
return Err(e.into()); return Err(e.into());
} }
}
let listener = UnixListener::bind(&socket_path)?; let listener = UnixListener::bind(&socket_path)?;
fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600))?; fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600))?;
@@ -109,7 +90,7 @@ async fn commands_loop(listener: UnixListener) -> Result<(), Box<dyn Error>> {
let request_len = u32::from_le_bytes(len_bytes) as usize; let request_len = u32::from_le_bytes(len_bytes) as usize;
if request_len > 10 * 1024 * 1024 { if request_len > MAX_MESSAGE_SIZE {
eprintln!( eprintln!(
"Failed to read message from client: request too large ({} bytes)!", "Failed to read message from client: request too large ({} bytes)!",
request_len request_len
+115 -100
View File
@@ -1,9 +1,10 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use egui::{ use egui::{
Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Grid, Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Label,
Label, Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2, Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
}; };
use egui_dnd::dnd; use egui_dnd::dnd;
use egui_extras::{Column, TableBuilder};
use egui_material_icons::icons::*; use egui_material_icons::icons::*;
use pwsp::types::socket::Request; use pwsp::types::socket::Request;
use pwsp::types::{audio_player::TrackInfo, gui::AppState}; use pwsp::types::{audio_player::TrackInfo, gui::AppState};
@@ -133,26 +134,24 @@ impl SoundpadGui {
} }
pub fn draw_hotkeys(&mut self, ui: &mut Ui) { pub fn draw_hotkeys(&mut self, ui: &mut Ui) {
let area_size = ui.available_size();
ui.vertical(|ui| { ui.vertical(|ui| {
ui.set_min_width(area_size.x);
ui.set_min_height(area_size.y);
ui.spacing_mut().item_spacing.y = 5.0; ui.spacing_mut().item_spacing.y = 5.0;
// Header // --- Header ---
ui.horizontal_top(|ui| { ui.horizontal(|ui| {
let back_button = Button::new(ICON_ARROW_BACK).frame(false); let back_button = Button::new(ICON_ARROW_BACK).frame(false);
if ui.add(back_button).clicked() { if ui.add(back_button).clicked() {
self.app_state.show_hotkeys = false; self.app_state.show_hotkeys = false;
} }
ui.add_space(ui.available_width() / 2.0 - 40.0); ui.vertical_centered(|ui| {
ui.label(RichText::new("Hotkeys").color(Color32::WHITE).monospace()); ui.label(RichText::new("Hotkeys").color(Color32::WHITE).monospace());
}); });
});
ui.separator(); ui.separator();
// Search and Add Command // --- Search and Add Command ---
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.menu_button(format!("{} Add Command", ICON_ADD.codepoint), |ui| { ui.menu_button(format!("{} Add Command", ICON_ADD.codepoint), |ui| {
let mut selected_cmd = None; let mut selected_cmd = None;
@@ -185,10 +184,10 @@ impl SoundpadGui {
ui.add_space(10.0); ui.add_space(10.0);
ui.add_sized( ui.add(
[ui.available_width(), 22.0],
TextEdit::singleline(&mut self.app_state.hotkey_search_query) TextEdit::singleline(&mut self.app_state.hotkey_search_query)
.hint_text("Search hotkeys..."), .hint_text("Search hotkeys...")
.desired_width(f32::INFINITY),
); );
}); });
@@ -196,52 +195,11 @@ impl SoundpadGui {
ui.add_space(5.0); ui.add_space(5.0);
let conflicts = self.app_state.hotkey_config.find_conflicts(); let conflicts = self.app_state.hotkey_config.find_conflicts();
let conflict_slots: std::collections::HashSet<String> = conflicts let conflict_slots: std::collections::HashSet<&str> =
.iter() conflicts.into_iter().flat_map(|(a, b)| [a, b]).collect();
.flat_map(|(a, b)| vec![a.clone(), b.clone()])
.collect();
let search = self.app_state.hotkey_search_query.to_lowercase(); let search = self.app_state.hotkey_search_query.to_lowercase();
// Slots table
let mut action: Option<HotkeyAction> = None; let mut action: Option<HotkeyAction> = None;
let area_size = ui.available_size();
ScrollArea::vertical().show(ui, |ui| {
ui.set_min_width(area_size.x);
Grid::new("hotkeys_grid")
.striped(true)
.num_columns(4)
.max_col_width(area_size.x)
.min_col_width(area_size.x / 4.0)
.spacing([40.0, 10.0])
.show(ui, |ui| {
// Table header
ui.label(
RichText::new("Slot")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
ui.label(
RichText::new("Sound")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
ui.label(
RichText::new("Key Chord")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
ui.label(
RichText::new("Actions")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
ui.end_row();
let slots: Vec<_> = self let slots: Vec<_> = self
.app_state .app_state
@@ -263,26 +221,91 @@ impl SoundpadGui {
.cloned() .cloned()
.collect(); .collect();
let available_width = ui.available_width();
let col_width = (available_width / 4.0).max(80.0);
TableBuilder::new(ui)
.striped(true)
.column(Column::exact(col_width).clip(true)) // Slot
.column(Column::exact(col_width).clip(true)) // Sound / Action name
.column(Column::exact(col_width).clip(true)) // Key Chord
.column(Column::exact(col_width).clip(true)) // Actions
.header(30.0, |mut header| {
header.col(|ui| {
ui.label(
RichText::new("Slot")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
});
header.col(|ui| {
ui.label(
RichText::new("Sound")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
});
header.col(|ui| {
ui.label(
RichText::new("Key Chord")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
});
header.col(|ui| {
ui.label(
RichText::new("Actions")
.strong()
.monospace()
.color(Color32::LIGHT_GRAY),
);
});
})
.body(|mut body| {
if slots.is_empty() {
body.row(30.0, |mut row| {
row.col(|_| {});
row.col(|ui| {
ui.label(
RichText::new("No hotkey slots configured.")
.color(Color32::GRAY),
);
});
row.col(|_| {});
row.col(|_| {});
});
return;
}
for slot in &slots { for slot in &slots {
body.row(30.0, |mut row| {
// Column 1: Slot
row.col(|ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
// Conflict badge if conflict_slots.contains(slot.slot.as_str()) {
if conflict_slots.contains(&slot.slot) {
ui.label( ui.label(
RichText::new(ICON_WARNING.codepoint) RichText::new(ICON_WARNING.codepoint)
.color(Color32::from_rgb(255, 165, 0)), .color(Color32::from_rgb(255, 165, 0)),
) )
.on_hover_text("Key chord conflict"); .on_hover_text("Key chord conflict");
} }
ui.add(
// Slot name Label::new(RichText::new(&slot.slot).monospace())
let slot_text = RichText::new(&slot.slot).monospace(); .truncate(),
ui.label(slot_text); );
});
}); });
// Action description // Column 2: Sound / Action name
row.col(|ui| {
let action_name = match slot.action.name.as_str() { let action_name = match slot.action.name.as_str() {
"play" => { "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) Path::new(file_path_str)
.file_name() .file_name()
.unwrap_or_default() .unwrap_or_default()
@@ -299,20 +322,29 @@ impl SoundpadGui {
"toggle_loop" => "Toggle Loop".to_string(), "toggle_loop" => "Toggle Loop".to_string(),
other => other.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(),
);
});
// Key chord // Column 3: Key Chord
row.col(|ui| {
let chord_text = slot.key_chord.as_deref().unwrap_or("(none)"); let chord_text = slot.key_chord.as_deref().unwrap_or("(none)");
ui.label(RichText::new(chord_text).monospace().color( ui.add(
Label::new(RichText::new(chord_text).monospace().color(
if slot.key_chord.is_some() { if slot.key_chord.is_some() {
Color32::from_rgb(100, 200, 100) Color32::from_rgb(100, 200, 100)
} else { } else {
Color32::GRAY Color32::GRAY
}, },
)); ))
.truncate(),
);
});
// Column 4: Actions
row.col(|ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
// Delete button
if ui if ui
.add(Button::new(ICON_DELETE).frame(false)) .add(Button::new(ICON_DELETE).frame(false))
.on_hover_text("Remove slot") .on_hover_text("Remove slot")
@@ -320,8 +352,6 @@ impl SoundpadGui {
{ {
action = Some(HotkeyAction::Remove(slot.slot.clone())); action = Some(HotkeyAction::Remove(slot.slot.clone()));
} }
// Set key chord button
if ui if ui
.add(Button::new(ICON_KEYBOARD).frame(false)) .add(Button::new(ICON_KEYBOARD).frame(false))
.on_hover_text("Set key chord") .on_hover_text("Set key chord")
@@ -329,8 +359,6 @@ impl SoundpadGui {
{ {
action = Some(HotkeyAction::Capture(slot.slot.clone())); action = Some(HotkeyAction::Capture(slot.slot.clone()));
} }
// Clear key chord
if slot.key_chord.is_some() if slot.key_chord.is_some()
&& ui && ui
.add(Button::new(ICON_BACKSPACE).frame(false)) .add(Button::new(ICON_BACKSPACE).frame(false))
@@ -339,8 +367,6 @@ impl SoundpadGui {
{ {
action = Some(HotkeyAction::ClearChord(slot.slot.clone())); action = Some(HotkeyAction::ClearChord(slot.slot.clone()));
} }
// Play button
if ui if ui
.add(Button::new(ICON_PLAY_ARROW).frame(false)) .add(Button::new(ICON_PLAY_ARROW).frame(false))
.on_hover_text("Play") .on_hover_text("Play")
@@ -349,18 +375,10 @@ impl SoundpadGui {
action = Some(HotkeyAction::Play(slot.slot.clone())); action = Some(HotkeyAction::Play(slot.slot.clone()));
} }
}); });
ui.end_row();
}
if slots.is_empty() {
ui.label("No hotkey slots configured.");
ui.label("");
ui.label("");
ui.label("");
ui.end_row();
}
}); });
}); });
}
});
if let Some(action) = action { if let Some(action) = action {
match action { match action {
@@ -609,11 +627,11 @@ impl SoundpadGui {
.unwrap_or_else(|| path.to_string_lossy().to_string()); .unwrap_or_else(|| path.to_string_lossy().to_string());
let mut dir_button_text = RichText::new(name.clone()); let mut dir_button_text = RichText::new(name.clone());
if let Some(current_dir) = &self.app_state.current_dir { if let Some(current_dir) = &self.app_state.current_dir
if current_dir.eq(&path) { && current_dir.eq(&path)
{
dir_button_text = dir_button_text.color(Color32::WHITE); dir_button_text = dir_button_text.color(Color32::WHITE);
} }
}
let dir_button = let dir_button =
Button::new(dir_button_text.atom_max_width(area_size.x)).frame(false); Button::new(dir_button_text.atom_max_width(area_size.x)).frame(false);
@@ -645,11 +663,10 @@ impl SoundpadGui {
ICON_OPEN_IN_BROWSER.codepoint, "Open in File Manager" ICON_OPEN_IN_BROWSER.codepoint, "Open in File Manager"
)) ))
.clicked() .clicked()
&& let Err(e) = opener::open(&path)
{ {
if let Err(e) = opener::open(&path) {
eprintln!("Failed to open file manager: {}", e); eprintln!("Failed to open file manager: {}", e);
} }
}
ui.separator(); ui.separator();
@@ -728,13 +745,13 @@ impl SoundpadGui {
} }
let mut file_button_text = RichText::new(&file_name); let mut file_button_text = RichText::new(&file_name);
if let Some(current_file) = &self.app_state.selected_file { if let Some(current_file) = &self.app_state.selected_file
if current_file.eq(&entry_path) { && current_file.eq(&entry_path)
{
file_button_text = file_button_text.color(Color32::WHITE); file_button_text = file_button_text.color(Color32::WHITE);
} }
}
let file_button = Button::new(file_button_text).frame(false); let file_button = Button::new(file_button_text).frame(false).truncate();
let file_button_response = ui.add(file_button); let file_button_response = ui.add(file_button);
if file_button_response.clicked() { if file_button_response.clicked() {
ui.input(|i| { ui.input(|i| {
@@ -792,11 +809,10 @@ impl SoundpadGui {
ICON_OPEN_IN_BROWSER.codepoint, "Show in File Manager" ICON_OPEN_IN_BROWSER.codepoint, "Show in File Manager"
)) ))
.clicked() .clicked()
&& let Err(e) = opener::reveal(&entry_path)
{ {
if let Err(e) = opener::reveal(&entry_path) {
eprintln!("Failed to open file manager: {}", e); eprintln!("Failed to open file manager: {}", e);
} }
}
ui.separator(); ui.separator();
@@ -822,9 +838,10 @@ impl SoundpadGui {
fn get_hotkey_badge(&self, path: &PathBuf) -> Option<String> { fn get_hotkey_badge(&self, path: &PathBuf) -> Option<String> {
for slot in &self.app_state.hotkey_config.slots { for slot in &self.app_state.hotkey_config.slots {
if slot.action.name == "play" { if slot.action.name == "play"
if let Some(file_path_str) = slot.action.args.get("file_path") { && let Some(file_path_str) = slot.action.args.get("file_path")
if Path::new(file_path_str) == path.as_path() { && Path::new(file_path_str) == path.as_path()
{
if let Some(chord) = &slot.key_chord { if let Some(chord) = &slot.key_chord {
return Some(format!("[{}]", chord)); return Some(format!("[{}]", chord));
} else { } else {
@@ -832,8 +849,6 @@ impl SoundpadGui {
} }
} }
} }
}
}
None None
} }
+25 -106
View File
@@ -7,57 +7,15 @@ use std::path::PathBuf;
/// Convert an egui Key + Modifiers to a normalized chord string like "Ctrl+Shift+A". /// Convert an egui Key + Modifiers to a normalized chord string like "Ctrl+Shift+A".
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> { fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
let key_name = match key { let key_name = key.name();
Key::A => "A", let is_valid = (key_name.len() == 1
Key::B => "B", && key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
Key::C => "C", || (key_name.starts_with('F')
Key::D => "D", && key_name.len() > 1
Key::E => "E", && key_name[1..].chars().all(|c| c.is_ascii_digit()));
Key::F => "F", if !is_valid {
Key::G => "G", return None;
Key::H => "H", }
Key::I => "I",
Key::J => "J",
Key::K => "K",
Key::L => "L",
Key::M => "M",
Key::N => "N",
Key::O => "O",
Key::P => "P",
Key::Q => "Q",
Key::R => "R",
Key::S => "S",
Key::T => "T",
Key::U => "U",
Key::V => "V",
Key::W => "W",
Key::X => "X",
Key::Y => "Y",
Key::Z => "Z",
Key::Num0 => "0",
Key::Num1 => "1",
Key::Num2 => "2",
Key::Num3 => "3",
Key::Num4 => "4",
Key::Num5 => "5",
Key::Num6 => "6",
Key::Num7 => "7",
Key::Num8 => "8",
Key::Num9 => "9",
Key::F1 => "F1",
Key::F2 => "F2",
Key::F3 => "F3",
Key::F4 => "F4",
Key::F5 => "F5",
Key::F6 => "F6",
Key::F7 => "F7",
Key::F8 => "F8",
Key::F9 => "F9",
Key::F10 => "F10",
Key::F11 => "F11",
Key::F12 => "F12",
_ => return None,
};
// Require at least one modifier for hotkey chords (ignoring command/Super due to Wayland/Niri bug) // Require at least one modifier for hotkey chords (ignoring command/Super due to Wayland/Niri bug)
if !modifiers.ctrl && !modifiers.alt && !modifiers.shift { if !modifiers.ctrl && !modifiers.alt && !modifiers.shift {
@@ -100,57 +58,18 @@ pub fn parse_chord(chord: &str) -> Option<(Modifiers, Key)> {
} }
} }
let key = match parts[parts.len() - 1] { let key_name = parts[parts.len() - 1];
"A" => Key::A, let is_valid = (key_name.len() == 1
"B" => Key::B, && key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
"C" => Key::C, || (key_name.starts_with('F')
"D" => Key::D, && key_name.len() > 1
"E" => Key::E, && key_name[1..].chars().all(|c| c.is_ascii_digit()));
"F" => Key::F,
"G" => Key::G, if !is_valid {
"H" => Key::H, return None;
"I" => Key::I, }
"J" => Key::J,
"K" => Key::K, let key = Key::from_name(key_name)?;
"L" => Key::L,
"M" => Key::M,
"N" => Key::N,
"O" => Key::O,
"P" => Key::P,
"Q" => Key::Q,
"R" => Key::R,
"S" => Key::S,
"T" => Key::T,
"U" => Key::U,
"V" => Key::V,
"W" => Key::W,
"X" => Key::X,
"Y" => Key::Y,
"Z" => Key::Z,
"0" => Key::Num0,
"1" => Key::Num1,
"2" => Key::Num2,
"3" => Key::Num3,
"4" => Key::Num4,
"5" => Key::Num5,
"6" => Key::Num6,
"7" => Key::Num7,
"8" => Key::Num8,
"9" => Key::Num9,
"F1" => Key::F1,
"F2" => Key::F2,
"F3" => Key::F3,
"F4" => Key::F4,
"F5" => Key::F5,
"F6" => Key::F6,
"F7" => Key::F7,
"F8" => Key::F8,
"F9" => Key::F9,
"F10" => Key::F10,
"F11" => Key::F11,
"F12" => Key::F12,
_ => return None,
};
Some((modifiers, key)) Some((modifiers, key))
} }
@@ -273,8 +192,9 @@ impl SoundpadGui {
} }
// Play selected file on Enter // Play selected file on Enter
if self.key_pressed(ctx, Key::Enter) { if self.key_pressed(ctx, Key::Enter)
if let Some(path) = self.app_state.selected_file.clone() { && let Some(path) = self.app_state.selected_file.clone()
{
if modifiers.ctrl { if modifiers.ctrl {
self.play_file(&path, true); self.play_file(&path, true);
} else if modifiers.shift } else if modifiers.shift
@@ -286,14 +206,13 @@ impl SoundpadGui {
self.play_file(&path, false); self.play_file(&path, false);
} }
} }
}
// Iterate through dirs and files with Ctrl + Up/Down // Iterate through dirs and files with Ctrl + Up/Down
let arrow_up_pressed = self.key_pressed(ctx, Key::ArrowUp); let arrow_up_pressed = self.key_pressed(ctx, Key::ArrowUp);
let arrow_down_pressed = self.key_pressed(ctx, Key::ArrowDown); let arrow_down_pressed = self.key_pressed(ctx, Key::ArrowDown);
if modifiers.ctrl && (arrow_up_pressed || arrow_down_pressed) { if modifiers.ctrl && (arrow_up_pressed || arrow_down_pressed) {
if modifiers.shift && !self.app_state.dirs.is_empty() { if modifiers.shift && !self.app_state.dirs.is_empty() {
let mut dirs: Vec<PathBuf> = self.app_state.dirs.iter().cloned().collect(); let mut dirs: Vec<PathBuf> = self.app_state.dirs.to_vec();
dirs.sort(); dirs.sort();
let current_dir_index = self let current_dir_index = self
+47 -14
View File
@@ -2,7 +2,7 @@ use crate::{
types::pipewire::{DeviceType, Terminate}, types::pipewire::{DeviceType, Terminate},
utils::{ utils::{
daemon::get_daemon_config, daemon::get_daemon_config,
pipewire::{create_link, get_device}, pipewire::{create_link, get_device, link_player_to_virtual_mic},
}, },
}; };
use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source}; use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source};
@@ -58,6 +58,7 @@ pub struct AudioPlayer {
pub next_id: u32, pub next_id: u32,
input_link_sender: Option<pipewire::channel::Sender<Terminate>>, input_link_sender: Option<pipewire::channel::Sender<Terminate>>,
player_link_sender: Option<pipewire::channel::Sender<Terminate>>,
pub input_device_name: Option<String>, pub input_device_name: Option<String>,
pub volume: f32, // Master volume pub volume: f32, // Master volume
@@ -74,6 +75,7 @@ impl AudioPlayer {
next_id: 1, next_id: 1,
input_link_sender: None, input_link_sender: None,
player_link_sender: None,
input_device_name: daemon_config.default_input_name.clone(), input_device_name: daemon_config.default_input_name.clone(),
volume: default_volume, volume: default_volume,
@@ -98,19 +100,44 @@ impl AudioPlayer {
fn drop_stream(&mut self) { fn drop_stream(&mut self) {
if self.stream_handle.is_some() { if self.stream_handle.is_some() {
self.stream_handle = None; self.stream_handle = None;
self.abort_player_link_thread();
} }
} }
fn abort_link_thread(&mut self) { fn abort_link_thread(&mut self) {
if let Some(sender) = &self.input_link_sender { if let Some(sender) = &self.input_link_sender {
match sender.send(Terminate {}) { if sender.send(Terminate {}).is_ok() {
Ok(_) => { println!("Sent terminate signal to input link thread");
println!("Sent terminate signal to link thread");
self.input_link_sender = None; self.input_link_sender = None;
} else {
eprintln!("Failed to send terminate signal to input link thread");
} }
Err(_) => eprintln!("Failed to send terminate signal to link thread"),
} }
} }
fn abort_player_link_thread(&mut self) {
if let Some(sender) = &self.player_link_sender {
if sender.send(Terminate {}).is_ok() {
println!("Sent terminate signal to player link thread");
self.player_link_sender = None;
} else {
eprintln!("Failed to send terminate signal to player link thread");
}
}
}
async fn link_player(&mut self) -> Result<(), Box<dyn Error>> {
if self.player_link_sender.is_some() {
return Ok(());
}
match link_player_to_virtual_mic().await {
Ok(sender) => {
self.player_link_sender = Some(sender);
Ok(())
}
Err(_) => Ok(()),
}
} }
async fn link_devices(&mut self) -> Result<(), Box<dyn Error>> { async fn link_devices(&mut self) -> Result<(), Box<dyn Error>> {
@@ -227,12 +254,12 @@ impl AudioPlayer {
pub fn get_volume(&mut self, id: Option<u32>) -> Option<f32> { pub fn get_volume(&mut self, id: Option<u32>) -> Option<f32> {
if let Some(id) = id { if let Some(id) = id {
if let Some(sound) = self.tracks.get_mut(&id) { if let Some(sound) = self.tracks.get_mut(&id) {
return Some(sound.sink.volume()); Some(sound.sink.volume())
} else { } else {
return None; None
} }
} else { } else {
return Some(self.volume); Some(self.volume)
} }
} }
@@ -316,6 +343,7 @@ impl AudioPlayer {
} }
self.ensure_stream()?; self.ensure_stream()?;
self.link_player().await.ok();
let id = self.next_id; let id = self.next_id;
self.next_id += 1; self.next_id += 1;
@@ -394,6 +422,10 @@ impl AudioPlayer {
self.link_devices().await.ok(); self.link_devices().await.ok();
} }
} }
if self.stream_handle.is_some() && self.player_link_sender.is_none() {
self.link_player().await.ok();
}
} }
// Handle looped sounds // Handle looped sounds
@@ -411,11 +443,11 @@ impl AudioPlayer {
if let Some(sound) = self.tracks.get(&id) { if let Some(sound) = self.tracks.get(&id) {
let path = sound.path.clone(); let path = sound.path.clone();
let handle = tokio::task::spawn_blocking(move || { let handle = tokio::task::spawn_blocking(move || {
if let Ok(file) = fs::File::open(&path) { if let Ok(file) = fs::File::open(&path)
if let Ok(source) = Decoder::try_from(file) { && let Ok(source) = Decoder::try_from(file)
{
return Some((id, source)); return Some((id, source));
} }
}
None None
}); });
restart_futures.push(handle); restart_futures.push(handle);
@@ -423,13 +455,14 @@ impl AudioPlayer {
} }
for handle in restart_futures { for handle in restart_futures {
if let Ok(Some((id, source))) = handle.await { if let Ok(res) = handle.await
if let Some(sound) = self.tracks.get_mut(&id) { && let Some((id, source)) = res
&& let Some(sound) = self.tracks.get_mut(&id)
{
sound.sink.append(source); sound.sink.append(source);
sound.sink.play(); sound.sink.play();
} }
} }
}
self.tracks self.tracks
.retain(|_, sound| !sound.sink.empty() || sound.looped); .retain(|_, sound| !sound.sink.empty() || sound.looped);
+1 -1
View File
@@ -673,7 +673,7 @@ impl Executable for PlayHotkeyCommand {
if let Some(cmd) = parse_command(&action) { if let Some(cmd) = parse_command(&action) {
cmd.execute().await cmd.execute().await
} else { } else {
Response::new(false, "Unknown command in hotkey slot".to_string()) Response::new(false, "Unknown command in hotkey slot")
} }
} }
} }
+8 -8
View File
@@ -13,11 +13,11 @@ impl DaemonConfig {
pub fn save_to_file(&self) -> Result<(), Box<dyn Error>> { pub fn save_to_file(&self) -> Result<(), Box<dyn Error>> {
let config_path = get_config_path()?.join("daemon.json"); let config_path = get_config_path()?.join("daemon.json");
if let Some(config_dir) = config_path.parent() { if let Some(config_dir) = config_path.parent()
if !config_path.exists() { && !config_path.exists()
{
fs::create_dir_all(config_dir)?; fs::create_dir_all(config_dir)?;
} }
}
let config_json = serde_json::to_string_pretty(self)?; let config_json = serde_json::to_string_pretty(self)?;
fs::write(config_path, config_json.as_bytes())?; fs::write(config_path, config_json.as_bytes())?;
@@ -68,11 +68,11 @@ impl GuiConfig {
pub fn save_to_file(&mut self) -> Result<(), Box<dyn Error>> { pub fn save_to_file(&mut self) -> Result<(), Box<dyn Error>> {
let config_path = get_config_path()?.join("gui.json"); let config_path = get_config_path()?.join("gui.json");
if let Some(config_dir) = config_path.parent() { if let Some(config_dir) = config_path.parent()
if !config_path.exists() { && !config_path.exists()
{
fs::create_dir_all(config_dir)?; fs::create_dir_all(config_dir)?;
} }
}
// Do not save scale factor if user does not want to // Do not save scale factor if user does not want to
if !self.save_scale_factor { if !self.save_scale_factor {
@@ -172,7 +172,7 @@ impl HotkeyConfig {
} }
/// Returns pairs of slot names that share the same key chord. /// Returns pairs of slot names that share the same key chord.
pub fn find_conflicts(&self) -> Vec<(String, String)> { pub fn find_conflicts(&self) -> Vec<(&str, &str)> {
let mut conflicts = vec![]; let mut conflicts = vec![];
let mut chord_map: HashMap<&str, Vec<&str>> = HashMap::new(); let mut chord_map: HashMap<&str, Vec<&str>> = HashMap::new();
@@ -186,7 +186,7 @@ impl HotkeyConfig {
if slots.len() > 1 { if slots.len() > 1 {
for i in 0..slots.len() { for i in 0..slots.len() {
for j in (i + 1)..slots.len() { for j in (i + 1)..slots.len() {
conflicts.push((slots[i].to_string(), slots[j].to_string())); conflicts.push((slots[i], slots[j]));
} }
} }
} }
+2
View File
@@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
pub const MAX_MESSAGE_SIZE: usize = 128 * 1024;
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Request { pub struct Request {
pub name: String, pub name: String,
+85
View File
@@ -122,3 +122,88 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
_ => None, _ => None,
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::types::socket::Request;
use std::collections::HashMap;
#[test]
fn test_parse_set_volume_valid() {
let mut args = HashMap::new();
args.insert("volume".to_string(), "0.5".to_string());
args.insert("id".to_string(), "1".to_string());
let request = Request {
name: "set_volume".to_string(),
args,
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
#[test]
fn test_parse_set_volume_missing_volume() {
let mut args = HashMap::new();
args.insert("id".to_string(), "1".to_string());
let request = Request {
name: "set_volume".to_string(),
args,
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
#[test]
fn test_parse_set_volume_invalid_volume() {
let mut args = HashMap::new();
args.insert("volume".to_string(), "not-a-float".to_string());
let request = Request {
name: "set_volume".to_string(),
args,
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
#[test]
fn test_parse_set_volume_missing_id() {
let mut args = HashMap::new();
args.insert("volume".to_string(), "0.5".to_string());
let request = Request {
name: "set_volume".to_string(),
args,
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
#[test]
fn test_parse_set_volume_invalid_id() {
let mut args = HashMap::new();
args.insert("id".to_string(), "not-an-int".to_string());
args.insert("volume".to_string(), "0.5".to_string());
let request = Request {
name: "set_volume".to_string(),
args,
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
#[test]
fn test_parse_set_volume_empty_args() {
let request = Request {
name: "set_volume".to_string(),
args: HashMap::new(),
};
let cmd = parse_command(&request);
assert!(cmd.is_some());
}
}
+11 -43
View File
@@ -1,11 +1,9 @@
use crate::{ use crate::types::{
types::{
audio_player::AudioPlayer, audio_player::AudioPlayer,
config::DaemonConfig, config::DaemonConfig,
socket::{Request, Response}, socket::{MAX_MESSAGE_SIZE, Request, Response},
},
utils::pipewire::{create_link, get_device},
}; };
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::{error::Error, fs}; use std::{error::Error, fs};
@@ -38,44 +36,6 @@ pub fn get_daemon_config() -> DaemonConfig {
}) })
} }
pub async fn link_player_to_virtual_mic() -> Result<(), Box<dyn Error>> {
let pwsp_daemon_output;
if let Ok(device) = get_device("pwsp-daemon").await {
pwsp_daemon_output = device;
} else {
return Err(
"Could not find alsa_playback.pwsp-daemon device, skipping device linking".into(),
);
}
let pwsp_daemon_input;
if let Ok(device) = get_device("pwsp-virtual-mic").await {
pwsp_daemon_input = device;
} else {
return Err("Could not find pwsp-virtual-mic device, skipping device linking".into());
}
let output_fl = pwsp_daemon_output
.clone()
.output_fl
.expect("Failed to get pwsp-daemon output_fl");
let output_fr = pwsp_daemon_output
.clone()
.output_fr
.expect("Failed to get pwsp-daemon output_fl");
let input_fl = pwsp_daemon_input
.clone()
.input_fl
.expect("Failed to get pwsp-daemon input_fl");
let input_fr = pwsp_daemon_input
.clone()
.input_fr
.expect("Failed to get pwsp-daemon input_fr");
create_link(output_fl, output_fr, input_fl, input_fr)?;
Ok(())
}
pub fn get_runtime_dir() -> PathBuf { pub fn get_runtime_dir() -> PathBuf {
dirs::runtime_dir().unwrap_or(PathBuf::from("/run/pwsp")) dirs::runtime_dir().unwrap_or(PathBuf::from("/run/pwsp"))
} }
@@ -135,6 +95,14 @@ pub async fn make_request(request: Request) -> Result<Response, Box<dyn Error +
} }
let response_len = u32::from_le_bytes(len_bytes) as usize; let response_len = u32::from_le_bytes(len_bytes) as usize;
if response_len > MAX_MESSAGE_SIZE {
eprintln!(
"Failed to read response from daemon: response too large ({} bytes)!",
response_len
);
return Err("Response too large".into());
}
let mut buffer = vec![0u8; response_len]; let mut buffer = vec![0u8; response_len];
if stream.read_exact(&mut buffer).await.is_err() { if stream.read_exact(&mut buffer).await.is_err() {
return Err("Failed to read response".into()); return Err("Failed to read response".into());
+3 -3
View File
@@ -112,14 +112,14 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
let hotkey_res = make_request(Request::get_hotkeys()) let hotkey_res = make_request(Request::get_hotkeys())
.await .await
.unwrap_or_default(); .unwrap_or_default();
if hotkey_res.status { if hotkey_res.status
if let Ok(config) = serde_json::from_str::<HotkeyConfig>(&hotkey_res.message) { && let Ok(config) = serde_json::from_str::<HotkeyConfig>(&hotkey_res.message)
{
let mut guard = audio_player_state_shared let mut guard = audio_player_state_shared
.lock() .lock()
.unwrap_or_else(|e| e.into_inner()); .unwrap_or_else(|e| e.into_inner());
guard.hotkey_config = Some(config); guard.hotkey_config = Some(config);
} }
}
last_hotkey_poll = Instant::now(); last_hotkey_poll = Instant::now();
} }
+52 -16
View File
@@ -9,6 +9,13 @@ use tokio::{
time::{Duration, timeout}, time::{Duration, timeout},
}; };
pub fn setup_pipewire_context() -> (MainLoopRc, ContextRc) {
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)
}
fn parse_global_object( fn parse_global_object(
global_object: &GlobalObject<&DictRef>, global_object: &GlobalObject<&DictRef>,
) -> (Option<AudioDevice>, Option<Port>) { ) -> (Option<AudioDevice>, Option<Port>) {
@@ -56,12 +63,13 @@ fn parse_global_object(
(None, None) (None, None)
}; };
// Check if the object is a port // Check if the object is a port
} else if props.get("port.direction").is_some() { } else if props.get("port.direction").is_some()
if let (Some(node_id), Some(port_id), Some(port_name)) = ( && let (Some(node_id), Some(port_id), Some(port_name)) = (
props.get("node.id").and_then(|id| id.parse::<u32>().ok()), props.get("node.id").and_then(|id| id.parse::<u32>().ok()),
props.get("port.id").and_then(|id| id.parse::<u32>().ok()), props.get("port.id").and_then(|id| id.parse::<u32>().ok()),
props.get("port.name"), props.get("port.name"),
) { )
{
let port = Port { let port = Port {
node_id, node_id,
port_id, port_id,
@@ -71,7 +79,6 @@ fn parse_global_object(
return (None, Some(port)); return (None, Some(port));
} }
} }
}
(None, None) (None, None)
} }
@@ -79,9 +86,7 @@ async fn pw_get_global_objects_thread(
main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>, main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>,
pw_receiver: pipewire::channel::Receiver<Terminate>, pw_receiver: pipewire::channel::Receiver<Terminate>,
) { ) {
pipewire::init(); let (main_loop, context) = setup_pipewire_context();
let main_loop = MainLoopRc::new(None).expect("Failed to initialize pipewire main loop");
// Stop main loop on Terminate message // Stop main loop on Terminate message
let _receiver = pw_receiver.attach(main_loop.loop_(), { let _receiver = pw_receiver.attach(main_loop.loop_(), {
@@ -89,7 +94,6 @@ async fn pw_get_global_objects_thread(
move |_| _main_loop.quit() move |_| _main_loop.quit()
}); });
let context = ContextRc::new(&main_loop, None).expect("Failed to create pipewire context");
let core = context let core = context
.connect(None) .connect(None)
.expect("Failed to connect to pipewire context"); .expect("Failed to connect to pipewire context");
@@ -224,10 +228,7 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>(); let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
let _pw_thread = thread::spawn(move || { let _pw_thread = thread::spawn(move || {
pipewire::init(); let (main_loop, context) = setup_pipewire_context();
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");
let core = context let core = context
.connect(None) .connect(None)
.expect("Failed to connect to pipewire context"); .expect("Failed to connect to pipewire context");
@@ -258,6 +259,44 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
Ok(pw_sender) Ok(pw_sender)
} }
pub async fn link_player_to_virtual_mic()
-> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
let pwsp_daemon_output = match get_device("pwsp-daemon").await {
Ok(device) => device,
Err(_) => {
return Err(
"Could not find alsa_playback.pwsp-daemon device, skipping device linking".into(),
);
}
};
let pwsp_daemon_input = match get_device("pwsp-virtual-mic").await {
Ok(device) => device,
Err(_) => {
return Err("Could not find pwsp-virtual-mic device, skipping device linking".into());
}
};
let output_fl = match pwsp_daemon_output.output_fl {
Some(port) => port,
None => return Err("Failed to get pwsp-daemon output_fl".into()),
};
let output_fr = match pwsp_daemon_output.output_fr {
Some(port) => port,
None => return Err("Failed to get pwsp-daemon output_fr".into()),
};
let input_fl = match pwsp_daemon_input.input_fl {
Some(port) => port,
None => return Err("Failed to get pwsp-virtual-mic input_fl".into()),
};
let input_fr = match pwsp_daemon_input.input_fr {
Some(port) => port,
None => return Err("Failed to get pwsp-virtual-mic input_fr".into()),
};
create_link(output_fl, output_fr, input_fl, input_fr)
}
pub fn create_link( pub fn create_link(
output_fl: Port, output_fl: Port,
output_fr: Port, output_fr: Port,
@@ -267,10 +306,7 @@ pub fn create_link(
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>(); let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
let _pw_thread = thread::spawn(move || { let _pw_thread = thread::spawn(move || {
pipewire::init(); let (main_loop, context) = setup_pipewire_context();
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");
let core = context let core = context
.connect(None) .connect(None)
.expect("Failed to connect to pipewire context"); .expect("Failed to connect to pipewire context");