mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-07-27 14:14:13 +00:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d7abdbf53 | ||
|
|
41bb035b2c | ||
|
|
3873bf4270 | ||
|
|
2b60b47cce | ||
|
|
65ae8589db | ||
|
|
a33d023950 | ||
|
|
5c73bc1bea | ||
|
|
42e305869a | ||
|
|
cb7f4f7f6b | ||
|
|
62821904af | ||
|
|
3a218283db | ||
|
|
02fdfb6ca8 | ||
|
|
a027838d0e | ||
|
|
1828d903a1 | ||
|
|
83ca7a5e34 | ||
|
|
f2aca5e34a | ||
|
|
6130a99c4b | ||
|
|
322b93efa1 | ||
|
|
683f4127a0 | ||
|
|
c873166e21 | ||
|
|
ec4e873071 | ||
|
|
a91252d7fb | ||
|
|
239170df45 | ||
|
|
5fef42a2e2 | ||
|
|
4f8f663f12 | ||
|
|
6ccc55a1d0 | ||
|
|
d266a3f073 | ||
|
|
d2993592be | ||
|
|
1a53ab62dc | ||
|
|
51fcd94a61 | ||
|
|
7f6a122816 | ||
|
|
7ca01ed8cb | ||
|
|
07321afb60 | ||
|
|
8534e516ad | ||
|
|
16ce233f79 | ||
|
|
c2f4658871 | ||
|
|
701bdebc88 | ||
|
|
d4ae78c7b0 | ||
|
|
9e1d50e30c | ||
|
|
85b6c17bd1 | ||
|
|
cfe478b02d | ||
|
|
0b23b3ed51 | ||
|
|
728d14006c | ||
|
|
f8bb84bbcf | ||
|
|
0dbcc771f5 | ||
|
|
b3a786a76c | ||
|
|
972e303bff | ||
|
|
c406704ce5 | ||
|
|
fd4a35e0e1 | ||
|
|
c793d09006 | ||
|
|
e4dd6d8b56 | ||
|
|
457416779a | ||
|
|
b0ac5490cc | ||
|
|
a728671b01 | ||
|
|
08153e4a1c | ||
|
|
c4699002fd | ||
|
|
dbcbf36df0 | ||
|
|
873ed9a6db | ||
|
|
601a5ce336 |
@@ -1,6 +1,29 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
- package-ecosystem: "cargo"
|
- package-ecosystem: "cargo"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
groups:
|
||||||
|
egui-stack:
|
||||||
|
patterns:
|
||||||
|
- "egui*"
|
||||||
|
- "eframe"
|
||||||
|
|
||||||
|
serde-stack:
|
||||||
|
patterns:
|
||||||
|
- "serde*"
|
||||||
|
|
||||||
|
tokio-stack:
|
||||||
|
patterns:
|
||||||
|
- "tokio*"
|
||||||
|
- "reqwest"
|
||||||
|
|
||||||
|
clap-stack:
|
||||||
|
patterns:
|
||||||
|
- "clap*"
|
||||||
|
|||||||
+126
-73
@@ -12,7 +12,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux-build:
|
build-and-package:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@@ -33,32 +33,30 @@ jobs:
|
|||||||
libclang-dev \
|
libclang-dev \
|
||||||
libasound2-dev \
|
libasound2-dev \
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
|
libssl-dev \
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.96.0
|
toolchain: stable
|
||||||
|
cache: false
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: "v3-${{ matrix.runner }}"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --locked
|
run: cargo test --locked
|
||||||
|
|
||||||
- name: Build all binaries (debug-speed compilation into target/release)
|
- name: Build all binaries
|
||||||
env:
|
run: cargo build --locked
|
||||||
CARGO_PROFILE_RELEASE_OPT_LEVEL: 0
|
|
||||||
CARGO_PROFILE_RELEASE_DEBUG: "true"
|
|
||||||
CARGO_PROFILE_RELEASE_STRIP: "false"
|
|
||||||
CARGO_PROFILE_RELEASE_LTO: "false"
|
|
||||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 256
|
|
||||||
run: cargo build --release --locked
|
|
||||||
|
|
||||||
- name: Extract all binary names
|
- name: Extract all binary names
|
||||||
id: cargo-meta
|
id: cargo-meta
|
||||||
@@ -70,7 +68,7 @@ jobs:
|
|||||||
echo "$BIN_NAMES" >> $GITHUB_OUTPUT
|
echo "$BIN_NAMES" >> $GITHUB_OUTPUT
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Package all binaries into one archive
|
- name: Package ZIP archive
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -81,7 +79,7 @@ jobs:
|
|||||||
FILES=()
|
FILES=()
|
||||||
while IFS= read -r BIN; do
|
while IFS= read -r BIN; do
|
||||||
[ -z "$BIN" ] && continue
|
[ -z "$BIN" ] && continue
|
||||||
FILES+=("target/release/$BIN")
|
FILES+=("target/debug/$BIN")
|
||||||
done <<< "${{ steps.cargo-meta.outputs.bin_names }}"
|
done <<< "${{ steps.cargo-meta.outputs.bin_names }}"
|
||||||
|
|
||||||
if [ "${#FILES[@]}" -eq 0 ]; then
|
if [ "${#FILES[@]}" -eq 0 ]; then
|
||||||
@@ -89,70 +87,18 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for f in "${FILES[@]}"; do
|
|
||||||
if [ ! -f "$f" ]; then
|
|
||||||
echo "Error: expected binary not found: $f" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Will add: $f"
|
|
||||||
done
|
|
||||||
|
|
||||||
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
|
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
|
||||||
|
|
||||||
- name: Upload archive as artifact
|
- name: Upload ZIP artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: archive-${{ matrix.arch }}
|
name: archive-${{ matrix.arch }}
|
||||||
path: pwsp-*.zip
|
path: pwsp-*.zip
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
deb-build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- arch: x64
|
|
||||||
runner: ubuntu-latest
|
|
||||||
- arch: arm64
|
|
||||||
runner: ubuntu-24.04-arm
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install apt deps (dev-libs)
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y \
|
|
||||||
libpipewire-0.3-dev \
|
|
||||||
libclang-dev \
|
|
||||||
libasound2-dev \
|
|
||||||
libdbus-1-dev \
|
|
||||||
pkg-config
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: 1.96.0
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Build all binaries (debug-speed compilation into target/release)
|
|
||||||
env:
|
|
||||||
CARGO_PROFILE_RELEASE_OPT_LEVEL: 0
|
|
||||||
CARGO_PROFILE_RELEASE_DEBUG: "true"
|
|
||||||
CARGO_PROFILE_RELEASE_STRIP: "false"
|
|
||||||
CARGO_PROFILE_RELEASE_LTO: "false"
|
|
||||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 256
|
|
||||||
run: cargo build --release --locked
|
|
||||||
|
|
||||||
- name: Cache cargo-deb
|
- name: Cache cargo-deb
|
||||||
id: cache-cargo-deb
|
id: cache-cargo-deb
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin/cargo-deb
|
path: ~/.cargo/bin/cargo-deb
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-cargo-deb-v1
|
key: ${{ runner.os }}-${{ runner.arch }}-cargo-deb-v1
|
||||||
@@ -161,20 +107,108 @@ jobs:
|
|||||||
if: steps.cache-cargo-deb.outputs.cache-hit != 'true'
|
if: steps.cache-cargo-deb.outputs.cache-hit != 'true'
|
||||||
run: cargo install --locked cargo-deb
|
run: cargo install --locked cargo-deb
|
||||||
|
|
||||||
- name: Create .deb package (debug binaries from target/release)
|
- name: Create .deb package
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
cargo-deb -p pwsp-gui --no-build --no-strip
|
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == "refs/heads/main" ]; then
|
||||||
|
BASE_VERSION=$(grep '^version =' Cargo.toml | head -1 | cut -d'"' -f2)
|
||||||
|
GIT_REV=$(git rev-parse --short HEAD)
|
||||||
|
DATE=$(date +%Y%m%d)
|
||||||
|
DEB_VER="${BASE_VERSION}+git${DATE}.${GIT_REV}"
|
||||||
|
echo "Building nightly deb version: $DEB_VER"
|
||||||
|
cargo-deb -p pwsp-gui --no-build --no-strip --profile dev --deb-version "$DEB_VER"
|
||||||
|
else
|
||||||
|
cargo-deb -p pwsp-gui --no-build --no-strip --profile dev
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Upload .deb(s) as artifacts
|
- name: Upload .deb artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: deb-packages-${{ matrix.arch }}
|
name: deb-packages-${{ matrix.arch }}
|
||||||
path: target/debian/*.deb
|
path: target/debian/*.deb
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
deploy-nightly-apt:
|
||||||
|
needs: build-and-package
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency: gh-pages-deploy
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Setup GPG
|
||||||
|
id: gpg
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v7
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
|
- name: Download DEB artifacts
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
pattern: deb-packages-*
|
||||||
|
path: debs
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Checkout existing gh-pages
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
path: gh-pages-branch
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Generate Nightly APT Repository
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y reprepro
|
||||||
|
|
||||||
|
mkdir -p apt-repo
|
||||||
|
if [ -d "gh-pages-branch/apt" ]; then
|
||||||
|
cp -r gh-pages-branch/apt/* apt-repo/ || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p apt-repo/conf
|
||||||
|
cat <<EOF > apt-repo/conf/distributions
|
||||||
|
Origin: arabianq
|
||||||
|
Label: PipeWire Soundpad
|
||||||
|
Codename: stable
|
||||||
|
Architectures: amd64 arm64
|
||||||
|
Components: main
|
||||||
|
Description: APT Repository for PWSP
|
||||||
|
SignWith: ${{ steps.gpg.outputs.fingerprint }}
|
||||||
|
|
||||||
|
Origin: arabianq
|
||||||
|
Label: PipeWire Soundpad
|
||||||
|
Codename: nightly
|
||||||
|
Architectures: amd64 arm64
|
||||||
|
Components: main
|
||||||
|
Description: Nightly APT Repository for PWSP
|
||||||
|
SignWith: ${{ steps.gpg.outputs.fingerprint }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
gpg --armor --export ${{ steps.gpg.outputs.fingerprint }} > apt-repo/pubkey.gpg
|
||||||
|
|
||||||
|
cd apt-repo
|
||||||
|
for deb in ../debs/*.deb; do
|
||||||
|
if [ -f "$deb" ]; then
|
||||||
|
echo "Adding $deb to nightly APT repository..."
|
||||||
|
reprepro includedeb nightly "$deb"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Deploy APT Repo to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./apt-repo
|
||||||
|
destination_dir: apt
|
||||||
|
keep_files: true
|
||||||
|
|
||||||
flatpak-build:
|
flatpak-build:
|
||||||
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||||
strategy:
|
strategy:
|
||||||
@@ -191,7 +225,7 @@ jobs:
|
|||||||
options: --privileged
|
options: --privileged
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Build Flatpak
|
- name: Build Flatpak
|
||||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
@@ -202,3 +236,22 @@ jobs:
|
|||||||
branch: master
|
branch: master
|
||||||
build-bundle: true
|
build-bundle: true
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
trigger-copr-nightly:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install copr-cli
|
||||||
|
run: pip install copr-cli
|
||||||
|
|
||||||
|
- name: Trigger Copr Build
|
||||||
|
env:
|
||||||
|
COPR_CONFIG: ${{ secrets.COPR_CONFIG }}
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.config
|
||||||
|
echo "$COPR_CONFIG" > ~/.config/copr
|
||||||
|
copr-cli buildscm --nowait \
|
||||||
|
--clone-url https://github.com/arabianq/pipewire-soundpad.git \
|
||||||
|
--commit ${{ github.sha }} \
|
||||||
|
--spec packages/rpm/pwsp-git.spec \
|
||||||
|
arabianq/pwsp
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Dependabot Flatpak Cargo Sources
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
paths:
|
||||||
|
- "Cargo.lock"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-cargo-sources:
|
||||||
|
if: github.actor == 'dependabot[bot]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # Needed to push back to the branch
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v7
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install aiohttp tomlkit
|
||||||
|
|
||||||
|
- name: Generate cargo-sources.json
|
||||||
|
run: bash ./scripts/generate-sources.sh
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
|
with:
|
||||||
|
commit_message: "chore(flatpak): automatically update cargo-sources.json"
|
||||||
|
file_pattern: "packages/flatpak/cargo-sources.json"
|
||||||
@@ -21,9 +21,17 @@ on:
|
|||||||
default: "stable"
|
default: "stable"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
flatter-x64:
|
build-flatpak:
|
||||||
name: Flatter (x86_64)
|
name: Build Flatpak (${{ matrix.arch }})
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x86_64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
- arch: aarch64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
container:
|
container:
|
||||||
@@ -32,13 +40,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag_name || github.ref }}
|
ref: ${{ inputs.tag_name || github.ref }}
|
||||||
|
|
||||||
- name: Setup GPG
|
- name: Setup GPG
|
||||||
id: gpg
|
id: gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v6
|
uses: crazy-max/ghaction-import-gpg@v7
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
@@ -56,6 +64,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Modify Manifest
|
- name: Modify Manifest
|
||||||
run: |
|
run: |
|
||||||
|
sed -i '/^branch:/d' packages/flatpak/ru.arabianq.pwsp.yaml
|
||||||
|
sed -i '/^default-branch:/d' packages/flatpak/ru.arabianq.pwsp.yaml
|
||||||
echo "branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
echo "branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
||||||
echo "default-branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
echo "default-branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
||||||
|
|
||||||
@@ -69,77 +79,108 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: packages/flatpak/ru.arabianq.pwsp.yaml
|
files: packages/flatpak/ru.arabianq.pwsp.yaml
|
||||||
gpg-sign: ${{ steps.gpg.outputs.fingerprint }}
|
gpg-sign: ${{ steps.gpg.outputs.fingerprint }}
|
||||||
upload-bundles: false
|
upload-bundles: true
|
||||||
upload-pages-artifact: false
|
upload-pages-artifact: false
|
||||||
arch: x86_64
|
arch: ${{ matrix.arch }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
flatter-arm64:
|
prepare-repo:
|
||||||
name: Flatter (aarch64)
|
name: Prepare OSTree Repo
|
||||||
needs: flatter-x64
|
needs: build-flatpak
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
concurrency: gh-pages-deploy
|
||||||
contents: read
|
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/andyholmes/flatter/freedesktop:25.08
|
image: ghcr.io/andyholmes/flatter/freedesktop:25.08
|
||||||
options: --privileged
|
options: --privileged
|
||||||
|
permissions:
|
||||||
|
contents: write # Needed to upload files to GitHub Release
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Download Flatpak Bundles
|
||||||
uses: actions/checkout@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag_name || github.ref }}
|
path: bundles
|
||||||
|
pattern: ru.arabianq.pwsp-*
|
||||||
|
|
||||||
|
- name: Checkout existing gh-pages
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
path: gh-pages-branch
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Setup GPG
|
- name: Setup GPG
|
||||||
id: gpg
|
id: gpg
|
||||||
uses: crazy-max/ghaction-import-gpg@v6
|
uses: crazy-max/ghaction-import-gpg@v7
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
- name: Set Default Branch
|
- name: Update OSTree Repo and Prepare Release Bundles
|
||||||
id: set_branch
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event_name }}" == "release" ]; then
|
mkdir -p release-bundles
|
||||||
echo "branch=stable" >> $GITHUB_OUTPUT
|
|
||||||
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
# Inherit existing OSTree repository to preserve previous branches (e.g. stable vs nightly)
|
||||||
echo "branch=${{ inputs.build_branch }}" >> $GITHUB_OUTPUT
|
# Only inherit if objects/ exists, otherwise it's a corrupted/empty repo due to Git ignoring empty folders
|
||||||
|
if [ -d "gh-pages-branch/objects" ]; then
|
||||||
|
echo "Found existing OSTree repository. Copying..."
|
||||||
|
cp -r gh-pages-branch pages-repo
|
||||||
|
# Clean up the .git folder so it doesn't interfere
|
||||||
|
rm -rf pages-repo/.git
|
||||||
|
# Recreate empty OSTree directories that Git ignored when pushing previously
|
||||||
|
mkdir -p pages-repo/refs/heads pages-repo/refs/mirrors pages-repo/refs/remotes \
|
||||||
|
pages-repo/objects pages-repo/state pages-repo/extensions pages-repo/tmp
|
||||||
else
|
else
|
||||||
echo "branch=nightly" >> $GITHUB_OUTPUT
|
echo "Initializing new OSTree repository..."
|
||||||
|
mkdir -p pages-repo
|
||||||
|
ostree init --mode=archive-z2 --repo=pages-repo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Modify Manifest
|
shopt -s nullglob
|
||||||
run: |
|
for dir in bundles/ru.arabianq.pwsp-*; do
|
||||||
echo "branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
arch=$(basename "$dir" | sed 's/ru.arabianq.pwsp-//')
|
||||||
echo "default-branch: ${{ steps.set_branch.outputs.branch }}" >> packages/flatpak/ru.arabianq.pwsp.yaml
|
|
||||||
|
|
||||||
- name: Install SDK Extensions
|
# Find the flatpak file inside the dir
|
||||||
run: |
|
bundle=$(find "$dir" -name "*.flatpak" | head -n 1)
|
||||||
flatpak install -y flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
|
|
||||||
flatpak install -y flathub org.freedesktop.Sdk.Extension.llvm20//25.08
|
|
||||||
|
|
||||||
- name: Build Flatpak
|
if [ -n "$bundle" ]; then
|
||||||
uses: andyholmes/flatter@main
|
echo "Importing $bundle for architecture $arch..."
|
||||||
|
flatpak build-import-bundle pages-repo "$bundle"
|
||||||
|
|
||||||
|
# Copy with a unique name for GitHub Release
|
||||||
|
cp "$bundle" "release-bundles/ru.arabianq.pwsp-${arch}.flatpak"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Update summary and sign the repository
|
||||||
|
flatpak build-update-repo --generate-static-deltas --gpg-sign=${{ steps.gpg.outputs.fingerprint }} pages-repo
|
||||||
|
|
||||||
|
# Export GPG public key for manual import
|
||||||
|
gpg --export ${{ steps.gpg.outputs.fingerprint }} > pages-repo/pwsp.gpg
|
||||||
|
|
||||||
|
# Generate a .flatpakrepo file for easy installation
|
||||||
|
GPG_BASE64=$(gpg --export ${{ steps.gpg.outputs.fingerprint }} | base64 -w 0)
|
||||||
|
cat <<EOF | sed 's/^ *//' > pages-repo/pwsp.flatpakrepo
|
||||||
|
[Flatpak Repo]
|
||||||
|
Title=PipeWire Soundpad
|
||||||
|
Url=https://arabianq.github.io/pipewire-soundpad/
|
||||||
|
Homepage=https://github.com/arabianq/pipewire-soundpad
|
||||||
|
Comment=PipeWire Soundpad Flatpak Repository
|
||||||
|
Description=Official repository for PWSP
|
||||||
|
GPGKey=${GPG_BASE64}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy Flatpak Repo to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
files: packages/flatpak/ru.arabianq.pwsp.yaml
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
gpg-sign: ${{ steps.gpg.outputs.fingerprint }}
|
publish_dir: ./pages-repo
|
||||||
upload-bundles: false
|
destination_dir: .
|
||||||
upload-pages-artifact: true
|
keep_files: true
|
||||||
arch: aarch64
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
deploy:
|
- name: Upload bundles to GitHub Release
|
||||||
name: Deploy to GitHub Pages
|
if: github.event_name == 'release'
|
||||||
runs-on: ubuntu-latest
|
uses: softprops/action-gh-release@v3
|
||||||
needs: flatter-arm64
|
with:
|
||||||
permissions:
|
files: release-bundles/*.flatpak
|
||||||
pages: write
|
tag_name: ${{ github.event.release.tag_name }}
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
|
|||||||
+117
-112
@@ -23,46 +23,32 @@ jobs:
|
|||||||
id: tag
|
id: tag
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INPUT_TAG="${{ github.event.inputs.tag || '' }}"
|
INPUT_TAG="${{ github.event.inputs.tag || '' }}"
|
||||||
if [ -n "$INPUT_TAG" ]; then
|
|
||||||
echo "Using input tag: $INPUT_TAG"
|
|
||||||
echo "tag=$INPUT_TAG" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
EVENT_TAG="${{ github.event.release.tag_name || '' }}"
|
EVENT_TAG="${{ github.event.release.tag_name || '' }}"
|
||||||
if [ -n "$EVENT_TAG" ]; then
|
|
||||||
echo "Using event tag: $EVENT_TAG"
|
if [ -n "$INPUT_TAG" ]; then
|
||||||
|
echo "tag=$INPUT_TAG" >> $GITHUB_OUTPUT
|
||||||
|
elif [ -n "$EVENT_TAG" ]; then
|
||||||
echo "tag=$EVENT_TAG" >> $GITHUB_OUTPUT
|
echo "tag=$EVENT_TAG" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
elif [[ "${GITHUB_REF:-}" =~ ^refs/tags/(.+)$ ]]; then
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${GITHUB_REF:-}" =~ ^refs/tags/(.+)$ ]]; then
|
|
||||||
echo "Using GITHUB_REF tag: ${BASH_REMATCH[1]}"
|
|
||||||
echo "tag=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
|
echo "tag=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
else
|
||||||
fi
|
|
||||||
|
|
||||||
echo "No tag in input/event/GITHUB_REF — querying latest release via API..."
|
|
||||||
LATEST_JSON=$(curl -sSf -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github+json" "https://api.github.com/repos/${{ github.repository }}/releases/latest" || true)
|
LATEST_JSON=$(curl -sSf -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github+json" "https://api.github.com/repos/${{ github.repository }}/releases/latest" || true)
|
||||||
TAG_NAME=$(echo "$LATEST_JSON" | jq -r '.tag_name // empty')
|
TAG_NAME=$(echo "$LATEST_JSON" | jq -r '.tag_name // empty')
|
||||||
if [ -n "$TAG_NAME" ]; then
|
if [ -n "$TAG_NAME" ]; then
|
||||||
echo "Found latest release tag: $TAG_NAME"
|
|
||||||
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
else
|
||||||
fi
|
|
||||||
|
|
||||||
echo "No tag found"
|
|
||||||
echo "tag=" >> $GITHUB_OUTPUT
|
echo "tag=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Fail if no tag determined
|
- name: Fail if no tag determined
|
||||||
if: ${{ steps.tag.outputs.tag == '' }}
|
if: ${{ steps.tag.outputs.tag == '' }}
|
||||||
run: |
|
run: |
|
||||||
echo "ERROR: No tag determined. Provide a tag when running manually or ensure a release exists."
|
echo "ERROR: No tag determined."
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
linux-release:
|
build-release:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -75,7 +61,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install apt deps (jq/zip + dev-libs)
|
- name: Install apt deps
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
@@ -84,10 +70,11 @@ jobs:
|
|||||||
libclang-dev \
|
libclang-dev \
|
||||||
libasound2-dev \
|
libasound2-dev \
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
|
libssl-dev \
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|
||||||
- name: Checkout code at tag
|
- name: Checkout code at tag
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.tag }}
|
ref: ${{ needs.prepare.outputs.tag }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -95,10 +82,13 @@ jobs:
|
|||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.96.0
|
toolchain: stable
|
||||||
|
cache: false
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: "v3-${{ matrix.runner }}"
|
||||||
|
|
||||||
- name: Extract all binary names
|
- name: Extract all binary names
|
||||||
id: cargo-meta
|
id: cargo-meta
|
||||||
@@ -113,13 +103,12 @@ jobs:
|
|||||||
- name: Build all release binaries
|
- name: Build all release binaries
|
||||||
run: cargo build --release --locked
|
run: cargo build --release --locked
|
||||||
|
|
||||||
- name: Package all binaries into one archive
|
- name: Package ZIP archive
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TAG="${{ needs.prepare.outputs.tag }}"
|
TAG="${{ needs.prepare.outputs.tag }}"
|
||||||
ARCHIVE_NAME="pwsp-${TAG}-linux-${{ matrix.arch }}.zip"
|
ARCHIVE_NAME="pwsp-${TAG}-linux-${{ matrix.arch }}.zip"
|
||||||
echo "Creating archive: $ARCHIVE_NAME"
|
|
||||||
|
|
||||||
FILES=()
|
FILES=()
|
||||||
while IFS= read -r BIN; do
|
while IFS= read -r BIN; do
|
||||||
@@ -127,71 +116,18 @@ jobs:
|
|||||||
FILES+=("target/release/$BIN")
|
FILES+=("target/release/$BIN")
|
||||||
done <<< "${{ steps.cargo-meta.outputs.bin_names }}"
|
done <<< "${{ steps.cargo-meta.outputs.bin_names }}"
|
||||||
|
|
||||||
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
||||||
echo "Error: no binaries were discovered via cargo metadata." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for f in "${FILES[@]}"; do
|
|
||||||
if [ ! -f "$f" ]; then
|
|
||||||
echo "Error: expected binary not found: $f" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Will add: $f"
|
|
||||||
done
|
|
||||||
|
|
||||||
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
|
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
|
||||||
|
|
||||||
- name: Upload zip archive
|
- name: Upload ZIP artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: zip-archive-${{ matrix.arch }}
|
name: zip-archive-${{ matrix.arch }}
|
||||||
path: pwsp-*.zip
|
path: pwsp-*.zip
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
deb-release:
|
|
||||||
needs: prepare
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- arch: x64
|
|
||||||
runner: ubuntu-latest
|
|
||||||
- arch: arm64
|
|
||||||
runner: ubuntu-24.04-arm
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ${{ matrix.runner }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install apt deps (dev-libs)
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y \
|
|
||||||
libpipewire-0.3-dev \
|
|
||||||
libclang-dev \
|
|
||||||
libasound2-dev \
|
|
||||||
libdbus-1-dev \
|
|
||||||
pkg-config
|
|
||||||
|
|
||||||
- name: Checkout code at tag
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ needs.prepare.outputs.tag }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: 1.96.0
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Build all release binaries
|
|
||||||
run: cargo build --release --locked
|
|
||||||
|
|
||||||
- name: Cache cargo-deb
|
- name: Cache cargo-deb
|
||||||
id: cache-cargo-deb
|
id: cache-cargo-deb
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin/cargo-deb
|
path: ~/.cargo/bin/cargo-deb
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-cargo-deb-v1
|
key: ${{ runner.os }}-${{ runner.arch }}-cargo-deb-v1
|
||||||
@@ -200,50 +136,32 @@ jobs:
|
|||||||
if: steps.cache-cargo-deb.outputs.cache-hit != 'true'
|
if: steps.cache-cargo-deb.outputs.cache-hit != 'true'
|
||||||
run: cargo install --locked cargo-deb
|
run: cargo install --locked cargo-deb
|
||||||
|
|
||||||
- name: Create .deb package (release binaries)
|
- name: Create .deb package
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export PATH="$HOME/.cargo/bin:$PATH"
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
cargo-deb -p pwsp-gui --no-build
|
cargo-deb -p pwsp-gui --no-build
|
||||||
|
|
||||||
- name: Upload deb package
|
- name: Upload DEB artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: deb-package-${{ matrix.arch }}
|
name: deb-package-${{ matrix.arch }}
|
||||||
path: target/debian/*.deb
|
path: target/debian/*.deb
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
needs: [prepare, linux-release, deb-release]
|
needs: [prepare, build-release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download zip archive (x64)
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: zip-archive-x64
|
|
||||||
path: ./dist
|
|
||||||
|
|
||||||
- name: Download zip archive (arm64)
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: zip-archive-arm64
|
|
||||||
path: ./dist
|
|
||||||
|
|
||||||
- name: Download deb package (x64)
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: deb-package-x64
|
|
||||||
path: ./dist
|
|
||||||
|
|
||||||
- name: Download deb package (arm64)
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: deb-package-arm64
|
|
||||||
path: ./dist
|
path: ./dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Upload artifacts to Release
|
- name: Upload artifacts to Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag_name: ${{ needs.prepare.outputs.tag }}
|
tag_name: ${{ needs.prepare.outputs.tag }}
|
||||||
@@ -251,6 +169,10 @@ jobs:
|
|||||||
./dist/pwsp-*.zip
|
./dist/pwsp-*.zip
|
||||||
./dist/*.deb
|
./dist/*.deb
|
||||||
|
|
||||||
|
trigger-copr:
|
||||||
|
needs: prepare
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
- name: Install copr-cli
|
- name: Install copr-cli
|
||||||
run: pip install copr-cli
|
run: pip install copr-cli
|
||||||
|
|
||||||
@@ -265,3 +187,86 @@ jobs:
|
|||||||
--commit ${{ needs.prepare.outputs.tag }} \
|
--commit ${{ needs.prepare.outputs.tag }} \
|
||||||
--spec packages/rpm/pwsp.spec \
|
--spec packages/rpm/pwsp.spec \
|
||||||
arabianq/pwsp
|
arabianq/pwsp
|
||||||
|
|
||||||
|
build-apt-repo:
|
||||||
|
needs: [prepare, build-release]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency: gh-pages-deploy
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Setup GPG
|
||||||
|
id: gpg
|
||||||
|
uses: crazy-max/ghaction-import-gpg@v7
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
|
|
||||||
|
- name: Download DEB artifacts
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
pattern: deb-package-*
|
||||||
|
path: debs
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Checkout existing gh-pages
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: gh-pages
|
||||||
|
path: gh-pages-branch
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Generate APT Repository
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y reprepro
|
||||||
|
|
||||||
|
mkdir -p apt-repo
|
||||||
|
|
||||||
|
# Restore existing repository first so we don't lose old packages
|
||||||
|
if [ -d "gh-pages-branch/apt" ]; then
|
||||||
|
cp -r gh-pages-branch/apt/* apt-repo/ || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always rewrite the config and public key to ensure they are up-to-date
|
||||||
|
mkdir -p apt-repo/conf
|
||||||
|
|
||||||
|
cat <<EOF > apt-repo/conf/distributions
|
||||||
|
Origin: arabianq
|
||||||
|
Label: PipeWire Soundpad
|
||||||
|
Codename: stable
|
||||||
|
Architectures: amd64 arm64
|
||||||
|
Components: main
|
||||||
|
Description: APT Repository for PWSP
|
||||||
|
SignWith: ${{ steps.gpg.outputs.fingerprint }}
|
||||||
|
|
||||||
|
Origin: arabianq
|
||||||
|
Label: PipeWire Soundpad
|
||||||
|
Codename: nightly
|
||||||
|
Architectures: amd64 arm64
|
||||||
|
Components: main
|
||||||
|
Description: Nightly APT Repository for PWSP
|
||||||
|
SignWith: ${{ steps.gpg.outputs.fingerprint }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
gpg --armor --export ${{ steps.gpg.outputs.fingerprint }} > apt-repo/pubkey.gpg
|
||||||
|
|
||||||
|
cd apt-repo
|
||||||
|
|
||||||
|
for deb in ../debs/*.deb; do
|
||||||
|
if [ -f "$deb" ]; then
|
||||||
|
echo "Adding $deb to APT repository..."
|
||||||
|
reprepro includedeb stable "$deb"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Deploy APT Repo to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./apt-repo
|
||||||
|
destination_dir: apt
|
||||||
|
keep_files: true
|
||||||
|
|||||||
Generated
+510
-666
File diff suppressed because it is too large
Load Diff
+9
-4
@@ -8,7 +8,7 @@ members = [
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.12.2"
|
version = "1.13.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["arabian"]
|
authors = ["arabian"]
|
||||||
homepage = "https://pwsp.arabianq.ru"
|
homepage = "https://pwsp.arabianq.ru"
|
||||||
@@ -52,7 +52,7 @@ rustix = { version = "1.1.4", features = ["process"] }
|
|||||||
rust-i18n = "4.0.0"
|
rust-i18n = "4.0.0"
|
||||||
sys-locale = "0.3.2"
|
sys-locale = "0.3.2"
|
||||||
|
|
||||||
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "4746803c2e5f67004264ac946708d47a9faf510c", default-features = false, features = [
|
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "610955cf524467690cc9abe1a6277e8bffcadb71", default-features = false, features = [
|
||||||
"symphonia-all",
|
"symphonia-all",
|
||||||
"symphonia-libopus",
|
"symphonia-libopus",
|
||||||
"playback",
|
"playback",
|
||||||
@@ -73,13 +73,18 @@ egui_extras = "0.35.0"
|
|||||||
egui_material_icons = "0.7.0"
|
egui_material_icons = "0.7.0"
|
||||||
egui_dnd = "0.16.0"
|
egui_dnd = "0.16.0"
|
||||||
|
|
||||||
reqwest = "0.13.4"
|
reqwest = { version = "0.13.4", default-features = false, features = ["native-tls"] }
|
||||||
percent-encoding = "2.3.2"
|
percent-encoding = "2.3.2"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
debug = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
|
|||||||
@@ -1,118 +1,149 @@
|
|||||||
<div align="center">
|
# PipeWire Soundpad (PWSP) 🎵
|
||||||
<h1>🎵 PipeWire Soundpad (PWSP)</h1>
|
|
||||||
<p><b>A simple, modern, and powerful soundboard for Linux, written in Rust.</b></p>
|
|
||||||
<img src="pwsp-gui/assets/screenshot.png" alt="PWSP Screenshot" width="700"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## 🌟 Overview
|

|
||||||
**PipeWire Soundpad (PWSP)** is a graphical soundboard application that routes audio directly to your virtual microphone using **PipeWire**. It provides an intuitive interface for managing your audio collection, making it an ideal tool for gamers, streamers, and anyone looking to inject sound effects into voice chats on platforms like Discord, Zoom, or TeamSpeak.
|
|
||||||
|
|
||||||
## ✨ Key Features
|
[🇷🇺 Читать на русском](README.ru.md) | [🇺🇸 Read in English](README.md)
|
||||||
* **🎙️ Virtual Microphone Output:** Seamlessly mixes your microphone input with sound effects by automatically managing PipeWire virtual devices.
|
|
||||||
* **🎵 Multi-Format Support:** Plays popular audio formats including `mp3`, `wav`, `ogg`, `flac`, `mp4`, and `aac`.
|
|
||||||
* **⚡ Global Hotkeys:** Trigger sounds instantly from anywhere, even when the app is running in the background.
|
|
||||||
* **📂 Smart Collection Management:** Drag-and-drop folders, quick search, and collapsible tracks to keep your library organized.
|
|
||||||
* **🎛️ Advanced Playback Controls:** Individual volume sliders, play/pause, position scrubbing, and concurrent multi-track playback.
|
|
||||||
* **🔌 Plug & Play:** Automatically detects when an input device is connected or disconnected and handles linking/unlinking on the fly.
|
|
||||||
* **🖥️ Modern GUI:** Clean, responsive, and lightweight interface powered by [egui](https://egui.rs/).
|
|
||||||
|
|
||||||
## ⚙️ Architecture
|
[](https://github.com/arabianq/pipewire-soundpad/actions)
|
||||||
PWSP is built with a client-server model to ensure stability and separation of concerns:
|
[](https://opensource.org/licenses/MIT)
|
||||||
* **`pwsp-daemon`**: The background engine. It runs silently, managing PipeWire virtual devices, audio routing, and playback.
|
[](https://github.com/arabianq/pipewire-soundpad/releases/latest)
|
||||||
* **`pwsp-gui`**: The graphical interface. Communicates with the daemon via a Unix socket to control playback and settings.
|
[](https://pipewire.org/)
|
||||||
* **`pwsp-cli`**: The command-line tool. Perfect for scripting, hotkey binding, or quick terminal-based control.
|
|
||||||
|
**PipeWire Soundpad (PWSP)** is a modern, low-latency application that lets you play audio files directly through your microphone. Designed specifically for Linux, it leverages the power of PipeWire to achieve native integration without the need for Pulseaudio bridges or complex virtual sinks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- **Native PipeWire Integration:** Direct communication with the PipeWire API for the lowest possible latency.
|
||||||
|
- **Modular Architecture:** Consists of a background `daemon`, a command-line interface (`cli`), and a graphical user interface (`gui`).
|
||||||
|
- **Modern GUI:** Built with `egui`, supporting both Wayland and X11 seamlessly.
|
||||||
|
- **Global Hotkeys:** Powered by `evdev`, allowing you to play sounds from anywhere.
|
||||||
|
- **Broad Audio Support:** Powered by `rodio` and `symphonia` to support a wide range of audio formats.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Installation
|
## 🚀 Installation
|
||||||
|
|
||||||
|
We provide multiple ways to install PWSP, including stable releases and rolling "nightly" builds directly from the `main` branch.
|
||||||
|
|
||||||
### 📦 Flatpak (Recommended)
|
### 📦 Flatpak (Recommended)
|
||||||
Install PWSP via Flatpak from our custom repository:
|
|
||||||
|
Add our official OSTree repository and install the application:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flatpak remote-add --user --if-not-exists pwsp-repo https://arabianq.github.io/pipewire-soundpad/index.flatpakrepo
|
# Add the repository
|
||||||
|
flatpak remote-add --if-not-exists pwsp https://arabianq.github.io/pipewire-soundpad/pwsp.flatpakrepo
|
||||||
|
|
||||||
# Install stable version
|
# Install the Stable version
|
||||||
flatpak install --user arabianq-repo ru.arabianq.pwsp//stable
|
flatpak install pwsp ru.arabianq.pwsp//stable
|
||||||
|
|
||||||
# Or install the nightly version (latest commit)
|
# OR Install the Nightly version (rolling updates)
|
||||||
flatpak install --user arabianq-repo ru.arabianq.pwsp//nightly
|
flatpak install pwsp ru.arabianq.pwsp//nightly
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🐧 Linux Packages
|
### 🟠 Debian / Ubuntu (APT Repository)
|
||||||
**Fedora (and derivatives):**
|
|
||||||
|
We maintain an official APT repository for seamless updates via `apt`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Download the public GPG key
|
||||||
|
wget -O- https://arabianq.github.io/pipewire-soundpad/apt/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/pwsp.gpg
|
||||||
|
|
||||||
|
# 2. Add the repository (Choose STABLE or NIGHTLY)
|
||||||
|
# For Stable:
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/pwsp.gpg] https://arabianq.github.io/pipewire-soundpad/apt/ stable main" | sudo tee /etc/apt/sources.list.d/pwsp.list
|
||||||
|
|
||||||
|
# For Nightly:
|
||||||
|
# echo "deb [signed-by=/etc/apt/keyrings/pwsp.gpg] https://arabianq.github.io/pipewire-soundpad/apt/ nightly main" | sudo tee /etc/apt/sources.list.d/pwsp.list
|
||||||
|
|
||||||
|
# 3. Update and install
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install pwsp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🐧 Fedora / RHEL (COPR)
|
||||||
|
|
||||||
|
Available via the Fedora COPR repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dnf copr enable arabianq/pwsp
|
sudo dnf copr enable arabianq/pwsp
|
||||||
sudo dnf install pwsp
|
sudo dnf install pwsp
|
||||||
```
|
```
|
||||||
|
|
||||||
**Arch Linux (AUR):**
|
### ⚙️ Manual / Standalone
|
||||||
|
|
||||||
|
You can manually download `.deb` packages or standalone `.zip` binaries from the [Releases page](https://github.com/arabianq/pipewire-soundpad/releases).
|
||||||
|
|
||||||
|
### 🦀 Build from Source
|
||||||
|
|
||||||
|
Make sure you have Rust, Cargo, and the required dependencies (`libpipewire-0.3-dev`, `libclang-dev`, `libasound2-dev`, `libdbus-1-dev`) installed.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
paru -S pwsp-bin # or 'pwsp' to build from source
|
|
||||||
```
|
|
||||||
|
|
||||||
**Debian / Ubuntu:**
|
|
||||||
Download pre-built `.deb` packages or standalone binaries from the [Releases page](https://github.com/arabianq/pipewire-soundpad/releases).
|
|
||||||
|
|
||||||
### 🦀 Cargo / Source Build
|
|
||||||
```bash
|
|
||||||
cargo install pwsp
|
|
||||||
|
|
||||||
# OR clone and build manually:
|
|
||||||
git clone https://github.com/arabianq/pipewire-soundpad.git
|
git clone https://github.com/arabianq/pipewire-soundpad.git
|
||||||
cd pipewire-soundpad
|
cd pipewire-soundpad
|
||||||
cargo build --release
|
cargo build --release --locked
|
||||||
```
|
```
|
||||||
*(Note: Requires Rust toolchain and PipeWire running on your system).*
|
|
||||||
|
The binaries will be located in `target/release/`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎮 Usage
|
## 🎮 Usage
|
||||||
|
|
||||||
### 1. Start the Daemon
|
### 1. Start the Daemon
|
||||||
Before using the GUI or CLI, the daemon must be running in the background.
|
|
||||||
|
PWSP operates via a background daemon that handles the audio routing.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Recommended: Start and enable via systemd (starts on login)
|
# Run the daemon
|
||||||
systemctl --user enable --now pwsp-daemon
|
pwsp-daemon
|
||||||
|
|
||||||
# Manual start (if not using systemd):
|
|
||||||
pwsp-daemon &
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Using the GUI
|
_(Tip: If installed via package managers, a systemd user service is provided. You can enable it with `systemctl --user enable --now pwsp-daemon.service`)_
|
||||||
1. **Add Sounds:** Click the **"+"** button to add a directory containing your audio files.
|
|
||||||
2. **Select Mic:** Choose your physical microphone from the dropdown. PWSP will instantly create a virtual microphone combining your voice and the soundboard.
|
### 2. Launch the GUI
|
||||||
3. **Play:** Click any sound to play it, adjust its volume, or assign a hotkey for quick access.
|
|
||||||
|
Simply run the graphical interface to manage and play your sounds:
|
||||||
|
|
||||||
### 3. Using the CLI
|
|
||||||
Control the daemon directly from your terminal:
|
|
||||||
```bash
|
```bash
|
||||||
pwsp-cli action play /path/to/sound.mp3
|
pwsp-gui
|
||||||
pwsp-cli get volume
|
```
|
||||||
pwsp-cli set position 20
|
|
||||||
pwsp-cli --help # View all commands
|
### 3. Use the CLI
|
||||||
|
|
||||||
|
You can also interact with the daemon directly via the command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pwsp-cli play /path/to/sound.mp3
|
||||||
|
pwsp-cli stop
|
||||||
|
pwsp-cli status
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⌨️ Shortcuts & Controls
|
## 📚 Documentation & DeepWiki
|
||||||
|
|
||||||
| Action | Keyboard | Mouse |
|
For advanced configuration, troubleshooting, architecture details, and custom setups, please visit our official Wiki:
|
||||||
| :----------------------------------- | :--------------------- | :------------------- |
|
|
||||||
| **Play Track** (Stops others) | | `Left Click` |
|
[](https://deepwiki.com/arabianq/pipewire-soundpad)
|
||||||
| **Add Track** (Plays simultaneously) | | `Ctrl + Left Click` |
|
|
||||||
| **Replace Last Track** | | `Shift + Left Click` |
|
|
||||||
| **Pause / Resume** | `Space` | |
|
|
||||||
| **Stop All Tracks** | `Backspace` | |
|
|
||||||
| **Open / Close Settings** | `I` | |
|
|
||||||
| **Search** | `/` | |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
Contributions, issues, and feature requests are welcome! Feel free to check out the [issues page](https://github.com/arabianq/pipewire-soundpad/issues).
|
|
||||||
|
|
||||||
[](https://deepwiki.com/arabianq/pipewire-soundpad)
|
Contributions, issues, and feature requests are welcome!
|
||||||
|
|
||||||
## 📜 License
|
1. Fork the project.
|
||||||
This project is licensed under the [MIT License](LICENSE).
|
2. Create your feature branch (`git checkout -b feat/amazing-feature`).
|
||||||
|
3. Commit your changes (`git commit -m 'Add some amazing feature'`).
|
||||||
|
4. Push to the branch (`git push origin feat/amazing-feature`).
|
||||||
|
5. Open a Pull Request.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 License
|
||||||
|
|
||||||
|
Distributed under the MIT License. See `LICENSE` for more information.
|
||||||
|
|
||||||
|
_Built with ❤️ for the Linux community._
|
||||||
|
|||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# PipeWire Soundpad (PWSP) 🎵
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[🇷🇺 Читать на русском](README.ru.md) | [🇺🇸 Read in English](README.md)
|
||||||
|
|
||||||
|
[](https://github.com/arabianq/pipewire-soundpad/actions)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
[](https://github.com/arabianq/pipewire-soundpad/releases/latest)
|
||||||
|
[](https://pipewire.org/)
|
||||||
|
|
||||||
|
**PipeWire Soundpad (PWSP)** — это современное приложение с низкой задержкой, которое позволяет воспроизводить аудиофайлы прямо в ваш микрофон. Разработано специально для Linux с использованием мощи PipeWire для достижения нативной интеграции без использования мостов Pulseaudio или сложных виртуальных устройств.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Возможности
|
||||||
|
|
||||||
|
- **Нативная интеграция с PipeWire:** Прямое взаимодействие с PipeWire API для минимальной задержки.
|
||||||
|
- **Модульная архитектура:** Состоит из фонового демона (`daemon`), интерфейса командной строки (`cli`) и графического интерфейса (`gui`).
|
||||||
|
- **Современный GUI:** Построен на `egui`, плавно работает как на Wayland, так и на X11.
|
||||||
|
- **Глобальные горячие клавиши:** Работают через `evdev`, позволяя воспроизводить звуки из любого окна.
|
||||||
|
- **Широкая поддержка форматов:** Использует `rodio` и `symphonia` для поддержки большинства аудиоформатов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Установка
|
||||||
|
|
||||||
|
Мы предоставляем несколько способов установки PWSP, включая стабильные релизы и постоянно обновляемые "nightly" сборки напрямую из ветки `main`.
|
||||||
|
|
||||||
|
### 📦 Flatpak (Рекомендуется)
|
||||||
|
|
||||||
|
Добавьте наш официальный OSTree репозиторий и установите приложение:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Добавьте репозиторий
|
||||||
|
flatpak remote-add --if-not-exists pwsp https://arabianq.github.io/pipewire-soundpad/pwsp.flatpakrepo
|
||||||
|
|
||||||
|
# Установите стабильную (Stable) версию
|
||||||
|
flatpak install pwsp ru.arabianq.pwsp//stable
|
||||||
|
|
||||||
|
# ИЛИ установите Nightly версию (самые свежие обновления)
|
||||||
|
flatpak install pwsp ru.arabianq.pwsp//nightly
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🟠 Debian / Ubuntu (APT Репозиторий)
|
||||||
|
|
||||||
|
Мы поддерживаем официальный APT-репозиторий для бесшовных обновлений через `apt`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Скачайте публичный GPG ключ
|
||||||
|
wget -O- https://arabianq.github.io/pipewire-soundpad/apt/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/pwsp.gpg
|
||||||
|
|
||||||
|
# 2. Добавьте репозиторий (Выберите STABLE или NIGHTLY)
|
||||||
|
# Для Stable:
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/pwsp.gpg] https://arabianq.github.io/pipewire-soundpad/apt/ stable main" | sudo tee /etc/apt/sources.list.d/pwsp.list
|
||||||
|
|
||||||
|
# Для Nightly:
|
||||||
|
# echo "deb [signed-by=/etc/apt/keyrings/pwsp.gpg] https://arabianq.github.io/pipewire-soundpad/apt/ nightly main" | sudo tee /etc/apt/sources.list.d/pwsp.list
|
||||||
|
|
||||||
|
# 3. Обновите индексы и установите
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install pwsp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🐧 Fedora / RHEL (COPR)
|
||||||
|
|
||||||
|
Доступно через репозиторий Fedora COPR:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf copr enable arabianq/pwsp
|
||||||
|
sudo dnf install pwsp
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⚙️ Ручная установка
|
||||||
|
|
||||||
|
Вы можете вручную скачать пакеты `.deb` или готовые бинарники `.zip` на [странице релизов](https://github.com/arabianq/pipewire-soundpad/releases).
|
||||||
|
|
||||||
|
### 🦀 Сборка из исходников
|
||||||
|
|
||||||
|
Убедитесь, что у вас установлены Rust, Cargo и необходимые зависимости (`libpipewire-0.3-dev`, `libclang-dev`, `libasound2-dev`, `libdbus-1-dev`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/arabianq/pipewire-soundpad.git
|
||||||
|
cd pipewire-soundpad
|
||||||
|
cargo build --release --locked
|
||||||
|
```
|
||||||
|
|
||||||
|
Собранные бинарники будут находиться в папке `target/release/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 Использование
|
||||||
|
|
||||||
|
### 1. Запуск демона
|
||||||
|
|
||||||
|
PWSP работает через фоновый демон, который маршрутизирует аудио.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запуск демона
|
||||||
|
pwsp-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
_(Подсказка: Если вы установили программу через пакетный менеджер, она включает пользовательский systemd-сервис. Вы можете включить его командой: `systemctl --user enable --now pwsp-daemon.service`)_
|
||||||
|
|
||||||
|
### 2. Запуск GUI
|
||||||
|
|
||||||
|
Просто запустите графический интерфейс для управления и воспроизведения звуков:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pwsp-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Использование CLI
|
||||||
|
|
||||||
|
Вы также можете взаимодействовать с демоном напрямую через командную строку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pwsp-cli play /path/to/sound.mp3
|
||||||
|
pwsp-cli stop
|
||||||
|
pwsp-cli status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Документация и DeepWiki
|
||||||
|
|
||||||
|
Для детальной настройки, решения проблем, описания архитектуры и кастомных конфигураций, пожалуйста, посетите нашу официальную Wiki:
|
||||||
|
|
||||||
|
[](https://deepwiki.com/arabianq/pipewire-soundpad)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Вклад в проект (Contributing)
|
||||||
|
|
||||||
|
Будем рады вашей помощи, баг-репортам и идеям!
|
||||||
|
|
||||||
|
1. Сделайте Fork проекта.
|
||||||
|
2. Создайте свою ветку (`git checkout -b feat/amazing-feature`).
|
||||||
|
3. Закоммитьте изменения (`git commit -m 'Add some amazing feature'`).
|
||||||
|
4. Запушьте ветку (`git push origin feat/amazing-feature`).
|
||||||
|
5. Откройте Pull Request.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
Распространяется под лицензией MIT. Подробнее см. в файле `LICENSE`.
|
||||||
|
|
||||||
|
_Сделано с ❤️ для сообщества Linux._
|
||||||
@@ -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.12.2
|
pkgver = 1.13.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/arabianq/pipewire-soundpad
|
url = https://github.com/arabianq/pipewire-soundpad
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -10,11 +10,11 @@ pkgbase = pwsp-bin
|
|||||||
depends = alsa-lib
|
depends = alsa-lib
|
||||||
provides = pwsp
|
provides = pwsp
|
||||||
conflicts = pwsp
|
conflicts = pwsp
|
||||||
source = pipewire-soundpad-1.12.2.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.2.tar.gz
|
source = pipewire-soundpad-1.13.0.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.13.0.tar.gz
|
||||||
sha256sums = SKIP
|
sha256sums = SKIP
|
||||||
source_x86_64 = pwsp-1.12.2-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.2/pwsp-v1.12.2-linux-x64.zip
|
source_x86_64 = pwsp-1.13.0-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.13.0/pwsp-v1.13.0-linux-x64.zip
|
||||||
sha256sums_x86_64 = SKIP
|
sha256sums_x86_64 = SKIP
|
||||||
source_aarch64 = pwsp-1.12.2-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.2/pwsp-v1.12.2-linux-arm64.zip
|
source_aarch64 = pwsp-1.13.0-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.13.0/pwsp-v1.13.0-linux-arm64.zip
|
||||||
sha256sums_aarch64 = SKIP
|
sha256sums_aarch64 = SKIP
|
||||||
|
|
||||||
pkgname = pwsp-bin
|
pkgname = pwsp-bin
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# 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.12.2
|
pkgver=1.13.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
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' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
url="https://github.com/arabianq/pipewire-soundpad"
|
url="https://github.com/arabianq/pipewire-soundpad"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
depends=('pipewire' 'alsa-lib')
|
depends=('pipewire' 'alsa-lib' 'openssl')
|
||||||
provides=('pwsp')
|
provides=('pwsp')
|
||||||
conflicts=('pwsp')
|
conflicts=('pwsp')
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ package() {
|
|||||||
|
|
||||||
install -Dm644 "$_srcsrc/pwsp-gui/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
|
install -Dm644 "$_srcsrc/pwsp-gui/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
|
||||||
install -Dm644 "$_srcsrc/pwsp-gui/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
|
install -Dm644 "$_srcsrc/pwsp-gui/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
|
||||||
install -Dm644 "$_srcsrc/pwsp-gui/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
|
install -Dm644 "$_srcsrc/pwsp-daemon/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
|
||||||
|
|
||||||
install -Dm644 "$_srcsrc/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
install -Dm644 "$_srcsrc/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.12.2
|
pkgver = 1.13.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/arabianq/pipewire-soundpad
|
url = https://github.com/arabianq/pipewire-soundpad
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -12,7 +12,7 @@ pkgbase = pwsp
|
|||||||
makedepends = cmake
|
makedepends = cmake
|
||||||
makedepends = pipewire
|
makedepends = pipewire
|
||||||
makedepends = alsa-lib
|
makedepends = alsa-lib
|
||||||
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.2.tar.gz
|
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.13.0.tar.gz
|
||||||
sha256sums = SKIP
|
sha256sums = SKIP
|
||||||
|
|
||||||
pkgname = pwsp
|
pkgname = pwsp
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
|
||||||
pkgsubn=pwsp
|
pkgsubn=pwsp
|
||||||
pkgname=pwsp
|
pkgname=pwsp
|
||||||
pkgver=1.12.2
|
pkgver=1.13.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lets you play audio files through your microphone"
|
pkgdesc="Lets you play audio files through your microphone"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
url="https://github.com/arabianq/pipewire-soundpad"
|
url="https://github.com/arabianq/pipewire-soundpad"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
|
depends=('pipewire' 'alsa-lib' 'openssl')
|
||||||
makedepends=(clang rust cargo cmake pipewire alsa-lib)
|
makedepends=(clang rust cargo cmake pipewire alsa-lib)
|
||||||
source=("$url/archive/refs/tags/v$pkgver.tar.gz")
|
source=("$url/archive/refs/tags/v$pkgver.tar.gz")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
@@ -43,5 +44,5 @@ package() {
|
|||||||
install -Dm644 "pwsp-gui/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
|
install -Dm644 "pwsp-gui/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
|
||||||
install -Dm644 "pwsp-gui/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
|
install -Dm644 "pwsp-gui/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
|
||||||
|
|
||||||
install -Dm644 "pwsp-gui/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
|
install -Dm644 "pwsp-daemon/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
|
||||||
}
|
}
|
||||||
|
|||||||
+670
-839
File diff suppressed because one or more lines are too long
@@ -25,7 +25,7 @@
|
|||||||
<name>arabian</name>
|
<name>arabian</name>
|
||||||
</developer>
|
</developer>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="1.12.2" date="2026-07-02" />
|
<release version="1.13.0" date="2026-07-24" />
|
||||||
</releases>
|
</releases>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
</component>
|
</component>
|
||||||
@@ -30,6 +30,7 @@ modules:
|
|||||||
- name: pwsp
|
- name: pwsp
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
|
- if [ -w /run/ccache ]; then mkdir -p /run/ccache/cargo-target && rm -rf target && ln -s /run/ccache/cargo-target target; fi
|
||||||
- export CARGO_HOME=$PWD/cargo && cargo build --release --offline
|
- 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
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ BuildRequires: pipewire-devel
|
|||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
BuildRequires: alsa-devel
|
BuildRequires: alsa-devel
|
||||||
BuildRequires: dbus-1-devel
|
BuildRequires: dbus-1-devel
|
||||||
|
BuildRequires: libopenssl-devel
|
||||||
%else
|
%else
|
||||||
BuildRequires: alsa-lib-devel
|
BuildRequires: alsa-lib-devel
|
||||||
BuildRequires: dbus-devel
|
BuildRequires: dbus-devel
|
||||||
|
BuildRequires: openssl-devel
|
||||||
%endif
|
%endif
|
||||||
BuildRequires: clang-devel
|
BuildRequires: clang-devel
|
||||||
BuildRequires: cmake
|
BuildRequires: cmake
|
||||||
@@ -66,7 +68,16 @@ install -Dm755 target/release/pwsp-gui %{buildroot}%{_bindir}/pwsp-gui
|
|||||||
install -Dm644 pwsp-gui/assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop
|
install -Dm644 pwsp-gui/assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop
|
||||||
install -Dm644 pwsp-gui/assets/icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/pwsp.png
|
install -Dm644 pwsp-gui/assets/icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/pwsp.png
|
||||||
|
|
||||||
install -Dm644 pwsp-gui/assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service
|
install -Dm644 pwsp-daemon/assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service
|
||||||
|
|
||||||
|
%post
|
||||||
|
%systemd_user_post pwsp-daemon.service
|
||||||
|
|
||||||
|
%preun
|
||||||
|
%systemd_user_preun pwsp-daemon.service
|
||||||
|
|
||||||
|
%postun
|
||||||
|
%systemd_user_postun_with_restart pwsp-daemon.service
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
|
|||||||
+13
-2
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
|
|
||||||
Name: pwsp
|
Name: pwsp
|
||||||
Version: 1.12.2
|
Version: 1.13.0
|
||||||
Release: %autorelease
|
Release: %autorelease
|
||||||
Summary: Lets you play audio files through your microphone
|
Summary: Lets you play audio files through your microphone
|
||||||
|
|
||||||
@@ -28,9 +28,11 @@ BuildRequires: pipewire-devel
|
|||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
BuildRequires: alsa-devel
|
BuildRequires: alsa-devel
|
||||||
BuildRequires: dbus-1-devel
|
BuildRequires: dbus-1-devel
|
||||||
|
BuildRequires: libopenssl-devel
|
||||||
%else
|
%else
|
||||||
BuildRequires: alsa-lib-devel
|
BuildRequires: alsa-lib-devel
|
||||||
BuildRequires: dbus-devel
|
BuildRequires: dbus-devel
|
||||||
|
BuildRequires: openssl-devel
|
||||||
%endif
|
%endif
|
||||||
BuildRequires: clang-devel
|
BuildRequires: clang-devel
|
||||||
BuildRequires: cmake
|
BuildRequires: cmake
|
||||||
@@ -66,7 +68,16 @@ install -Dm755 target/release/pwsp-gui %{buildroot}%{_bindir}/pwsp-gui
|
|||||||
install -Dm644 pwsp-gui/assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop
|
install -Dm644 pwsp-gui/assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop
|
||||||
install -Dm644 pwsp-gui/assets/icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/pwsp.png
|
install -Dm644 pwsp-gui/assets/icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/pwsp.png
|
||||||
|
|
||||||
install -Dm644 pwsp-gui/assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service
|
install -Dm644 pwsp-daemon/assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service
|
||||||
|
|
||||||
|
%post
|
||||||
|
%systemd_user_post pwsp-daemon.service
|
||||||
|
|
||||||
|
%preun
|
||||||
|
%systemd_user_preun pwsp-daemon.service
|
||||||
|
|
||||||
|
%postun
|
||||||
|
%systemd_user_postun_with_restart pwsp-daemon.service
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ enum Actions {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Daemon configuration
|
||||||
|
SaveDaemonConfig,
|
||||||
/// Play a sound by hotkey slot name
|
/// Play a sound by hotkey slot name
|
||||||
PlayHotkey { slot: String },
|
PlayHotkey { slot: String },
|
||||||
/// Remove the hotkey slot
|
/// Remove the hotkey slot
|
||||||
@@ -86,6 +88,8 @@ enum GetCommands {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Volume multiplier for all tracks
|
||||||
|
VolumeMultiplier,
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position {
|
Position {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
@@ -106,6 +110,8 @@ enum GetCommands {
|
|||||||
Inputs,
|
Inputs,
|
||||||
/// Version of the daemon
|
/// Version of the daemon
|
||||||
DaemonVersion,
|
DaemonVersion,
|
||||||
|
/// Daemon configuration
|
||||||
|
DaemonConfig,
|
||||||
/// Full player state
|
/// Full player state
|
||||||
FullState,
|
FullState,
|
||||||
/// All hotkey slots
|
/// All hotkey slots
|
||||||
@@ -120,6 +126,8 @@ enum SetCommands {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
id: Option<u32>,
|
id: Option<u32>,
|
||||||
},
|
},
|
||||||
|
/// Volume multiplier for all tracks
|
||||||
|
VolumeMultiplier { volume: f32 },
|
||||||
/// Playback position (in seconds)
|
/// Playback position (in seconds)
|
||||||
Position {
|
Position {
|
||||||
position: f32,
|
position: f32,
|
||||||
@@ -165,6 +173,7 @@ async fn main() -> Result<()> {
|
|||||||
concurrent,
|
concurrent,
|
||||||
} => Request::play(&file_path.to_string_lossy(), concurrent),
|
} => Request::play(&file_path.to_string_lossy(), concurrent),
|
||||||
Actions::ToggleLoop { id } => Request::toggle_loop(id),
|
Actions::ToggleLoop { id } => Request::toggle_loop(id),
|
||||||
|
Actions::SaveDaemonConfig => Request::save_daemon_config(),
|
||||||
Actions::PlayHotkey { slot } => Request::play_hotkey(&slot),
|
Actions::PlayHotkey { slot } => Request::play_hotkey(&slot),
|
||||||
Actions::ClearHotkey { slot } => Request::clear_hotkey(&slot),
|
Actions::ClearHotkey { slot } => Request::clear_hotkey(&slot),
|
||||||
Actions::ClearHotkeyKey { slot } => Request::clear_hotkey_key(&slot),
|
Actions::ClearHotkeyKey { slot } => Request::clear_hotkey_key(&slot),
|
||||||
@@ -172,6 +181,7 @@ async fn main() -> Result<()> {
|
|||||||
Commands::Get { parameter } => match parameter {
|
Commands::Get { parameter } => match parameter {
|
||||||
GetCommands::IsPaused => Request::get_is_paused(),
|
GetCommands::IsPaused => Request::get_is_paused(),
|
||||||
GetCommands::Volume { id } => Request::get_volume(id),
|
GetCommands::Volume { id } => Request::get_volume(id),
|
||||||
|
GetCommands::VolumeMultiplier => Request::get_volume_multiplier(),
|
||||||
GetCommands::Position { id } => Request::get_position(id),
|
GetCommands::Position { id } => Request::get_position(id),
|
||||||
GetCommands::Duration { id } => Request::get_duration(id),
|
GetCommands::Duration { id } => Request::get_duration(id),
|
||||||
GetCommands::State => Request::get_state(),
|
GetCommands::State => Request::get_state(),
|
||||||
@@ -179,11 +189,13 @@ async fn main() -> Result<()> {
|
|||||||
GetCommands::Input => Request::get_input(),
|
GetCommands::Input => Request::get_input(),
|
||||||
GetCommands::Inputs => Request::get_inputs(),
|
GetCommands::Inputs => Request::get_inputs(),
|
||||||
GetCommands::DaemonVersion => Request::get_daemon_version(),
|
GetCommands::DaemonVersion => Request::get_daemon_version(),
|
||||||
|
GetCommands::DaemonConfig => Request::get_daemon_config(),
|
||||||
GetCommands::FullState => Request::get_full_state(),
|
GetCommands::FullState => Request::get_full_state(),
|
||||||
GetCommands::Hotkeys => Request::get_hotkeys(),
|
GetCommands::Hotkeys => Request::get_hotkeys(),
|
||||||
},
|
},
|
||||||
Commands::Set { parameter } => match parameter {
|
Commands::Set { parameter } => match parameter {
|
||||||
SetCommands::Volume { volume, id } => Request::set_volume(volume, id),
|
SetCommands::Volume { volume, id } => Request::set_volume(volume, id),
|
||||||
|
SetCommands::VolumeMultiplier { volume } => Request::set_volume_multiplier(volume),
|
||||||
SetCommands::Position { position, id } => Request::seek(position, id),
|
SetCommands::Position { position, id } => Request::seek(position, id),
|
||||||
SetCommands::Input { name } => Request::set_input(&name),
|
SetCommands::Input { name } => Request::set_input(&name),
|
||||||
SetCommands::Loop { enabled, id } => Request::set_loop(&enabled, id),
|
SetCommands::Loop { enabled, id } => Request::set_loop(&enabled, id),
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Pipewire Soundpad Daemon
|
||||||
|
Requires=pipewire.service
|
||||||
|
After=pipewire.service
|
||||||
|
PartOf=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/bin/pwsp-daemon
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
RuntimeDirectory=pwsp
|
||||||
|
SyslogIdentifier=pwsp-daemon
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
+5
-1
@@ -35,6 +35,10 @@ reqwest.workspace = true
|
|||||||
percent-encoding.workspace = true
|
percent-encoding.workspace = true
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
|
name = "pwsp"
|
||||||
|
section = "sound"
|
||||||
|
priority = "optional"
|
||||||
|
maintainer-scripts = "maintainer-scripts"
|
||||||
assets = [
|
assets = [
|
||||||
[
|
[
|
||||||
"target/release/pwsp-daemon",
|
"target/release/pwsp-daemon",
|
||||||
@@ -62,7 +66,7 @@ assets = [
|
|||||||
"644",
|
"644",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"assets/pwsp-daemon.service",
|
"../pwsp-daemon/assets/pwsp-daemon.service",
|
||||||
"usr/lib/systemd/user/pwsp-daemon.service",
|
"usr/lib/systemd/user/pwsp-daemon.service",
|
||||||
"644",
|
"644",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Pipewire Soundpad Daemon
|
|
||||||
After=pipewire.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/pwsp-daemon
|
|
||||||
Restart=no
|
|
||||||
RuntimeDirectory=pwsp
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
@@ -217,6 +217,28 @@ kz = "Әрқашан дыбыс деңгейін есте сақтау"
|
|||||||
he = "זכור תמיד עוצמת קול"
|
he = "זכור תמיד עוצמת קול"
|
||||||
pt-BR = "Lembrar volume"
|
pt-BR = "Lembrar volume"
|
||||||
|
|
||||||
|
[gui.settings.remember_volume_multiplier]
|
||||||
|
en = "Always remember volume multiplier"
|
||||||
|
ru = "Всегда запоминать множитель громкости"
|
||||||
|
es = "Recordar siempre el multiplicador de volumen"
|
||||||
|
fr = "Toujours se souvenir du multiplicateur de volume"
|
||||||
|
zh = "始终记住音量乘数"
|
||||||
|
ar = "تذكر ضرب الصوت دائمًا"
|
||||||
|
kz = "Әрқашан дыбыс деңгейін есте сақтау"
|
||||||
|
he = "זכור תמיד עוצמת קול"
|
||||||
|
pt-BR = "Lembrar volume"
|
||||||
|
|
||||||
|
[gui.settings.volume_multiplier]
|
||||||
|
en = "Volume multiplier"
|
||||||
|
ru = "Множитель громкости"
|
||||||
|
es = "Multiplicador de volumen"
|
||||||
|
fr = "Multiplicateur de volume"
|
||||||
|
zh = "音量乘数"
|
||||||
|
ar = "ضرب الصوت"
|
||||||
|
kz = "Дыбыс деңгейі"
|
||||||
|
he = "עוצמת קול"
|
||||||
|
pt-BR = "Volume"
|
||||||
|
|
||||||
[gui.settings.remember_mic]
|
[gui.settings.remember_mic]
|
||||||
en = "Always remember microphone"
|
en = "Always remember microphone"
|
||||||
ru = "Всегда запоминать микрофон"
|
ru = "Всегда запоминать микрофон"
|
||||||
|
|||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$1" = "configure" ]; then
|
||||||
|
if [ -x "/usr/bin/systemctl" ]; then
|
||||||
|
systemctl --global enable pwsp-daemon.service || true
|
||||||
|
|
||||||
|
for uid_dir in /run/user/*; do
|
||||||
|
if [ -d "$uid_dir/systemd" ]; then
|
||||||
|
uid=$(basename "$uid_dir")
|
||||||
|
if [ "$uid" != "0" ]; then
|
||||||
|
username=$(id -un "$uid" 2>/dev/null) || continue
|
||||||
|
su -s /bin/sh -c "XDG_RUNTIME_DIR=$uid_dir systemctl --user daemon-reload" "$username" || true
|
||||||
|
su -s /bin/sh -c "XDG_RUNTIME_DIR=$uid_dir systemctl --user try-restart pwsp-daemon.service" "$username" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$1" = "remove" ]; then
|
||||||
|
if [ -x "/usr/bin/systemctl" ]; then
|
||||||
|
for uid_dir in /run/user/*; do
|
||||||
|
if [ -d "$uid_dir/systemd" ]; then
|
||||||
|
uid=$(basename "$uid_dir")
|
||||||
|
if [ "$uid" != "0" ]; then
|
||||||
|
username=$(id -un "$uid" 2>/dev/null) || continue
|
||||||
|
su -s /bin/sh -c "XDG_RUNTIME_DIR=$uid_dir systemctl --user daemon-reload" "$username" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
|
||||||
|
if [ -x "/usr/bin/systemctl" ]; then
|
||||||
|
systemctl --global disable pwsp-daemon.service || true
|
||||||
|
|
||||||
|
for uid_dir in /run/user/*; do
|
||||||
|
if [ -d "$uid_dir/systemd" ]; then
|
||||||
|
uid=$(basename "$uid_dir")
|
||||||
|
if [ "$uid" != "0" ]; then
|
||||||
|
username=$(id -un "$uid" 2>/dev/null) || continue
|
||||||
|
su -s /bin/sh -c "XDG_RUNTIME_DIR=$uid_dir systemctl --user stop pwsp-daemon.service" "$username" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
+9
-13
@@ -9,14 +9,13 @@ use itertools::Itertools;
|
|||||||
use pwsp_lib::{
|
use pwsp_lib::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::PlayerState,
|
audio_player::PlayerState,
|
||||||
config::GuiConfig,
|
config::{GuiConfig, HotkeyConfig},
|
||||||
config::HotkeyConfig,
|
|
||||||
gui::{AppState, AudioPlayerState},
|
gui::{AppState, AudioPlayerState},
|
||||||
socket::Request,
|
socket::Request,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::gui::{
|
||||||
daemon::get_daemon_config,
|
get_daemon_config, get_gui_config, make_request_async, make_request_sync,
|
||||||
gui::{get_gui_config, make_request_async, make_request_sync, start_app_state_thread},
|
start_app_state_thread, update_daemon_config,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
@@ -130,10 +129,11 @@ impl SoundpadGui {
|
|||||||
pub fn set_input(&mut self, name: String) {
|
pub fn set_input(&mut self, name: String) {
|
||||||
make_request_async(Request::set_input(&name));
|
make_request_async(Request::set_input(&name));
|
||||||
|
|
||||||
if self.config.save_input {
|
if self.config.save_input
|
||||||
let mut daemon_config = get_daemon_config();
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
daemon_config.default_input_name = Some(name);
|
daemon_config.default_input_name = Some(name);
|
||||||
daemon_config.save_to_file().ok();
|
update_daemon_config(&daemon_config).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,11 +199,7 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !search_query.is_empty() {
|
if !search_query.is_empty() {
|
||||||
let file_name = entry_path
|
let file_name = entry_path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
.file_name()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if !file_name.to_lowercase().contains(search_query) {
|
if !file_name.to_lowercase().contains(search_query) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use eframe::{App, Frame as EFrame};
|
|||||||
use egui::{CentralPanel, Context, ThemePreference};
|
use egui::{CentralPanel, Context, ThemePreference};
|
||||||
use pwsp_lib::{
|
use pwsp_lib::{
|
||||||
types::{config::PreferredTheme, socket::Request},
|
types::{config::PreferredTheme, socket::Request},
|
||||||
utils::{daemon::get_daemon_config, gui::make_request_async},
|
utils::gui::{get_daemon_config, make_request_async, update_daemon_config},
|
||||||
};
|
};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -85,10 +85,29 @@ impl App for SoundpadGui {
|
|||||||
self.app_state.ignore_volume_update_until =
|
self.app_state.ignore_volume_update_until =
|
||||||
Some(Instant::now() + Duration::from_millis(300));
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
|
||||||
if self.config.save_volume {
|
if self.config.save_volume
|
||||||
let mut daemon_config = get_daemon_config();
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
daemon_config.default_volume = Some(self.app_state.volume_slider_value);
|
daemon_config.default_volume = Some(self.app_state.volume_slider_value);
|
||||||
daemon_config.save_to_file().ok();
|
update_daemon_config(&daemon_config).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.app_state.volume_multiplier_dragged {
|
||||||
|
make_request_async(Request::set_volume_multiplier(
|
||||||
|
self.app_state.volume_multiplier_slider_value,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.app_state.volume_multiplier_dragged = false;
|
||||||
|
self.app_state.ignore_volume_multiplier_update_until =
|
||||||
|
Some(Instant::now() + Duration::from_millis(300));
|
||||||
|
|
||||||
|
if self.config.save_volume_multiplier
|
||||||
|
&& let Ok(mut daemon_config) = get_daemon_config()
|
||||||
|
{
|
||||||
|
daemon_config.default_volume_multiplier =
|
||||||
|
Some(self.app_state.volume_multiplier_slider_value);
|
||||||
|
update_daemon_config(&daemon_config).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::gui::SoundpadGui;
|
use crate::gui::SoundpadGui;
|
||||||
use egui::{Align, Button, Color32, ComboBox, Layout, RichText, Ui};
|
use egui::{Align, Button, Color32, ComboBox, Layout, RichText, Slider, Ui};
|
||||||
use egui_material_icons::icons::ICON_ARROW_BACK;
|
use egui_material_icons::icons::ICON_ARROW_BACK;
|
||||||
use pwsp_lib::types::config::PreferredTheme;
|
use pwsp_lib::types::config::PreferredTheme;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
@@ -34,6 +34,10 @@ impl SoundpadGui {
|
|||||||
&mut self.config.save_volume,
|
&mut self.config.save_volume,
|
||||||
t!("gui.settings.remember_volume"),
|
t!("gui.settings.remember_volume"),
|
||||||
);
|
);
|
||||||
|
let save_volume_multiplier_response = ui.checkbox(
|
||||||
|
&mut self.config.save_volume_multiplier,
|
||||||
|
t!("gui.settings.remember_volume_multiplier"),
|
||||||
|
);
|
||||||
let save_input_response =
|
let save_input_response =
|
||||||
ui.checkbox(&mut self.config.save_input, t!("gui.settings.remember_mic"));
|
ui.checkbox(&mut self.config.save_input, t!("gui.settings.remember_mic"));
|
||||||
let save_scale_response = ui.checkbox(
|
let save_scale_response = ui.checkbox(
|
||||||
@@ -46,6 +50,7 @@ impl SoundpadGui {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if save_volume_response.changed()
|
if save_volume_response.changed()
|
||||||
|
|| save_volume_multiplier_response.changed()
|
||||||
|| save_input_response.changed()
|
|| save_input_response.changed()
|
||||||
|| save_scale_response.changed()
|
|| save_scale_response.changed()
|
||||||
|| pause_on_exit_response.changed()
|
|| pause_on_exit_response.changed()
|
||||||
@@ -88,6 +93,39 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
// ----------- Sliders ------------
|
||||||
|
// Volume multiplier
|
||||||
|
let should_update_multiplier = !self.app_state.volume_multiplier_dragged
|
||||||
|
&& self
|
||||||
|
.app_state
|
||||||
|
.ignore_volume_multiplier_update_until
|
||||||
|
.map(|t| std::time::Instant::now() > t)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if should_update_multiplier {
|
||||||
|
self.app_state.volume_multiplier_slider_value =
|
||||||
|
self.audio_player_state.volume_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let slider = Slider::new(
|
||||||
|
&mut self.app_state.volume_multiplier_slider_value,
|
||||||
|
0.01..=3.0,
|
||||||
|
);
|
||||||
|
let response = ui.add(slider);
|
||||||
|
ui.label(t!("gui.settings.volume_multiplier"));
|
||||||
|
|
||||||
|
if response.changed() {
|
||||||
|
// This condition is required to avoid spamming requests while dragging, but to allow changing the value via TextEdit
|
||||||
|
if !response.dragged() || (response.dragged() && response.drag_stopped()) {
|
||||||
|
self.app_state.volume_multiplier_dragged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// --------------------------------
|
||||||
|
|
||||||
ui.with_layout(Layout::bottom_up(Align::Min), |ui| {
|
ui.with_layout(Layout::bottom_up(Align::Min), |ui| {
|
||||||
ui.label(t!(
|
ui.label(t!(
|
||||||
"gui.settings.version",
|
"gui.settings.version",
|
||||||
|
|||||||
+25
-9
@@ -1,9 +1,13 @@
|
|||||||
mod gui;
|
mod gui;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use pwsp_lib::utils::gui::ensure_pwsp_audio_dir;
|
use pwsp_lib::utils::gui::ensure_pwsp_audio_dir;
|
||||||
use rust_i18n::i18n;
|
use rust_i18n::i18n;
|
||||||
use std::{env, path::PathBuf};
|
use std::{
|
||||||
|
env,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
i18n!("locales", fallback = "en");
|
i18n!("locales", fallback = "en");
|
||||||
|
|
||||||
@@ -31,19 +35,31 @@ async fn download_audio_from_url(uri: &str) -> Result<PathBuf> {
|
|||||||
|
|
||||||
let target_url = uri
|
let target_url = uri
|
||||||
.strip_prefix(prefix)
|
.strip_prefix(prefix)
|
||||||
.ok_or_else(|| anyhow::anyhow!("URI does not containt an expected prefix: {}", prefix))?;
|
.ok_or_else(|| anyhow!("URI does not containt an expected prefix: {}", prefix))?;
|
||||||
|
|
||||||
let file_name_encoded = target_url
|
let file_name_encoded = match target_url.split('/').next_back() {
|
||||||
.split('/')
|
Some(path) => path.to_string(),
|
||||||
.next_back()
|
None => {
|
||||||
.unwrap_or("downloaded_audio.mp3");
|
let id = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went back")
|
||||||
|
.as_nanos();
|
||||||
|
format!("downloaded_audio_{}.mp3", id)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let file_name = percent_encoding::percent_decode_str(file_name_encoded)
|
let file_name = percent_encoding::percent_decode_str(&file_name_encoded.clone())
|
||||||
.decode_utf8()
|
.decode_utf8()
|
||||||
.unwrap_or_else(|_| file_name_encoded.into())
|
.unwrap_or_else(|_| file_name_encoded.into())
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let save_path = ensure_pwsp_audio_dir().join(file_name);
|
let normalized_file_name = file_name.replace('\\', "/");
|
||||||
|
let sanitized_file_name = Path::new(&normalized_file_name)
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("downloaded_audio.mp3");
|
||||||
|
|
||||||
|
let save_path = ensure_pwsp_audio_dir()?.join(sanitized_file_name);
|
||||||
|
|
||||||
let response = reqwest::get(target_url)
|
let response = reqwest::get(target_url)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::pipewire::DeviceType,
|
types::pipewire::DeviceType,
|
||||||
utils::{
|
utils::{
|
||||||
daemon::get_daemon_config,
|
daemon::with_daemon_config,
|
||||||
pipewire::{PwTerminator, create_link, get_device, link_player_to_virtual_mic},
|
pipewire::{PwTerminator, create_link, get_device, link_player_to_virtual_mic},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -40,6 +40,7 @@ pub struct FullState {
|
|||||||
pub state: PlayerState,
|
pub state: PlayerState,
|
||||||
pub tracks: Vec<TrackInfo>,
|
pub tracks: Vec<TrackInfo>,
|
||||||
pub volume: f32,
|
pub volume: f32,
|
||||||
|
pub volume_multiplier: f32,
|
||||||
pub current_input: String,
|
pub current_input: String,
|
||||||
pub all_inputs: HashMap<String, String>,
|
pub all_inputs: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
@@ -63,12 +64,19 @@ pub struct AudioPlayer {
|
|||||||
pub input_device_name: Option<String>,
|
pub input_device_name: Option<String>,
|
||||||
|
|
||||||
pub volume: f32, // Master volume
|
pub volume: f32, // Master volume
|
||||||
|
pub volume_multiplier: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioPlayer {
|
impl AudioPlayer {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
let daemon_config = get_daemon_config();
|
let (default_input_name, default_volume, default_volume_multiplier) =
|
||||||
let default_volume = daemon_config.default_volume.unwrap_or(1.0);
|
with_daemon_config(|c| {
|
||||||
|
(
|
||||||
|
c.default_input_name.clone(),
|
||||||
|
c.default_volume.unwrap_or(1.0),
|
||||||
|
c.default_volume_multiplier.unwrap_or(1.0),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut audio_player = AudioPlayer {
|
let mut audio_player = AudioPlayer {
|
||||||
stream_handle: None,
|
stream_handle: None,
|
||||||
@@ -77,9 +85,10 @@ impl AudioPlayer {
|
|||||||
|
|
||||||
input_link_sender: None,
|
input_link_sender: None,
|
||||||
player_link_sender: None,
|
player_link_sender: None,
|
||||||
input_device_name: daemon_config.default_input_name.clone(),
|
input_device_name: default_input_name,
|
||||||
|
|
||||||
volume: default_volume,
|
volume: default_volume,
|
||||||
|
volume_multiplier: default_volume_multiplier,
|
||||||
};
|
};
|
||||||
|
|
||||||
if audio_player.input_device_name.is_some() {
|
if audio_player.input_device_name.is_some() {
|
||||||
@@ -262,12 +271,16 @@ impl AudioPlayer {
|
|||||||
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) {
|
||||||
sound.volume = volume;
|
sound.volume = volume;
|
||||||
sound.sink.set_volume(self.volume * volume);
|
sound
|
||||||
|
.sink
|
||||||
|
.set_volume(self.volume * sound.volume * self.volume_multiplier);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.volume = volume;
|
self.volume = volume;
|
||||||
for sound in self.tracks.values_mut() {
|
for sound in self.tracks.values_mut() {
|
||||||
sound.sink.set_volume(self.volume * sound.volume);
|
sound
|
||||||
|
.sink
|
||||||
|
.set_volume(self.volume * sound.volume * self.volume_multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,7 +360,7 @@ impl AudioPlayer {
|
|||||||
.ok_or_else(|| anyhow::anyhow!("stream_handle is unexpectedly missing"))?
|
.ok_or_else(|| anyhow::anyhow!("stream_handle is unexpectedly missing"))?
|
||||||
.mixer();
|
.mixer();
|
||||||
let sink = Player::connect_new(mixer);
|
let sink = Player::connect_new(mixer);
|
||||||
sink.set_volume(self.volume); // Default volume is 1.0 * master
|
sink.set_volume(self.volume * self.volume_multiplier); // Default volume is 1.0 * master
|
||||||
sink.append(source);
|
sink.append(source);
|
||||||
sink.play();
|
sink.play();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::{FullState, PlayerState},
|
audio_player::{FullState, PlayerState},
|
||||||
config::HotkeyConfig,
|
config::{DaemonConfig, HotkeyConfig},
|
||||||
socket::{Request, Response},
|
socket::{Request, Response},
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
commands::parse_command,
|
commands::parse_command,
|
||||||
daemon::get_audio_player,
|
daemon::{get_audio_player, with_daemon_config},
|
||||||
pipewire::{get_all_devices, get_device},
|
pipewire::{get_all_devices, get_device},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -46,11 +46,17 @@ pub struct GetVolumeCommand {
|
|||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GetVolumeMultiplierCommand {}
|
||||||
|
|
||||||
pub struct SetVolumeCommand {
|
pub struct SetVolumeCommand {
|
||||||
pub volume: Option<f32>,
|
pub volume: Option<f32>,
|
||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SetVolumeMultiplierCommand {
|
||||||
|
pub volume_multiplier: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GetPositionCommand {
|
pub struct GetPositionCommand {
|
||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
@@ -127,6 +133,14 @@ pub struct ClearHotkeyKeyCommand {
|
|||||||
pub slot: Option<String>,
|
pub slot: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GetDaemonConfigCommand {}
|
||||||
|
|
||||||
|
pub struct SaveDaemonConfigCommand {}
|
||||||
|
|
||||||
|
pub struct UpdateDaemonConfigCommand {
|
||||||
|
pub new_config: DaemonConfig,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for PingCommand {
|
impl Executable for PingCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -262,6 +276,18 @@ impl Executable for GetVolumeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for GetVolumeMultiplierCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
let audio_player = match get_audio_player().await {
|
||||||
|
Ok(player) => player.lock().await,
|
||||||
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::new(true, audio_player.volume_multiplier.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for SetVolumeCommand {
|
impl Executable for SetVolumeCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -278,6 +304,26 @@ impl Executable for SetVolumeCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for SetVolumeMultiplierCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
if let Some(volume_multiplier) = self.volume_multiplier {
|
||||||
|
let mut audio_player = match get_audio_player().await {
|
||||||
|
Ok(player) => player.lock().await,
|
||||||
|
Err(err) => return Response::new(false, format!("Audio player error: {}", err)),
|
||||||
|
};
|
||||||
|
audio_player.volume_multiplier = volume_multiplier;
|
||||||
|
audio_player.set_volume(volume_multiplier, None); // Reset current volume for all tracks to apply multiplier
|
||||||
|
Response::new(
|
||||||
|
true,
|
||||||
|
format!("Audio volume multiplier was set to {}", volume_multiplier),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Response::new(false, "Invalid volume multiplier value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for GetPositionCommand {
|
impl Executable for GetPositionCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -508,6 +554,7 @@ impl Executable for GetFullStateCommand {
|
|||||||
state: audio_player.get_state(),
|
state: audio_player.get_state(),
|
||||||
tracks: audio_player.get_tracks(),
|
tracks: audio_player.get_tracks(),
|
||||||
volume: audio_player.volume,
|
volume: audio_player.volume,
|
||||||
|
volume_multiplier: audio_player.volume_multiplier,
|
||||||
current_input: current_input_nick,
|
current_input: current_input_nick,
|
||||||
all_inputs,
|
all_inputs,
|
||||||
};
|
};
|
||||||
@@ -723,3 +770,36 @@ impl Executable for ClearHotkeyKeyCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for GetDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
let serialized = with_daemon_config(|c| serde_json::to_string(&c));
|
||||||
|
|
||||||
|
match serialized {
|
||||||
|
Ok(s) => Response::new(true, s),
|
||||||
|
Err(err) => Response::new(false, format!("Failed to serialize daemon config: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for SaveDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
match with_daemon_config(|c| c.save_to_file()) {
|
||||||
|
Ok(_) => Response::new(true, "Daemon config saved successfully"),
|
||||||
|
Err(err) => Response::new(false, format!("Failed to save daemon config: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for UpdateDaemonConfigCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
with_daemon_config(|c| {
|
||||||
|
c.clone_from(&self.new_config);
|
||||||
|
});
|
||||||
|
|
||||||
|
Response::new(true, "Daemon config updated successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use std::{
|
|||||||
pub struct DaemonConfig {
|
pub struct DaemonConfig {
|
||||||
pub default_input_name: Option<String>,
|
pub default_input_name: Option<String>,
|
||||||
pub default_volume: Option<f32>,
|
pub default_volume: Option<f32>,
|
||||||
|
pub default_volume_multiplier: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DaemonConfig {
|
impl DaemonConfig {
|
||||||
@@ -73,6 +74,7 @@ pub struct GuiConfig {
|
|||||||
pub left_panel_width: f32,
|
pub left_panel_width: f32,
|
||||||
|
|
||||||
pub save_volume: bool,
|
pub save_volume: bool,
|
||||||
|
pub save_volume_multiplier: bool,
|
||||||
pub save_input: bool,
|
pub save_input: bool,
|
||||||
pub save_scale_factor: bool,
|
pub save_scale_factor: bool,
|
||||||
pub pause_on_exit: bool,
|
pub pause_on_exit: bool,
|
||||||
@@ -117,11 +119,12 @@ impl Default for GuiConfig {
|
|||||||
left_panel_width: 280.0,
|
left_panel_width: 280.0,
|
||||||
|
|
||||||
save_volume: false,
|
save_volume: false,
|
||||||
|
save_volume_multiplier: false,
|
||||||
save_input: false,
|
save_input: false,
|
||||||
save_scale_factor: false,
|
save_scale_factor: false,
|
||||||
pause_on_exit: false,
|
pause_on_exit: false,
|
||||||
|
|
||||||
dirs: vec![ensure_pwsp_audio_dir()],
|
dirs: vec![ensure_pwsp_audio_dir().unwrap()],
|
||||||
|
|
||||||
preferred_theme: PreferredTheme::System,
|
preferred_theme: PreferredTheme::System,
|
||||||
dirs_settings: HashMap::new(),
|
dirs_settings: HashMap::new(),
|
||||||
|
|||||||
@@ -31,13 +31,16 @@ pub struct AppState {
|
|||||||
|
|
||||||
pub show_settings: bool,
|
pub show_settings: bool,
|
||||||
pub volume_dragged: bool,
|
pub volume_dragged: bool,
|
||||||
|
pub volume_multiplier_dragged: bool,
|
||||||
pub force_focus_search: bool,
|
pub force_focus_search: bool,
|
||||||
|
|
||||||
pub volume_slider_value: f32,
|
pub volume_slider_value: f32,
|
||||||
|
pub volume_multiplier_slider_value: f32,
|
||||||
|
|
||||||
pub search_field_id: Option<Id>,
|
pub search_field_id: Option<Id>,
|
||||||
|
|
||||||
pub ignore_volume_update_until: Option<Instant>,
|
pub ignore_volume_update_until: Option<Instant>,
|
||||||
|
pub ignore_volume_multiplier_update_until: Option<Instant>,
|
||||||
|
|
||||||
pub current_dir: Option<PathBuf>,
|
pub current_dir: Option<PathBuf>,
|
||||||
pub dirs: Vec<PathBuf>,
|
pub dirs: Vec<PathBuf>,
|
||||||
@@ -65,6 +68,7 @@ pub struct AudioPlayerState {
|
|||||||
pub tracks: Vec<TrackInfo>,
|
pub tracks: Vec<TrackInfo>,
|
||||||
|
|
||||||
pub volume: f32, // Master volume
|
pub volume: f32, // Master volume
|
||||||
|
pub volume_multiplier: f32,
|
||||||
|
|
||||||
pub current_input: String,
|
pub current_input: String,
|
||||||
pub all_inputs: HashMap<String, String>,
|
pub all_inputs: HashMap<String, String>,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::config::DaemonConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -94,6 +95,10 @@ impl Request {
|
|||||||
Request::new("get_volume", args)
|
Request::new("get_volume", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_volume_multiplier() -> Self {
|
||||||
|
Request::new("get_volume_multiplier", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_position(id: Option<u32>) -> Self {
|
pub fn get_position(id: Option<u32>) -> Self {
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
let id_str;
|
let id_str;
|
||||||
@@ -138,6 +143,13 @@ impl Request {
|
|||||||
Request::new("set_volume".to_string(), args)
|
Request::new("set_volume".to_string(), args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_volume_multiplier(volume: f32) -> Self {
|
||||||
|
Request::new(
|
||||||
|
"set_volume_multiplier",
|
||||||
|
vec![("volume_multiplier", &volume.to_string())],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seek(position: f32, id: Option<u32>) -> Self {
|
pub fn seek(position: f32, id: Option<u32>) -> Self {
|
||||||
let mut args = vec![("position".to_string(), position.to_string())];
|
let mut args = vec![("position".to_string(), position.to_string())];
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
@@ -172,6 +184,24 @@ impl Request {
|
|||||||
Request::new("get_daemon_version", vec![])
|
Request::new("get_daemon_version", vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_daemon_config() -> Self {
|
||||||
|
Request::new("get_daemon_config", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_daemon_config() -> Self {
|
||||||
|
Request::new("save_daemon_config", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_daemon_config(new_config: &DaemonConfig) -> Self {
|
||||||
|
Request::new(
|
||||||
|
"update_daemon_config",
|
||||||
|
vec![(
|
||||||
|
"new_config",
|
||||||
|
&serde_json::to_string(&new_config).unwrap_or_default(),
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_full_state() -> Self {
|
pub fn get_full_state() -> Self {
|
||||||
Request::new("get_full_state", vec![])
|
Request::new("get_full_state", vec![])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::types::{commands::*, socket::Request};
|
use crate::types::{commands::*, config::DaemonConfig, socket::Request};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
"is_paused" => Some(Box::new(IsPausedCommand {})),
|
||||||
"get_state" => Some(Box::new(GetStateCommand {})),
|
"get_state" => Some(Box::new(GetStateCommand {})),
|
||||||
"get_volume" => Some(Box::new(GetVolumeCommand { id })),
|
"get_volume" => Some(Box::new(GetVolumeCommand { id })),
|
||||||
|
"get_volume_multiplier" => Some(Box::new(GetVolumeMultiplierCommand {})),
|
||||||
"set_volume" => {
|
"set_volume" => {
|
||||||
let volume = request
|
let volume = request
|
||||||
.args
|
.args
|
||||||
@@ -24,6 +25,15 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
.ok();
|
.ok();
|
||||||
Some(Box::new(SetVolumeCommand { volume, id }))
|
Some(Box::new(SetVolumeCommand { volume, id }))
|
||||||
}
|
}
|
||||||
|
"set_volume_multiplier" => {
|
||||||
|
let volume_multiplier = request
|
||||||
|
.args
|
||||||
|
.get("volume_multiplier")
|
||||||
|
.unwrap_or(&String::new())
|
||||||
|
.parse::<f32>()
|
||||||
|
.ok();
|
||||||
|
Some(Box::new(SetVolumeMultiplierCommand { volume_multiplier }))
|
||||||
|
}
|
||||||
"get_position" => Some(Box::new(GetPositionCommand { id })),
|
"get_position" => Some(Box::new(GetPositionCommand { id })),
|
||||||
"seek" => {
|
"seek" => {
|
||||||
let position = request
|
let position = request
|
||||||
@@ -119,6 +129,17 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
key_chord,
|
key_chord,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
"get_daemon_config" => Some(Box::new(GetDaemonConfigCommand {})),
|
||||||
|
"save_daemon_config" => Some(Box::new(SaveDaemonConfigCommand {})),
|
||||||
|
"update_daemon_config" => {
|
||||||
|
let new_config = request
|
||||||
|
.args
|
||||||
|
.get("new_config")
|
||||||
|
.and_then(|nc| serde_json::from_str::<DaemonConfig>(nc).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
Some(Box::new(UpdateDaemonConfigCommand { new_config }))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn get_config_path() -> Result<PathBuf> {
|
pub fn get_config_path() -> Result<PathBuf> {
|
||||||
let config_path = dirs::config_dir().expect("Failed to obtain config dir");
|
let config_path = dirs::config_dir().context("Failed to obtain config dir")?;
|
||||||
Ok(config_path.join("pwsp"))
|
Ok(config_path.join("pwsp"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,36 +7,52 @@ use crate::types::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, error::Error, fs};
|
use std::{
|
||||||
|
env,
|
||||||
|
error::Error,
|
||||||
|
fs,
|
||||||
|
sync::{Arc, Mutex, OnceLock},
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::UnixStream,
|
net::UnixStream,
|
||||||
sync::{Mutex, OnceCell},
|
sync::{Mutex as AsyncMutex, OnceCell},
|
||||||
time::{Duration, sleep},
|
time::{Duration, sleep},
|
||||||
};
|
};
|
||||||
|
|
||||||
static AUDIO_PLAYER: OnceCell<Mutex<AudioPlayer>> = OnceCell::const_new();
|
static AUDIO_PLAYER: OnceCell<AsyncMutex<AudioPlayer>> = OnceCell::const_new();
|
||||||
|
static DAEMON_CONFIG: OnceLock<Arc<Mutex<DaemonConfig>>> = OnceLock::new();
|
||||||
|
|
||||||
pub async fn get_audio_player() -> Result<&'static Mutex<AudioPlayer>, String> {
|
pub async fn get_audio_player() -> Result<&'static AsyncMutex<AudioPlayer>, String> {
|
||||||
AUDIO_PLAYER
|
AUDIO_PLAYER
|
||||||
.get_or_try_init(|| async {
|
.get_or_try_init(|| async {
|
||||||
println!("Initializing audio player");
|
println!("Initializing audio player");
|
||||||
match AudioPlayer::new().await {
|
match AudioPlayer::new().await {
|
||||||
Ok(player) => Ok(Mutex::new(player)),
|
Ok(player) => Ok(AsyncMutex::new(player)),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_daemon_config() -> DaemonConfig {
|
pub fn get_daemon_config() -> &'static Arc<Mutex<DaemonConfig>> {
|
||||||
DaemonConfig::load_from_file().unwrap_or_else(|_| {
|
DAEMON_CONFIG.get_or_init(|| {
|
||||||
|
Arc::new(Mutex::new(DaemonConfig::load_from_file().unwrap_or_else(
|
||||||
|
|_| {
|
||||||
let config = DaemonConfig::default();
|
let config = DaemonConfig::default();
|
||||||
config.save_to_file().ok();
|
config.save_to_file().ok();
|
||||||
config
|
config
|
||||||
|
},
|
||||||
|
)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_daemon_config<R>(f: impl FnOnce(&mut DaemonConfig) -> R) -> R {
|
||||||
|
let config = get_daemon_config().clone();
|
||||||
|
let mut guard = config.lock().unwrap();
|
||||||
|
f(&mut guard)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_current_uid() -> u32 {
|
fn get_current_uid() -> u32 {
|
||||||
rustix::process::geteuid().as_raw()
|
rustix::process::geteuid().as_raw()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
audio_player::FullState,
|
audio_player::FullState,
|
||||||
config::{GuiConfig, HotkeyConfig},
|
config::{DaemonConfig, GuiConfig, HotkeyConfig},
|
||||||
gui::AudioPlayerState,
|
gui::AudioPlayerState,
|
||||||
socket::{Request, Response},
|
socket::{Request, Response},
|
||||||
},
|
},
|
||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use std::{
|
use std::{
|
||||||
|
fs,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
@@ -23,6 +24,19 @@ pub fn get_gui_config() -> GuiConfig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_daemon_config(new_config: &DaemonConfig) -> Result<()> {
|
||||||
|
make_request_sync(Request::update_daemon_config(new_config))?;
|
||||||
|
make_request_async(Request::save_daemon_config());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_daemon_config() -> Result<DaemonConfig> {
|
||||||
|
let response = make_request_sync(Request::get_daemon_config())?;
|
||||||
|
let config: DaemonConfig = serde_json::from_str(&response.message)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_request_sync(request: Request) -> Result<Response> {
|
pub fn make_request_sync(request: Request) -> Result<Response> {
|
||||||
tokio::task::block_in_place(|| {
|
tokio::task::block_in_place(|| {
|
||||||
tokio::runtime::Handle::current()
|
tokio::runtime::Handle::current()
|
||||||
@@ -37,15 +51,19 @@ pub fn make_request_async(request: Request) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_pwsp_audio_dir() -> PathBuf {
|
pub fn ensure_pwsp_audio_dir() -> Result<PathBuf> {
|
||||||
let audio_dir = dirs::audio_dir().unwrap_or("~/Music".into());
|
let audio_dir = dirs::audio_dir().unwrap_or(
|
||||||
|
dirs::home_dir()
|
||||||
|
.map(|p| p.join("Music"))
|
||||||
|
.ok_or_else(|| anyhow!("Failed to get home directory. Is your system ok?"))?,
|
||||||
|
);
|
||||||
let pwsp_audio_dir = audio_dir.join("PWSP");
|
let pwsp_audio_dir = audio_dir.join("PWSP");
|
||||||
|
|
||||||
if !pwsp_audio_dir.exists() {
|
if !pwsp_audio_dir.exists() {
|
||||||
std::fs::create_dir_all(&pwsp_audio_dir).ok();
|
fs::create_dir_all(&pwsp_audio_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
pwsp_audio_dir
|
Ok(pwsp_audio_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_time_pair(position: f32, duration: f32) -> String {
|
pub fn format_time_pair(position: f32, duration: f32) -> String {
|
||||||
@@ -98,6 +116,7 @@ pub fn start_app_state_thread(audio_player_state_shared: Arc<Mutex<AudioPlayerSt
|
|||||||
};
|
};
|
||||||
guard.tracks = full_state.tracks;
|
guard.tracks = full_state.tracks;
|
||||||
guard.volume = full_state.volume;
|
guard.volume = full_state.volume;
|
||||||
|
guard.volume_multiplier = full_state.volume_multiplier;
|
||||||
guard.current_input = full_state
|
guard.current_input = full_state
|
||||||
.current_input
|
.current_input
|
||||||
.split(" - ")
|
.split(" - ")
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ with open(cargo_toml_path, "r", encoding="utf-8") as f:
|
|||||||
|
|
||||||
# We want to match version in [workspace.package]
|
# We want to match version in [workspace.package]
|
||||||
# First, let's find the [workspace.package] section
|
# First, let's find the [workspace.package] section
|
||||||
workspace_package_match = re.search(
|
workspace_package_match = re.search(r"\[workspace\.package\](.*?)(?=\n\[|$)", cargo_toml_content, re.DOTALL)
|
||||||
r"\[workspace\.package\](.*?)(?=\n\[|$)", cargo_toml_content, re.DOTALL
|
|
||||||
)
|
|
||||||
if not workspace_package_match:
|
if not workspace_package_match:
|
||||||
fatal("Could not find [workspace.package] section in Cargo.toml.")
|
fatal("Could not find [workspace.package] section in Cargo.toml.")
|
||||||
|
|
||||||
|
if isinstance(workspace_package_match, re.Match):
|
||||||
workspace_package_sec = workspace_package_match.group(1)
|
workspace_package_sec = workspace_package_match.group(1)
|
||||||
version_match = re.search(r'version\s*=\s*"([^"]+)"', workspace_package_sec)
|
version_match = re.search(r'version\s*=\s*"([^"]+)"', workspace_package_sec)
|
||||||
if not version_match:
|
if not version_match:
|
||||||
fatal("Could not find version in [workspace.package] in Cargo.toml.")
|
fatal("Could not find version in [workspace.package] in Cargo.toml.")
|
||||||
|
|
||||||
|
if isinstance(version_match, re.Match):
|
||||||
current_version = version_match.group(1)
|
current_version = version_match.group(1)
|
||||||
print(f"Current version detected: {current_version}")
|
print(f"Current version detected: {current_version}")
|
||||||
|
|
||||||
@@ -62,9 +62,7 @@ print("Updating Cargo.toml...")
|
|||||||
|
|
||||||
def replace_version_in_workspace(match):
|
def replace_version_in_workspace(match):
|
||||||
section_content = match.group(1)
|
section_content = match.group(1)
|
||||||
updated_section_content = re.sub(
|
updated_section_content = re.sub(r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_version}\g<2>", section_content)
|
||||||
r'(version\s*=\s*")[^"]+(")', rf"\g<1>{new_version}\g<2>", section_content
|
|
||||||
)
|
|
||||||
return f"[workspace.package]{updated_section_content}"
|
return f"[workspace.package]{updated_section_content}"
|
||||||
|
|
||||||
|
|
||||||
@@ -127,9 +125,7 @@ def update_srcinfo(directory, pkgbuild_path, srcinfo_path):
|
|||||||
f.write(result.stdout)
|
f.write(result.stdout)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(f"Warning: makepkg failed in {directory}: {e}. Falling back to text replacement.")
|
||||||
f"Warning: makepkg failed in {directory}: {e}. Falling back to text replacement."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text replacement fallback
|
# Text replacement fallback
|
||||||
with open(srcinfo_path, "r", encoding="utf-8") as f:
|
with open(srcinfo_path, "r", encoding="utf-8") as f:
|
||||||
@@ -142,9 +138,7 @@ def update_srcinfo(directory, pkgbuild_path, srcinfo_path):
|
|||||||
|
|
||||||
|
|
||||||
update_srcinfo("packages/aur/bin", pkgbuild_bin_path, "packages/aur/bin/.SRCINFO")
|
update_srcinfo("packages/aur/bin", pkgbuild_bin_path, "packages/aur/bin/.SRCINFO")
|
||||||
update_srcinfo(
|
update_srcinfo("packages/aur/standart", pkgbuild_std_path, "packages/aur/standart/.SRCINFO")
|
||||||
"packages/aur/standart", pkgbuild_std_path, "packages/aur/standart/.SRCINFO"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. Update packages/flatpak/ru.arabianq.pwsp.metainfo.xml
|
# 4. Update packages/flatpak/ru.arabianq.pwsp.metainfo.xml
|
||||||
flatpak_xml_path = "packages/flatpak/ru.arabianq.pwsp.metainfo.xml"
|
flatpak_xml_path = "packages/flatpak/ru.arabianq.pwsp.metainfo.xml"
|
||||||
|
|||||||
Reference in New Issue
Block a user