mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-07-27 06:06:59 +00:00
Compare 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 |
@@ -19,12 +19,12 @@ jobs:
|
|||||||
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
|
||||||
|
|||||||
Generated
+402
-310
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -8,7 +8,7 @@ members = [
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.12.4"
|
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",
|
||||||
|
|||||||
@@ -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.4
|
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.4.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.4.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.4-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.4/pwsp-v1.12.4-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.4-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.4/pwsp-v1.12.4-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,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.4
|
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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.4
|
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.4.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,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.4
|
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"
|
||||||
}
|
}
|
||||||
|
|||||||
+529
-425
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.4" date="2026-07-07" />
|
<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>
|
||||||
@@ -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
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
|
|
||||||
Name: pwsp
|
Name: pwsp
|
||||||
Version: 1.12.4
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
+2
-1
@@ -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",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ async fn download_audio_from_url(uri: &str) -> Result<PathBuf> {
|
|||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.unwrap_or("downloaded_audio.mp3");
|
.unwrap_or("downloaded_audio.mp3");
|
||||||
|
|
||||||
let save_path = ensure_pwsp_audio_dir().join(sanitized_file_name);
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(|| {
|
||||||
let config = DaemonConfig::default();
|
Arc::new(Mutex::new(DaemonConfig::load_from_file().unwrap_or_else(
|
||||||
config.save_to_file().ok();
|
|_| {
|
||||||
config
|
let config = DaemonConfig::default();
|
||||||
|
config.save_to_file().ok();
|
||||||
|
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,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"
|
||||||
|
|||||||
Reference in New Issue
Block a user