Compare commits

..
32 Commits
Author SHA1 Message Date
arabianq 6d7abdbf53 change version to 1.13.0 2026-07-24 05:52:27 +03:00
arabianq 41bb035b2c cargo update 2026-07-24 05:43:52 +03:00
arabianq 3873bf4270 deps: update rodio 2026-07-24 05:43:02 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2b60b47cce chore(deps): bump clap from 4.6.3 to 4.6.4 in the clap-stack group (#176)
* chore(deps): bump clap from 4.6.3 to 4.6.4 in the clap-stack group

Bumps the clap-stack group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.6.3 to 4.6.4
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.3...clap_complete-v4.6.4)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: clap-stack
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 05:42:53 +03:00
Tarasov AleksandrandGitHub 65ae8589db feat(lib, daemon, cli, gui): add volume multiplier (#175)
* initial implementation

* rename DaemonConfig.volume_multiplier to default_volume_multiplier

* add volume_multiplier to the FullState

* add pwsp-gui integration
2026-07-24 05:33:51 +03:00
Tarasov AleksandrandGitHub a33d023950 feat(daemon, cli): global DaemonConfig and an ability to get/save it using cli (#174)
* use one global Arc<DaemonConfig>

* implement commands

* replace Arc<DaemonConfig> with Arc<Mutex<DaemonConfig>> and fix pwsp-gui

* add pwsp-cli support

* replace serde_json::to_string_pretty with to_string

* refactor

* implement UpdateDaemonConfigCommand and use it in pwsp-gui so it now uses real in-memory config, not the saved one

* implement utils::gui get_daemon_config and update_daemon_config to simplify code
2026-07-24 04:49:51 +03:00
arabianq 5c73bc1bea fix cagro sources 2026-07-20 21:26:08 +03:00
arabianq 42e305869a change version to 1.12.5 2026-07-20 21:20:02 +03:00
arabianq cb7f4f7f6b update cargo-sources.json 2026-07-20 21:18:43 +03:00
arabianq 62821904af cargo update 2026-07-20 21:16:56 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Tarasov Aleksandrarabianq
3a218283db chore(deps): bump the serde-stack group with 2 updates (#169)
* chore(deps): bump the serde-stack group with 2 updates

Bumps the serde-stack group with 2 updates: [serde](https://github.com/serde-rs/serde) and [serde_json](https://github.com/serde-rs/json).


Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.228...v1.0.229)

Updates `serde_json` from 1.0.150 to 1.0.151
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.150...v1.0.151)

---
updated-dependencies:
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: serde-stack
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: serde-stack
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

* fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tarasov Aleksandr <55220741+arabianq@users.noreply.github.com>
Co-authored-by: arabian <a.tevg@ya.ru>
2026-07-20 20:58:10 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Tarasov Aleksandr
02fdfb6ca8 chore(deps): bump rust-i18n from 4.2.0 to 4.2.1 (#172)
* chore(deps): bump rust-i18n from 4.2.0 to 4.2.1

Bumps [rust-i18n](https://github.com/longbridge/rust-i18n) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/longbridge/rust-i18n/releases)
- [Commits](https://github.com/longbridge/rust-i18n/compare/v4.2.0...v4.2.1)

---
updated-dependencies:
- dependency-name: rust-i18n
  dependency-version: 4.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tarasov Aleksandr <55220741+arabianq@users.noreply.github.com>
2026-07-20 19:33:48 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a027838d0e chore(deps): bump tokio from 1.52.3 to 1.53.0 in the tokio-stack group (#170)
* chore(deps): bump tokio from 1.52.3 to 1.53.0 in the tokio-stack group

Bumps the tokio-stack group with 1 update: [tokio](https://github.com/tokio-rs/tokio).


Updates `tokio` from 1.52.3 to 1.53.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.3...tokio-1.53.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: tokio-stack
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 19:22:03 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1828d903a1 chore(deps): bump clap from 4.6.1 to 4.6.3 in the clap-stack group (#171)
* chore(deps): bump clap from 4.6.1 to 4.6.3 in the clap-stack group

Bumps the clap-stack group with 1 update: [clap](https://github.com/clap-rs/clap).


Updates `clap` from 4.6.1 to 4.6.3
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.3)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: clap-stack
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 19:21:48 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
83ca7a5e34 chore(deps): bump async-trait from 0.1.89 to 0.1.91 (#173)
* chore(deps): bump async-trait from 0.1.89 to 0.1.91

Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.89 to 0.1.91.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.89...0.1.91)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-version: 0.1.91
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(flatpak): automatically update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 19:18:56 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f2aca5e34a chore(deps): bump actions/setup-python from 6 to 7 (#168)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 19:13:41 +03:00
6130a99c4b fix(lib): don't create '~' in $HOME (#166)
* fix(lib): don't create '~' in $HOME

* throw an error wen fail to get home directory

---------

Co-authored-by: arabian <a.tevg@ya.ru>
2026-07-20 19:02:10 +03:00
tonocoandGitHub 322b93efa1 fix(scripts): check workspace_package_match and version_match type before passing (#167) 2026-07-19 17:22:46 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>arabianq
683f4127a0 chore(deps): bump rust-i18n from 4.1.0 to 4.2.0 (#163)
* chore(deps): bump rust-i18n from 4.1.0 to 4.2.0

Bumps [rust-i18n](https://github.com/longbridge/rust-i18n) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/longbridge/rust-i18n/releases)
- [Commits](https://github.com/longbridge/rust-i18n/compare/v4.1.0...v4.2.0)

---
updated-dependencies:
- dependency-name: rust-i18n
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* update cargo-sources.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: arabian <a.tevg@ya.ru>
2026-07-15 16:21:05 +03:00
arabianq c873166e21 fix(dependabot): install tomlkit 2026-07-14 16:43:57 +03:00
Tarasov AleksandrandGitHub ec4e873071 refactor: better systemd service (#161)
* move `pwsp-daemon.service` from `pwsp-gui/assets` to pwsp-daemon/assets + autorestart service + better logging + wait for pipewire until start

* adapt all packages

* improve systemd service calling for .deb package
2026-07-07 21:29:26 +03:00
arabianq a91252d7fb change version to 1.12.4 2026-07-07 19:31:00 +03:00
arabianq 239170df45 cargo update 2026-07-07 19:30:15 +03:00
arabianq 5fef42a2e2 cargo fmt 2026-07-07 19:29:48 +03:00
Tarasov AleksandrGitHubgoogle-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
4f8f663f12 fix(pwsp-gui): fix arbitrary file overwrite via path traversal (#159)
* Fix path traversal vulnerability in audio download logic

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

* std::path::Path -> Path

* add random id to the filename if it's unknown

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-07-07 19:29:12 +03:00
Tarasov AleksandrGitHubgoogle-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
6ccc55a1d0 perf(pwsp-gui): optimize gui file filtering performance (#160)
Avoid unnecessary string cloning and allocation in `pwsp-gui/src/gui/mod.rs` when checking if a file matches the search query.

Specifically, we replaced `.to_string_lossy().to_string()` with `.to_string_lossy()` because `.to_lowercase()` directly works on `Cow<str>`, avoiding an intermediate String allocation.

Measured improvement was verified using an isolated benchmark demonstrating roughly 10-15% reduction in execution time.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-07-07 19:00:16 +03:00
Tarasov AleksandrGitHubgoogle-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
d266a3f073 fix(pwsp-lib): fixed unhandled panic on missing config directory (#157)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-07-07 18:58:43 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d2993592be chore(deps): bump actions/upload-artifact from 4 to 7 (#153)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 16:56:24 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1a53ab62dc chore(deps): bump peaceiris/actions-gh-pages from 3 to 4 (#154)
Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4.
- [Release notes](https://github.com/peaceiris/actions-gh-pages/releases)
- [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md)
- [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peaceiris/actions-gh-pages
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 16:06:50 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
51fcd94a61 chore(deps): bump actions/checkout from 4 to 7 (#155)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 15:59:53 +03:00
arabianq 7f6a122816 ci(flatpak): use /run/ccache for flatpak builds 2026-07-07 15:50:06 +03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7ca01ed8cb chore(deps): bump stefanzweifel/git-auto-commit-action from 5 to 7 (#156)
Bumps [stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action) from 5 to 7.
- [Release notes](https://github.com/stefanzweifel/git-auto-commit-action/releases)
- [Changelog](https://github.com/stefanzweifel/git-auto-commit-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stefanzweifel/git-auto-commit-action/compare/v5...v7)

---
updated-dependencies:
- dependency-name: stefanzweifel/git-auto-commit-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-07 15:41:31 +03:00
37 changed files with 1457 additions and 894 deletions
+7 -7
View File
@@ -37,7 +37,7 @@ jobs:
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
@@ -90,7 +90,7 @@ jobs:
zip -j "$ARCHIVE_NAME" "${FILES[@]}" zip -j "$ARCHIVE_NAME" "${FILES[@]}"
- name: Upload ZIP 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
@@ -124,7 +124,7 @@ jobs:
fi fi
- name: Upload .deb artifact - 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
@@ -139,7 +139,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v7
- name: Setup GPG - name: Setup GPG
id: gpg id: gpg
@@ -156,7 +156,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Checkout existing gh-pages - name: Checkout existing gh-pages
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
ref: gh-pages ref: gh-pages
path: gh-pages-branch path: gh-pages-branch
@@ -202,7 +202,7 @@ jobs:
done done
- name: Deploy APT Repo to GitHub Pages - name: Deploy APT Repo to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./apt-repo publish_dir: ./apt-repo
@@ -225,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
+4 -4
View File
@@ -14,23 +14,23 @@ jobs:
contents: write # Needed to push back to the branch contents: write # Needed to push back to the branch
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v6 uses: actions/setup-python@v7
with: with:
python-version: "3.x" python-version: "3.x"
- name: Install dependencies - name: Install dependencies
run: pip install aiohttp toml run: pip install aiohttp tomlkit
- name: Generate cargo-sources.json - name: Generate cargo-sources.json
run: bash ./scripts/generate-sources.sh run: bash ./scripts/generate-sources.sh
- name: Commit and push changes - name: Commit and push changes
uses: stefanzweifel/git-auto-commit-action@v5 uses: stefanzweifel/git-auto-commit-action@v7
with: with:
commit_message: "chore(flatpak): automatically update cargo-sources.json" commit_message: "chore(flatpak): automatically update cargo-sources.json"
file_pattern: "packages/flatpak/cargo-sources.json" file_pattern: "packages/flatpak/cargo-sources.json"
+3 -3
View File
@@ -40,7 +40,7 @@ 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 }}
@@ -103,7 +103,7 @@ jobs:
pattern: ru.arabianq.pwsp-* pattern: ru.arabianq.pwsp-*
- name: Checkout existing gh-pages - name: Checkout existing gh-pages
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
ref: gh-pages ref: gh-pages
path: gh-pages-branch path: gh-pages-branch
@@ -171,7 +171,7 @@ jobs:
EOF EOF
- name: Deploy Flatpak Repo to GitHub Pages - name: Deploy Flatpak Repo to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./pages-repo publish_dir: ./pages-repo
+6 -6
View File
@@ -74,7 +74,7 @@ jobs:
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
@@ -119,7 +119,7 @@ jobs:
zip -j "$ARCHIVE_NAME" "${FILES[@]}" zip -j "$ARCHIVE_NAME" "${FILES[@]}"
- name: Upload ZIP artifact - 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
@@ -144,7 +144,7 @@ jobs:
cargo-deb -p pwsp-gui --no-build cargo-deb -p pwsp-gui --no-build
- name: Upload DEB artifact - 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
@@ -196,7 +196,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v7
- name: Setup GPG - name: Setup GPG
id: gpg id: gpg
@@ -213,7 +213,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Checkout existing gh-pages - name: Checkout existing gh-pages
uses: actions/checkout@v4 uses: actions/checkout@v7
with: with:
ref: gh-pages ref: gh-pages
path: gh-pages-branch path: gh-pages-branch
@@ -264,7 +264,7 @@ jobs:
done done
- name: Deploy APT Repo to GitHub Pages - name: Deploy APT Repo to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v4
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./apt-repo publish_dir: ./apt-repo
Generated
+414 -322
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -8,7 +8,7 @@ members = [
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "1.12.3" 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",
+4 -4
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp-bin pkgbase = pwsp-bin
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries) pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
pkgver = 1.12.3 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.3.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.3.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.3-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.3/pwsp-v1.12.3-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.3-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.3/pwsp-v1.12.3-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
+2 -2
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru> # Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgname=pwsp-bin pkgname=pwsp-bin
_pkgname=pipewire-soundpad _pkgname=pipewire-soundpad
pkgver=1.12.3 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')
@@ -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"
} }
+2 -2
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp pkgbase = pwsp
pkgdesc = Lets you play audio files through your microphone pkgdesc = Lets you play audio files through your microphone
pkgver = 1.12.3 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.3.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
+2 -2
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru> # Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgsubn=pwsp pkgsubn=pwsp
pkgname=pwsp pkgname=pwsp
pkgver=1.12.3 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')
@@ -44,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"
} }
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.3" date="2026-07-05" /> <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>
+1
View File
@@ -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
+10 -1
View File
@@ -68,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
+11 -2
View File
@@ -13,7 +13,7 @@
Name: pwsp Name: pwsp
Version: 1.12.3 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
@@ -68,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
+12
View File
@@ -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),
+16
View File
@@ -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
+2 -1
View File
@@ -38,6 +38,7 @@ percent-encoding.workspace = true
name = "pwsp" name = "pwsp"
section = "sound" section = "sound"
priority = "optional" priority = "optional"
maintainer-scripts = "maintainer-scripts"
assets = [ assets = [
[ [
"target/release/pwsp-daemon", "target/release/pwsp-daemon",
@@ -65,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",
], ],
-11
View File
@@ -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
+22
View File
@@ -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 = "Всегда запоминать микрофон"
+21
View File
@@ -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
+18
View File
@@ -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
+20
View File
@@ -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
View File
@@ -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;
+23 -4
View File
@@ -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();
} }
} }
+39 -1
View File
@@ -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
View File
@@ -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?
+20 -7
View File
@@ -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();
+82 -2
View File
@@ -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")
}
}
+4 -1
View File
@@ -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(),
+4
View File
@@ -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>,
+30
View File
@@ -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![])
} }
+22 -1
View File
@@ -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,
} }
} }
+2 -2
View File
@@ -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"))
} }
+23 -7
View File
@@ -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()
} }
+24 -5
View File
@@ -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(" - ")
+8 -14
View File
@@ -27,18 +27,18 @@ 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.")
workspace_package_sec = workspace_package_match.group(1) if isinstance(workspace_package_match, re.Match):
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.")
current_version = version_match.group(1) if isinstance(version_match, re.Match):
current_version = version_match.group(1)
print(f"Current version detected: {current_version}") print(f"Current version detected: {current_version}")
# Get new version # Get new 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"