mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-28 14:31:23 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f50809a99 | |||
| 7dda4bc2b1 | |||
| 1569955e12 | |||
| 9adc6cfbda | |||
| 76b1d4f345 | |||
| 10f9937dc3 | |||
| 498c09eb50 | |||
| 78e0a133b6 | |||
| 7f8b7194b6 | |||
| 302f153b91 | |||
| f87dcb1564 | |||
| d4d16f6ce7 | |||
| 949307fcf8 | |||
| 2a8fcca06b | |||
| 5c4b8f4b45 | |||
| 70c7e3789b |
@@ -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
|
||||
@@ -174,10 +174,3 @@ jobs:
|
||||
cache: true
|
||||
branch: master
|
||||
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
@@ -58,7 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
|
||||
dependencies = [
|
||||
"android-properties",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cc",
|
||||
"jni 0.22.4",
|
||||
"libc",
|
||||
@@ -302,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
@@ -337,9 +337,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -439,7 +439,7 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"log",
|
||||
"polling",
|
||||
"rustix 0.38.44",
|
||||
@@ -453,7 +453,7 @@ version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"polling",
|
||||
"rustix 1.1.4",
|
||||
"slab",
|
||||
@@ -486,9 +486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.60"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -555,9 +555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -576,9 +576,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.0"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -713,22 +713,13 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16dd574a72a021b90c7656c474ea31d11a2f0366a8eff574186e761e0b9e3586"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-core-audio",
|
||||
@@ -814,9 +805,9 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "dary_heap"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04"
|
||||
checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe"
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
@@ -857,7 +848,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.6.2",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
@@ -957,7 +948,7 @@ checksum = "f34aaf627da598dfadd64b0fee6101d22e9c451d1e5348157312720b7f459f0f"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"emath",
|
||||
"epaint",
|
||||
"log",
|
||||
@@ -1031,6 +1022,20 @@ dependencies = [
|
||||
"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]]
|
||||
name = "egui_glow"
|
||||
version = "0.34.1"
|
||||
@@ -1088,6 +1093,26 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
@@ -1283,9 +1308,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d9237c6d82152100c691fb77ea18037b402bcc7257d2c876a4ffac81bc22a1c"
|
||||
checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
@@ -1456,7 +1481,7 @@ version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg_aliases",
|
||||
"cgl",
|
||||
"dispatch2",
|
||||
@@ -1713,9 +1738,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "include-flate"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a05fb00d9abc625268e0573a519506b264a7d6965de09bac13201bfb44e723d"
|
||||
checksum = "23e233413926ef735f7d87024466cfda5a4b87467730846bd82ea7d504121347"
|
||||
dependencies = [
|
||||
"include-flate-codegen",
|
||||
"include-flate-compress",
|
||||
@@ -1723,9 +1748,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "include-flate-codegen"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92c3c319a7527668538a8530c541e74e881e94c4f41e1425622d0a41c16468af"
|
||||
checksum = "5e7148f24ef8922cc0e5574ebb908729ccdd3a110c440a45165733fedadd9969"
|
||||
dependencies = [
|
||||
"include-flate-compress",
|
||||
"proc-macro-error2",
|
||||
@@ -1736,9 +1761,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "include-flate-compress"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0bd9ea81b94169d61c5a397e9faef02153d3711fc62d3270bcde3ac85380d9"
|
||||
checksum = "74783a9ed407e844e99d5e7a57bd650acbfa124cf6e97ffd790ba59d8ab8e7ff"
|
||||
dependencies = [
|
||||
"libflate",
|
||||
"zstd",
|
||||
@@ -1907,31 +1932,31 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.184"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "2.2.1"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3248b8d211bd23a104a42d81b4fa8bb8ac4a3b75e7a43d85d2c9ccb6179cd74"
|
||||
checksum = "cd96e993e5f3368b0cb8497dae6c860c22af8ff18388c61c6c0b86c58d86b5df"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"core2",
|
||||
"crc32fast",
|
||||
"dary_heap",
|
||||
"libflate_lz77",
|
||||
"no_std_io2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libflate_lz77"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c"
|
||||
checksum = "ff7a10e427698aef6eef269482776debfef63384d30f13aad39a1a95e0e098fd"
|
||||
dependencies = [
|
||||
"core2",
|
||||
"hashbrown 0.16.1",
|
||||
"no_std_io2",
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
@@ -1957,7 +1982,7 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"plain",
|
||||
"redox_syscall 0.7.4",
|
||||
@@ -1969,7 +1994,7 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cc",
|
||||
"convert_case",
|
||||
"cookie-factory",
|
||||
@@ -2069,6 +2094,24 @@ dependencies = [
|
||||
"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]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -2114,7 +2157,7 @@ checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
@@ -2137,7 +2180,7 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
@@ -2167,7 +2210,7 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2179,12 +2222,21 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"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]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
@@ -2323,7 +2375,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
@@ -2339,7 +2391,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.6.2",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-foundation",
|
||||
@@ -2353,7 +2405,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-audio",
|
||||
@@ -2378,7 +2430,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
@@ -2415,7 +2467,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
@@ -2425,7 +2477,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -2437,7 +2489,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
@@ -2450,7 +2502,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"dispatch2",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-foundation",
|
||||
@@ -2493,7 +2545,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"dispatch",
|
||||
"libc",
|
||||
@@ -2506,7 +2558,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.6.2",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
@@ -2519,7 +2571,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
@@ -2542,7 +2594,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -2554,7 +2606,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -2577,7 +2629,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit",
|
||||
@@ -2598,7 +2650,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
@@ -2621,7 +2673,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
@@ -2655,9 +2707,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6"
|
||||
checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
@@ -2721,6 +2773,50 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "pin-project"
|
||||
version = "1.1.11"
|
||||
@@ -2765,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
@@ -2804,7 +2900,7 @@ version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
@@ -2839,9 +2935,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
@@ -2913,7 +3009,7 @@ checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
||||
|
||||
[[package]]
|
||||
name = "pwsp"
|
||||
version = "1.7.1"
|
||||
version = "1.7.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap",
|
||||
@@ -2921,6 +3017,7 @@ dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_dnd",
|
||||
"egui_extras",
|
||||
"egui_material_icons",
|
||||
"evdev",
|
||||
"itertools 0.14.0",
|
||||
@@ -2935,9 +3032,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.28"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
@@ -2981,6 +3078,21 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -2989,9 +3101,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -3032,7 +3144,7 @@ version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3041,7 +3153,7 @@ version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3160,7 +3272,7 @@ version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
@@ -3173,7 +3285,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
@@ -3326,6 +3438,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c82d449ab1bccfeec125893c6875008206f038d4eb8a09e1e10caf86f44d574e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.40.0"
|
||||
@@ -3363,7 +3481,7 @@ version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"calloop 0.13.0",
|
||||
"calloop-wayland-source 0.3.0",
|
||||
"cursor-icon",
|
||||
@@ -3388,7 +3506,7 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"calloop 0.14.4",
|
||||
"calloop-wayland-source 0.4.1",
|
||||
"cursor-icon",
|
||||
@@ -3778,9 +3896,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.51.1"
|
||||
version = "1.52.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -3816,7 +3934,7 @@ dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 1.0.1",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3837,7 +3955,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow 1.0.1",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3846,7 +3964,7 @@ version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.1",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3907,6 +4025,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
@@ -3952,9 +4076,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.0"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
@@ -4017,11 +4141,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[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"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4116,7 +4240,7 @@ version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
@@ -4142,7 +4266,7 @@ version = "0.31.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"rustix 1.1.4",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
@@ -4154,7 +4278,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
@@ -4176,7 +4300,7 @@ version = "0.32.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@@ -4188,7 +4312,7 @@ version = "20250721.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -4201,7 +4325,7 @@ version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -4214,7 +4338,7 @@ version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -4227,7 +4351,7 @@ version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -4279,9 +4403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webbrowser"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16"
|
||||
checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"jni 0.22.4",
|
||||
@@ -4306,7 +4430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
@@ -4336,7 +4460,7 @@ dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
@@ -4373,7 +4497,7 @@ version = "29.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libloading",
|
||||
@@ -4404,7 +4528,7 @@ version = "29.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytemuck",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -4771,7 +4895,7 @@ dependencies = [
|
||||
"ahash",
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"block2 0.5.1",
|
||||
"bytemuck",
|
||||
"calloop 0.13.0",
|
||||
@@ -4824,9 +4948,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -4840,6 +4964,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
@@ -4889,7 +5019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4978,7 +5108,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"dlib",
|
||||
"log",
|
||||
"once_cell",
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "pwsp"
|
||||
version = "1.7.1"
|
||||
version = "1.7.4"
|
||||
edition = "2024"
|
||||
authors = ["arabian"]
|
||||
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]
|
||||
tokio = { version = "1.51.1", features = ["full"] }
|
||||
tokio = { version = "1.52.1", features = ["full"] }
|
||||
async-trait = "0.1.89"
|
||||
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
|
||||
clap = { version = "4.6.0", default-features = false, features = [
|
||||
clap = { version = "4.6.1", default-features = false, features = [
|
||||
"std",
|
||||
"suggestions",
|
||||
"help",
|
||||
@@ -50,6 +50,7 @@ eframe = { version = "0.34.1", default-features = false, features = [
|
||||
"x11",
|
||||
"wayland",
|
||||
] }
|
||||
egui_extras = "0.34.1"
|
||||
egui_material_icons = "0.6.0"
|
||||
egui_dnd = "0.15.0"
|
||||
|
||||
|
||||
@@ -52,6 +52,28 @@ three main components:
|
||||
You can download pre-built binaries and .deb packages from
|
||||
the [releases page](https://github.com/arabianq/pipewire-soundpad/releases).
|
||||
|
||||
## **Flatpak**
|
||||
|
||||
You can install PWSP via Flatpak from our custom repository hosted on GitHub Pages.
|
||||
|
||||
Add the repository:
|
||||
|
||||
```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)**
|
||||
|
||||
If you're using Fedora, you can install PWSP from a dedicated repository using DNF.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = pwsp-bin
|
||||
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
|
||||
pkgver = 1.7.1
|
||||
pkgver = 1.7.4
|
||||
pkgrel = 2
|
||||
url = https://github.com/arabianq/pipewire-soundpad
|
||||
arch = x86_64
|
||||
@@ -9,8 +9,8 @@ depends = pipewire
|
||||
depends = alsa-lib
|
||||
provides = 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 = pipewire-soundpad-1.7.1.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.1.tar.gz
|
||||
source = pwsp-bin-1.7.4.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.7.4/pwsp-v1.7.4-linux-x64.zip
|
||||
source = pipewire-soundpad-1.7.4.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.7.4.tar.gz
|
||||
sha256sums = SKIP
|
||||
sha256sums = SKIP
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
||||
pkgname=pwsp-bin
|
||||
_pkgname=pipewire-soundpad
|
||||
pkgver=1.7.1
|
||||
pkgver=1.7.4
|
||||
pkgrel=2
|
||||
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
|
||||
arch=('x86_64')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pkgbase = pwsp
|
||||
pkgdesc = Lets you play audio files through your microphone
|
||||
pkgver = 1.7.1
|
||||
pkgver = 1.7.4
|
||||
pkgrel = 1
|
||||
url = https://github.com/arabianq/pipewire-soundpad
|
||||
arch = any
|
||||
@@ -10,7 +10,7 @@ pkgbase = pwsp
|
||||
makedepends = cargo
|
||||
makedepends = pipewire
|
||||
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
|
||||
|
||||
pkgname = pwsp
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
||||
pkgsubn=pwsp
|
||||
pkgname=pwsp
|
||||
pkgver=1.7.1
|
||||
pkgver=1.7.4
|
||||
pkgrel=1
|
||||
pkgdesc="Lets you play audio files through your microphone"
|
||||
arch=('any')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+19
@@ -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"
|
||||
@@ -5,5 +5,5 @@ Exec=pwsp-wrapper.py %u
|
||||
Icon=ru.arabianq.pwsp
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Audio;Utility;
|
||||
Categories=AudioVideo;Audio;
|
||||
Keywords=soundpad;pipewire;audio;
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
<launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>
|
||||
https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image>
|
||||
<image>https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<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" />
|
||||
</component>
|
||||
@@ -14,10 +14,9 @@ finish-args:
|
||||
- --filesystem=xdg-run/pipewire-0
|
||||
- --filesystem=xdg-run/pwsp:create
|
||||
- --filesystem=xdg-run/app/ru.arabianq.pwsp:create
|
||||
- --filesystem=host
|
||||
- --filesystem=home
|
||||
- --device=all
|
||||
- --device=dri
|
||||
- --share=network
|
||||
- --talk-name=org.freedesktop.portal.Desktop
|
||||
- --talk-name=org.freedesktop.portal.Documents
|
||||
|
||||
@@ -30,11 +29,8 @@ build-options:
|
||||
modules:
|
||||
- name: pwsp
|
||||
buildsystem: simple
|
||||
build-options:
|
||||
build-args:
|
||||
- --share=network
|
||||
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-cli /app/bin/pwsp-cli
|
||||
- install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui
|
||||
@@ -45,3 +41,4 @@ modules:
|
||||
sources:
|
||||
- type: dir
|
||||
path: ../../
|
||||
- cargo-sources.json
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
%global cargo_install_lib 0
|
||||
|
||||
Name: pwsp
|
||||
Version: 1.7.1
|
||||
Version: 1.7.4
|
||||
Release: %autorelease
|
||||
Summary: Lets you play audio files through your microphone
|
||||
|
||||
|
||||
+7
-26
@@ -1,10 +1,10 @@
|
||||
use pwsp::{
|
||||
types::socket::{Request, Response},
|
||||
types::socket::{MAX_MESSAGE_SIZE, Request, Response},
|
||||
utils::{
|
||||
commands::parse_command,
|
||||
daemon::{
|
||||
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,
|
||||
pipewire::create_virtual_mic,
|
||||
@@ -32,25 +32,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
eprintln!("Failed to initialize audio player: {}", err);
|
||||
} // 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 {
|
||||
start_global_hotkey_listener().await;
|
||||
});
|
||||
@@ -61,10 +42,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
lock_file.lock()?;
|
||||
|
||||
let socket_path = runtime_dir.join("daemon.sock");
|
||||
if let Err(e) = fs::remove_file(&socket_path) {
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(e.into());
|
||||
}
|
||||
if let Err(e) = fs::remove_file(&socket_path)
|
||||
&& e.kind() != std::io::ErrorKind::NotFound
|
||||
{
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
let listener = UnixListener::bind(&socket_path)?;
|
||||
@@ -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;
|
||||
|
||||
if request_len > 10 * 1024 * 1024 {
|
||||
if request_len > MAX_MESSAGE_SIZE {
|
||||
eprintln!(
|
||||
"Failed to read message from client: request too large ({} bytes)!",
|
||||
request_len
|
||||
|
||||
+188
-173
@@ -1,9 +1,10 @@
|
||||
use crate::gui::SoundpadGui;
|
||||
use egui::{
|
||||
Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Grid,
|
||||
Label, Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
|
||||
Align, AtomExt, Button, CollapsingHeader, Color32, ComboBox, CursorIcon, FontFamily, Label,
|
||||
Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
|
||||
};
|
||||
use egui_dnd::dnd;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use egui_material_icons::icons::*;
|
||||
use pwsp::types::socket::Request;
|
||||
use pwsp::types::{audio_player::TrackInfo, gui::AppState};
|
||||
@@ -133,26 +134,24 @@ impl SoundpadGui {
|
||||
}
|
||||
|
||||
pub fn draw_hotkeys(&mut self, ui: &mut Ui) {
|
||||
let area_size = ui.available_size();
|
||||
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;
|
||||
|
||||
// Header
|
||||
ui.horizontal_top(|ui| {
|
||||
// --- Header ---
|
||||
ui.horizontal(|ui| {
|
||||
let back_button = Button::new(ICON_ARROW_BACK).frame(false);
|
||||
if ui.add(back_button).clicked() {
|
||||
self.app_state.show_hotkeys = false;
|
||||
}
|
||||
|
||||
ui.add_space(ui.available_width() / 2.0 - 40.0);
|
||||
ui.label(RichText::new("Hotkeys").color(Color32::WHITE).monospace());
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new("Hotkeys").color(Color32::WHITE).monospace());
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Search and Add Command
|
||||
// --- Search and Add Command ---
|
||||
ui.horizontal(|ui| {
|
||||
ui.menu_button(format!("{} Add Command", ICON_ADD.codepoint), |ui| {
|
||||
let mut selected_cmd = None;
|
||||
@@ -185,10 +184,10 @@ impl SoundpadGui {
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 22.0],
|
||||
ui.add(
|
||||
TextEdit::singleline(&mut self.app_state.hotkey_search_query)
|
||||
.hint_text("Search hotkeys..."),
|
||||
.hint_text("Search hotkeys...")
|
||||
.desired_width(f32::INFINITY),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -196,171 +195,190 @@ impl SoundpadGui {
|
||||
ui.add_space(5.0);
|
||||
|
||||
let conflicts = self.app_state.hotkey_config.find_conflicts();
|
||||
let conflict_slots: std::collections::HashSet<String> = conflicts
|
||||
.iter()
|
||||
.flat_map(|(a, b)| vec![a.clone(), b.clone()])
|
||||
.collect();
|
||||
let conflict_slots: std::collections::HashSet<&str> =
|
||||
conflicts.into_iter().flat_map(|(a, b)| [a, b]).collect();
|
||||
|
||||
let search = self.app_state.hotkey_search_query.to_lowercase();
|
||||
|
||||
// Slots table
|
||||
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
|
||||
let slots: Vec<_> = self
|
||||
.app_state
|
||||
.hotkey_config
|
||||
.slots
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
if search.is_empty() {
|
||||
return true;
|
||||
}
|
||||
s.slot.to_lowercase().contains(&search)
|
||||
|| format!("{:?}", s.action).to_lowercase().contains(&search)
|
||||
|| s.key_chord
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.to_lowercase()
|
||||
.contains(&search)
|
||||
})
|
||||
.cloned()
|
||||
.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),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
let slots: Vec<_> = self
|
||||
.app_state
|
||||
.hotkey_config
|
||||
.slots
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
if search.is_empty() {
|
||||
return true;
|
||||
}
|
||||
s.slot.to_lowercase().contains(&search)
|
||||
|| format!("{:?}", s.action).to_lowercase().contains(&search)
|
||||
|| s.key_chord
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.to_lowercase()
|
||||
.contains(&search)
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
for slot in &slots {
|
||||
ui.horizontal(|ui| {
|
||||
// Conflict badge
|
||||
if conflict_slots.contains(&slot.slot) {
|
||||
ui.label(
|
||||
RichText::new(ICON_WARNING.codepoint)
|
||||
.color(Color32::from_rgb(255, 165, 0)),
|
||||
)
|
||||
.on_hover_text("Key chord conflict");
|
||||
}
|
||||
|
||||
// Slot name
|
||||
let slot_text = RichText::new(&slot.slot).monospace();
|
||||
ui.label(slot_text);
|
||||
});
|
||||
|
||||
// Action description
|
||||
let action_name = match slot.action.name.as_str() {
|
||||
"play" => {
|
||||
if let Some(file_path_str) = slot.action.args.get("file_path") {
|
||||
Path::new(file_path_str)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
} else {
|
||||
"Play".to_string()
|
||||
}
|
||||
}
|
||||
"toggle_pause" => "Toggle Pause".to_string(),
|
||||
"pause" => "Pause Playback".to_string(),
|
||||
"resume" => "Resume Playback".to_string(),
|
||||
"stop" => "Stop Playback".to_string(),
|
||||
"toggle_loop" => "Toggle Loop".to_string(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
ui.add(Label::new(RichText::new(action_name).monospace()).truncate());
|
||||
|
||||
// Key chord
|
||||
let chord_text = slot.key_chord.as_deref().unwrap_or("(none)");
|
||||
ui.label(RichText::new(chord_text).monospace().color(
|
||||
if slot.key_chord.is_some() {
|
||||
Color32::from_rgb(100, 200, 100)
|
||||
} else {
|
||||
Color32::GRAY
|
||||
},
|
||||
));
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
// Delete button
|
||||
if ui
|
||||
.add(Button::new(ICON_DELETE).frame(false))
|
||||
.on_hover_text("Remove slot")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::Remove(slot.slot.clone()));
|
||||
}
|
||||
|
||||
// Set key chord button
|
||||
if ui
|
||||
.add(Button::new(ICON_KEYBOARD).frame(false))
|
||||
.on_hover_text("Set key chord")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::Capture(slot.slot.clone()));
|
||||
}
|
||||
|
||||
// Clear key chord
|
||||
if slot.key_chord.is_some()
|
||||
&& ui
|
||||
.add(Button::new(ICON_BACKSPACE).frame(false))
|
||||
.on_hover_text("Clear key chord")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::ClearChord(slot.slot.clone()));
|
||||
}
|
||||
|
||||
// Play button
|
||||
if ui
|
||||
.add(Button::new(ICON_PLAY_ARROW).frame(false))
|
||||
.on_hover_text("Play")
|
||||
.clicked()
|
||||
{
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.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 {
|
||||
body.row(30.0, |mut row| {
|
||||
// Column 1: Slot
|
||||
row.col(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if conflict_slots.contains(slot.slot.as_str()) {
|
||||
ui.label(
|
||||
RichText::new(ICON_WARNING.codepoint)
|
||||
.color(Color32::from_rgb(255, 165, 0)),
|
||||
)
|
||||
.on_hover_text("Key chord conflict");
|
||||
}
|
||||
ui.add(
|
||||
Label::new(RichText::new(&slot.slot).monospace())
|
||||
.truncate(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Column 2: Sound / Action name
|
||||
row.col(|ui| {
|
||||
let action_name = match slot.action.name.as_str() {
|
||||
"play" => {
|
||||
if let Some(file_path_str) =
|
||||
slot.action.args.get("file_path")
|
||||
{
|
||||
Path::new(file_path_str)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
} else {
|
||||
"Play".to_string()
|
||||
}
|
||||
}
|
||||
"toggle_pause" => "Toggle Pause".to_string(),
|
||||
"pause" => "Pause Playback".to_string(),
|
||||
"resume" => "Resume Playback".to_string(),
|
||||
"stop" => "Stop Playback".to_string(),
|
||||
"toggle_loop" => "Toggle Loop".to_string(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
ui.add(
|
||||
Label::new(RichText::new(action_name).monospace()).truncate(),
|
||||
);
|
||||
});
|
||||
|
||||
// Column 3: Key Chord
|
||||
row.col(|ui| {
|
||||
let chord_text = slot.key_chord.as_deref().unwrap_or("(none)");
|
||||
ui.add(
|
||||
Label::new(RichText::new(chord_text).monospace().color(
|
||||
if slot.key_chord.is_some() {
|
||||
Color32::from_rgb(100, 200, 100)
|
||||
} else {
|
||||
Color32::GRAY
|
||||
},
|
||||
))
|
||||
.truncate(),
|
||||
);
|
||||
});
|
||||
|
||||
// Column 4: Actions
|
||||
row.col(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.add(Button::new(ICON_DELETE).frame(false))
|
||||
.on_hover_text("Remove slot")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::Remove(slot.slot.clone()));
|
||||
}
|
||||
if ui
|
||||
.add(Button::new(ICON_KEYBOARD).frame(false))
|
||||
.on_hover_text("Set key chord")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::Capture(slot.slot.clone()));
|
||||
}
|
||||
if slot.key_chord.is_some()
|
||||
&& ui
|
||||
.add(Button::new(ICON_BACKSPACE).frame(false))
|
||||
.on_hover_text("Clear key chord")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::ClearChord(slot.slot.clone()));
|
||||
}
|
||||
if ui
|
||||
.add(Button::new(ICON_PLAY_ARROW).frame(false))
|
||||
.on_hover_text("Play")
|
||||
.clicked()
|
||||
{
|
||||
action = Some(HotkeyAction::Play(slot.slot.clone()));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(action) = action {
|
||||
match action {
|
||||
@@ -609,10 +627,10 @@ impl SoundpadGui {
|
||||
.unwrap_or_else(|| path.to_string_lossy().to_string());
|
||||
|
||||
let mut dir_button_text = RichText::new(name.clone());
|
||||
if let Some(current_dir) = &self.app_state.current_dir {
|
||||
if current_dir.eq(&path) {
|
||||
dir_button_text = dir_button_text.color(Color32::WHITE);
|
||||
}
|
||||
if let Some(current_dir) = &self.app_state.current_dir
|
||||
&& current_dir.eq(&path)
|
||||
{
|
||||
dir_button_text = dir_button_text.color(Color32::WHITE);
|
||||
}
|
||||
|
||||
let dir_button =
|
||||
@@ -645,10 +663,9 @@ impl SoundpadGui {
|
||||
ICON_OPEN_IN_BROWSER.codepoint, "Open in File Manager"
|
||||
))
|
||||
.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();
|
||||
@@ -728,13 +745,13 @@ impl SoundpadGui {
|
||||
}
|
||||
|
||||
let mut file_button_text = RichText::new(&file_name);
|
||||
if let Some(current_file) = &self.app_state.selected_file {
|
||||
if current_file.eq(&entry_path) {
|
||||
file_button_text = file_button_text.color(Color32::WHITE);
|
||||
}
|
||||
if let Some(current_file) = &self.app_state.selected_file
|
||||
&& current_file.eq(&entry_path)
|
||||
{
|
||||
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);
|
||||
if file_button_response.clicked() {
|
||||
ui.input(|i| {
|
||||
@@ -792,10 +809,9 @@ impl SoundpadGui {
|
||||
ICON_OPEN_IN_BROWSER.codepoint, "Show in File Manager"
|
||||
))
|
||||
.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();
|
||||
@@ -822,15 +838,14 @@ impl SoundpadGui {
|
||||
|
||||
fn get_hotkey_badge(&self, path: &PathBuf) -> Option<String> {
|
||||
for slot in &self.app_state.hotkey_config.slots {
|
||||
if slot.action.name == "play" {
|
||||
if let Some(file_path_str) = slot.action.args.get("file_path") {
|
||||
if Path::new(file_path_str) == path.as_path() {
|
||||
if let Some(chord) = &slot.key_chord {
|
||||
return Some(format!("[{}]", chord));
|
||||
} else {
|
||||
return Some(format!("[{}]", slot.slot));
|
||||
}
|
||||
}
|
||||
if slot.action.name == "play"
|
||||
&& let Some(file_path_str) = slot.action.args.get("file_path")
|
||||
&& Path::new(file_path_str) == path.as_path()
|
||||
{
|
||||
if let Some(chord) = &slot.key_chord {
|
||||
return Some(format!("[{}]", chord));
|
||||
} else {
|
||||
return Some(format!("[{}]", slot.slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-115
@@ -7,57 +7,15 @@ use std::path::PathBuf;
|
||||
|
||||
/// Convert an egui Key + Modifiers to a normalized chord string like "Ctrl+Shift+A".
|
||||
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
|
||||
let key_name = match key {
|
||||
Key::A => "A",
|
||||
Key::B => "B",
|
||||
Key::C => "C",
|
||||
Key::D => "D",
|
||||
Key::E => "E",
|
||||
Key::F => "F",
|
||||
Key::G => "G",
|
||||
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,
|
||||
};
|
||||
let key_name = key.name();
|
||||
let is_valid = (key_name.len() == 1
|
||||
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
|
||||
|| (key_name.starts_with('F')
|
||||
&& key_name.len() > 1
|
||||
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
||||
if !is_valid {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Require at least one modifier for hotkey chords (ignoring command/Super due to Wayland/Niri bug)
|
||||
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] {
|
||||
"A" => Key::A,
|
||||
"B" => Key::B,
|
||||
"C" => Key::C,
|
||||
"D" => Key::D,
|
||||
"E" => Key::E,
|
||||
"F" => Key::F,
|
||||
"G" => Key::G,
|
||||
"H" => Key::H,
|
||||
"I" => Key::I,
|
||||
"J" => Key::J,
|
||||
"K" => Key::K,
|
||||
"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,
|
||||
};
|
||||
let key_name = parts[parts.len() - 1];
|
||||
let is_valid = (key_name.len() == 1
|
||||
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
|
||||
|| (key_name.starts_with('F')
|
||||
&& key_name.len() > 1
|
||||
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
||||
|
||||
if !is_valid {
|
||||
return None;
|
||||
}
|
||||
|
||||
let key = Key::from_name(key_name)?;
|
||||
|
||||
Some((modifiers, key))
|
||||
}
|
||||
@@ -273,18 +192,18 @@ impl SoundpadGui {
|
||||
}
|
||||
|
||||
// Play selected file on Enter
|
||||
if self.key_pressed(ctx, Key::Enter) {
|
||||
if let Some(path) = self.app_state.selected_file.clone() {
|
||||
if modifiers.ctrl {
|
||||
self.play_file(&path, true);
|
||||
} else if modifiers.shift
|
||||
&& let Some(last_track) = self.audio_player_state.tracks.last()
|
||||
{
|
||||
self.stop(Some(last_track.id));
|
||||
self.play_file(&path, true);
|
||||
} else {
|
||||
self.play_file(&path, false);
|
||||
}
|
||||
if self.key_pressed(ctx, Key::Enter)
|
||||
&& let Some(path) = self.app_state.selected_file.clone()
|
||||
{
|
||||
if modifiers.ctrl {
|
||||
self.play_file(&path, true);
|
||||
} else if modifiers.shift
|
||||
&& let Some(last_track) = self.audio_player_state.tracks.last()
|
||||
{
|
||||
self.stop(Some(last_track.id));
|
||||
self.play_file(&path, true);
|
||||
} else {
|
||||
self.play_file(&path, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +212,7 @@ impl SoundpadGui {
|
||||
let arrow_down_pressed = self.key_pressed(ctx, Key::ArrowDown);
|
||||
if modifiers.ctrl && (arrow_up_pressed || arrow_down_pressed) {
|
||||
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();
|
||||
|
||||
let current_dir_index = self
|
||||
|
||||
+52
-19
@@ -2,7 +2,7 @@ use crate::{
|
||||
types::pipewire::{DeviceType, Terminate},
|
||||
utils::{
|
||||
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};
|
||||
@@ -58,6 +58,7 @@ pub struct AudioPlayer {
|
||||
pub next_id: u32,
|
||||
|
||||
input_link_sender: Option<pipewire::channel::Sender<Terminate>>,
|
||||
player_link_sender: Option<pipewire::channel::Sender<Terminate>>,
|
||||
pub input_device_name: Option<String>,
|
||||
|
||||
pub volume: f32, // Master volume
|
||||
@@ -74,6 +75,7 @@ impl AudioPlayer {
|
||||
next_id: 1,
|
||||
|
||||
input_link_sender: None,
|
||||
player_link_sender: None,
|
||||
input_device_name: daemon_config.default_input_name.clone(),
|
||||
|
||||
volume: default_volume,
|
||||
@@ -98,21 +100,46 @@ impl AudioPlayer {
|
||||
fn drop_stream(&mut self) {
|
||||
if self.stream_handle.is_some() {
|
||||
self.stream_handle = None;
|
||||
self.abort_player_link_thread();
|
||||
}
|
||||
}
|
||||
|
||||
fn abort_link_thread(&mut self) {
|
||||
if let Some(sender) = &self.input_link_sender {
|
||||
match sender.send(Terminate {}) {
|
||||
Ok(_) => {
|
||||
println!("Sent terminate signal to link thread");
|
||||
self.input_link_sender = None;
|
||||
}
|
||||
Err(_) => eprintln!("Failed to send terminate signal to link thread"),
|
||||
if sender.send(Terminate {}).is_ok() {
|
||||
println!("Sent terminate signal to input link thread");
|
||||
self.input_link_sender = None;
|
||||
} else {
|
||||
eprintln!("Failed to send terminate signal to input 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>> {
|
||||
self.abort_link_thread();
|
||||
|
||||
@@ -227,12 +254,12 @@ impl AudioPlayer {
|
||||
pub fn get_volume(&mut self, id: Option<u32>) -> Option<f32> {
|
||||
if let Some(id) = id {
|
||||
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||
return Some(sound.sink.volume());
|
||||
Some(sound.sink.volume())
|
||||
} else {
|
||||
return None;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
return Some(self.volume);
|
||||
Some(self.volume)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +343,7 @@ impl AudioPlayer {
|
||||
}
|
||||
|
||||
self.ensure_stream()?;
|
||||
self.link_player().await.ok();
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
@@ -394,6 +422,10 @@ impl AudioPlayer {
|
||||
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
|
||||
@@ -411,10 +443,10 @@ impl AudioPlayer {
|
||||
if let Some(sound) = self.tracks.get(&id) {
|
||||
let path = sound.path.clone();
|
||||
let handle = tokio::task::spawn_blocking(move || {
|
||||
if let Ok(file) = fs::File::open(&path) {
|
||||
if let Ok(source) = Decoder::try_from(file) {
|
||||
return Some((id, source));
|
||||
}
|
||||
if let Ok(file) = fs::File::open(&path)
|
||||
&& let Ok(source) = Decoder::try_from(file)
|
||||
{
|
||||
return Some((id, source));
|
||||
}
|
||||
None
|
||||
});
|
||||
@@ -423,11 +455,12 @@ impl AudioPlayer {
|
||||
}
|
||||
|
||||
for handle in restart_futures {
|
||||
if let Ok(Some((id, source))) = handle.await {
|
||||
if let Some(sound) = self.tracks.get_mut(&id) {
|
||||
sound.sink.append(source);
|
||||
sound.sink.play();
|
||||
}
|
||||
if let Ok(res) = handle.await
|
||||
&& let Some((id, source)) = res
|
||||
&& let Some(sound) = self.tracks.get_mut(&id)
|
||||
{
|
||||
sound.sink.append(source);
|
||||
sound.sink.play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -673,7 +673,7 @@ impl Executable for PlayHotkeyCommand {
|
||||
if let Some(cmd) = parse_command(&action) {
|
||||
cmd.execute().await
|
||||
} else {
|
||||
Response::new(false, "Unknown command in hotkey slot".to_string())
|
||||
Response::new(false, "Unknown command in hotkey slot")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -13,10 +13,10 @@ impl DaemonConfig {
|
||||
pub fn save_to_file(&self) -> Result<(), Box<dyn Error>> {
|
||||
let config_path = get_config_path()?.join("daemon.json");
|
||||
|
||||
if let Some(config_dir) = config_path.parent() {
|
||||
if !config_path.exists() {
|
||||
fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
if let Some(config_dir) = config_path.parent()
|
||||
&& !config_path.exists()
|
||||
{
|
||||
fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
|
||||
let config_json = serde_json::to_string_pretty(self)?;
|
||||
@@ -68,10 +68,10 @@ impl GuiConfig {
|
||||
pub fn save_to_file(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
let config_path = get_config_path()?.join("gui.json");
|
||||
|
||||
if let Some(config_dir) = config_path.parent() {
|
||||
if !config_path.exists() {
|
||||
fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
if let Some(config_dir) = config_path.parent()
|
||||
&& !config_path.exists()
|
||||
{
|
||||
fs::create_dir_all(config_dir)?;
|
||||
}
|
||||
|
||||
// Do not save scale factor if user does not want to
|
||||
@@ -172,7 +172,7 @@ impl HotkeyConfig {
|
||||
}
|
||||
|
||||
/// 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 chord_map: HashMap<&str, Vec<&str>> = HashMap::new();
|
||||
|
||||
@@ -186,7 +186,7 @@ impl HotkeyConfig {
|
||||
if slots.len() > 1 {
|
||||
for i in 0..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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const MAX_MESSAGE_SIZE: usize = 128 * 1024;
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Request {
|
||||
pub name: String,
|
||||
|
||||
@@ -122,3 +122,88 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
||||
_ => 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());
|
||||
}
|
||||
}
|
||||
|
||||
+13
-45
@@ -1,11 +1,9 @@
|
||||
use crate::{
|
||||
types::{
|
||||
audio_player::AudioPlayer,
|
||||
config::DaemonConfig,
|
||||
socket::{Request, Response},
|
||||
},
|
||||
utils::pipewire::{create_link, get_device},
|
||||
use crate::types::{
|
||||
audio_player::AudioPlayer,
|
||||
config::DaemonConfig,
|
||||
socket::{MAX_MESSAGE_SIZE, Request, Response},
|
||||
};
|
||||
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
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 {
|
||||
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;
|
||||
|
||||
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];
|
||||
if stream.read_exact(&mut buffer).await.is_err() {
|
||||
return Err("Failed to read response".into());
|
||||
|
||||
+7
-7
@@ -112,13 +112,13 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
||||
let hotkey_res = make_request(Request::get_hotkeys())
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if hotkey_res.status {
|
||||
if let Ok(config) = serde_json::from_str::<HotkeyConfig>(&hotkey_res.message) {
|
||||
let mut guard = audio_player_state_shared
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
guard.hotkey_config = Some(config);
|
||||
}
|
||||
if hotkey_res.status
|
||||
&& let Ok(config) = serde_json::from_str::<HotkeyConfig>(&hotkey_res.message)
|
||||
{
|
||||
let mut guard = audio_player_state_shared
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
guard.hotkey_config = Some(config);
|
||||
}
|
||||
last_hotkey_poll = Instant::now();
|
||||
}
|
||||
|
||||
+58
-22
@@ -9,6 +9,13 @@ use tokio::{
|
||||
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(
|
||||
global_object: &GlobalObject<&DictRef>,
|
||||
) -> (Option<AudioDevice>, Option<Port>) {
|
||||
@@ -56,20 +63,20 @@ fn parse_global_object(
|
||||
(None, None)
|
||||
};
|
||||
// Check if the object is a port
|
||||
} else if props.get("port.direction").is_some() {
|
||||
if let (Some(node_id), Some(port_id), Some(port_name)) = (
|
||||
} else if props.get("port.direction").is_some()
|
||||
&& let (Some(node_id), Some(port_id), Some(port_name)) = (
|
||||
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.name"),
|
||||
) {
|
||||
let port = Port {
|
||||
node_id,
|
||||
port_id,
|
||||
name: port_name.to_string(),
|
||||
};
|
||||
)
|
||||
{
|
||||
let port = Port {
|
||||
node_id,
|
||||
port_id,
|
||||
name: port_name.to_string(),
|
||||
};
|
||||
|
||||
return (None, Some(port));
|
||||
}
|
||||
return (None, Some(port));
|
||||
}
|
||||
}
|
||||
(None, None)
|
||||
@@ -79,9 +86,7 @@ async fn pw_get_global_objects_thread(
|
||||
main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>,
|
||||
pw_receiver: pipewire::channel::Receiver<Terminate>,
|
||||
) {
|
||||
pipewire::init();
|
||||
|
||||
let main_loop = MainLoopRc::new(None).expect("Failed to initialize pipewire main loop");
|
||||
let (main_loop, context) = setup_pipewire_context();
|
||||
|
||||
// Stop main loop on Terminate message
|
||||
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
||||
@@ -89,7 +94,6 @@ async fn pw_get_global_objects_thread(
|
||||
move |_| _main_loop.quit()
|
||||
});
|
||||
|
||||
let context = ContextRc::new(&main_loop, None).expect("Failed to create pipewire context");
|
||||
let core = context
|
||||
.connect(None)
|
||||
.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_thread = thread::spawn(move || {
|
||||
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");
|
||||
let (main_loop, context) = setup_pipewire_context();
|
||||
let core = context
|
||||
.connect(None)
|
||||
.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)
|
||||
}
|
||||
|
||||
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(
|
||||
output_fl: Port,
|
||||
output_fr: Port,
|
||||
@@ -267,10 +306,7 @@ pub fn create_link(
|
||||
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
|
||||
|
||||
let _pw_thread = thread::spawn(move || {
|
||||
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");
|
||||
let (main_loop, context) = setup_pipewire_context();
|
||||
let core = context
|
||||
.connect(None)
|
||||
.expect("Failed to connect to pipewire context");
|
||||
|
||||
Reference in New Issue
Block a user