Compare commits

...

7 Commits

Author SHA1 Message Date
arabianq dac9d53cef deps: update cargo-sources.json 2026-05-28 01:09:33 +03:00
arabianq 9da3799cd3 cargo update 2026-05-28 01:08:52 +03:00
arabianq d66369884c deps: update rodio 2026-05-28 01:08:13 +03:00
Tarasov Aleksandr 5e47e7d6fb feat(gui): support for soundpad:// uri (#123)
* feat(gui): support for soundpad:// uri

* fix: flatpak

* do not open gui when downloading file
2026-05-28 00:58:03 +03:00
Tarasov Aleksandr 695c83c9e6 feat(gui): theme selection (#122)
* fix: increment pkgrel to 2 for pwsp aur package

* feat(gui): implemented theme switching

* fix(gui): fixed incorrect colors in light theme

* fix(gui): fixed incorrect colors in light theme
2026-05-27 18:24:28 +03:00
dependabot[bot] 798a6d1887 chore(deps): bump system-fonts from 0.1.0 to 0.1.1 (#118)
* chore(deps): bump system-fonts from 0.1.0 to 0.1.1

Bumps [system-fonts](https://github.com/yijehyung/system-fonts) from 0.1.0 to 0.1.1.
- [Commits](https://github.com/yijehyung/system-fonts/commits)

---
updated-dependencies:
- dependency-name: system-fonts
  dependency-version: 0.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* deps: 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-05-27 18:03:02 +03:00
Ryan Lucas bb18175a30 fix: incorrect install icon name on AUR PKGBUILDs (#117)
Co-authored-by: Ryan Lucas <36653660+maxteer@users.noreply.github.com>
2026-05-25 15:04:01 +03:00
18 changed files with 1730 additions and 493 deletions
Generated
+677 -156
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -36,14 +36,14 @@ rfd = { version = "0.17.2", default-features = false, features = [
] } ] }
opener = { version = "0.8.4", features = ["reveal"] } opener = { version = "0.8.4", features = ["reveal"] }
system-fonts = "0.1.0" system-fonts = "0.1.1"
anyhow = "1.0.102" anyhow = "1.0.102"
rustix = { version = "1.1.4", features = ["process"] } 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 = "1a08f281c352622bd82b87b8731585245802d9cf", default-features = false, features = [ rodio = { git = "https://github.com/arabianq/rodio.git", rev = "33afba2a2d97bb730eb6537f09d2b3815bff0f33", default-features = false, features = [
"symphonia-all", "symphonia-all",
"symphonia-libopus", "symphonia-libopus",
"playback", "playback",
@@ -64,6 +64,9 @@ egui_extras = "0.34.1"
egui_material_icons = "0.6.0" egui_material_icons = "0.6.0"
egui_dnd = "0.15.0" egui_dnd = "0.15.0"
reqwest = "0.13.4"
percent-encoding = "2.3.2"
[[bin]] [[bin]]
name = "pwsp-daemon" name = "pwsp-daemon"
path = "src/bin/daemon.rs" path = "src/bin/daemon.rs"
+3 -2
View File
@@ -1,8 +1,9 @@
[Desktop Entry] [Desktop Entry]
Name=PWSP (Soundpad) Name=PWSP (Soundpad)
Comment=Let's you play audio files through you microphone Comment=Let's you play audio files through you microphone
Exec=pwsp-gui %u Exec=/usr/bin/pwsp-gui %u
Icon=pwsp Icon=pwsp
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Audio Categories=Audio
MimeType=x-scheme-handler/soundpad;
+44
View File
@@ -195,6 +195,50 @@ kz = "GUI нұсқасы: %{version}"
he = "גרסת ממשק משתמש: %{version}" he = "גרסת ממשק משתמש: %{version}"
pt-BR = "Versão da GUI: %{version}" pt-BR = "Versão da GUI: %{version}"
[gui.settings.theme.label]
en = "Color Scheme"
ru = "Цветовая схема"
es = "Esquema de color"
fr = "Schéma de couleurs"
zh = "配色方案"
ar = "نظام الألوان"
kz = "Түс схемасы"
he = "ערכת צבעים"
pt-BR = "Esquema de cores"
[gui.settings.theme.system]
en = "System"
ru = "Системная"
es = "Sistema"
fr = "Système"
zh = "系统"
ar = "النظام"
kz = "Жүйе"
he = "מערכת"
pt-BR = "Sistema"
[gui.settings.theme.light]
en = "Light"
ru = "Светлая"
es = "Claro"
fr = "Clair"
zh = "浅色"
ar = "فاتح"
kz = "Жарық"
he = "בהיר"
pt-BR = "Claro"
[gui.settings.theme.dark]
en = "Dark"
ru = "Тёмная"
es = "Oscuro"
fr = "Sombre"
zh = "暗色"
ar = "داكن"
kz = "Қараңғы"
he = "כהה"
pt-BR = "Escuro"
# ---------------- # ----------------
# Hotkeys # Hotkeys
# ---------------- # ----------------
+1 -1
View File
@@ -25,7 +25,7 @@ package() {
install -Dm755 "${srcdir}/pwsp-gui" "${pkgdir}/usr/bin/pwsp-gui" install -Dm755 "${srcdir}/pwsp-gui" "${pkgdir}/usr/bin/pwsp-gui"
install -Dm644 "$_srcsrc/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop" install -Dm644 "$_srcsrc/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
install -Dm644 "$_srcsrc/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/icon.png" install -Dm644 "$_srcsrc/assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
install -Dm644 "$_srcsrc/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service" install -Dm644 "$_srcsrc/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 -1
View File
@@ -1,7 +1,7 @@
pkgbase = pwsp pkgbase = pwsp
pkgdesc = Lets you play audio files through your microphone pkgdesc = Lets you play audio files through your microphone
pkgver = 1.9.1 pkgver = 1.9.1
pkgrel = 1 pkgrel = 2
url = https://github.com/arabianq/pipewire-soundpad url = https://github.com/arabianq/pipewire-soundpad
arch = any arch = any
license = MIT license = MIT
+2 -2
View File
@@ -2,7 +2,7 @@
pkgsubn=pwsp pkgsubn=pwsp
pkgname=pwsp pkgname=pwsp
pkgver=1.9.1 pkgver=1.9.1
pkgrel=1 pkgrel=2
pkgdesc="Lets you play audio files through your microphone" pkgdesc="Lets you play audio files through your microphone"
arch=('any') arch=('any')
url="https://github.com/arabianq/pipewire-soundpad" url="https://github.com/arabianq/pipewire-soundpad"
@@ -41,7 +41,7 @@ package() {
install -Dm755 "target/release/pwsp-gui" "${pkgdir}/usr/bin/pwsp-gui" install -Dm755 "target/release/pwsp-gui" "${pkgdir}/usr/bin/pwsp-gui"
install -Dm644 "assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop" install -Dm644 "assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
install -Dm644 "assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/icon.png" install -Dm644 "assets/icon.png" "${pkgdir}/usr/share/icons/hicolor/256x256/apps/pwsp.png"
install -Dm644 "assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service" install -Dm644 "assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
} }
File diff suppressed because one or more lines are too long
+11 -6
View File
@@ -2,21 +2,26 @@
import argparse import argparse
import subprocess import subprocess
import sys
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) == 2 and sys.argv[1].startswith("soundpad://"):
subprocess.Popen(["pwsp-gui", sys.argv[1]])
sys.exit(0)
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="PWSP Flatpak", prog="PWSP Flatpak", add_help=True, exit_on_error=True
add_help=True,
exit_on_error=True
) )
subparsers = parser.add_subparsers(dest="command") subparsers = parser.add_subparsers(dest="command")
cli_parser = subparsers.add_parser("cli", add_help=False, prefix_chars=" ") cli_parser = subparsers.add_parser("cli", add_help=False, prefix_chars=" ")
cli_parser.add_argument("args", nargs=argparse.REMAINDER, help="Arguments for pwsp-cli") cli_parser.add_argument(
"args", nargs=argparse.REMAINDER, help="Arguments for pwsp-cli"
)
daemon_parser = subparsers.add_parser("daemon", add_help=True) daemon_parser = subparsers.add_parser("daemon", add_help=True)
daemon_group = daemon_parser.add_mutually_exclusive_group(required=True) daemon_group = daemon_parser.add_mutually_exclusive_group(required=True)
daemon_group.add_argument("--start", action="store_true", help="Start pwps-daemon") daemon_group.add_argument("--start", action="store_true", help="Start pwsp-daemon")
daemon_group.add_argument("--kill", action="store_true", help="Kill pwsp-daemon") daemon_group.add_argument("--kill", action="store_true", help="Kill pwsp-daemon")
args = parser.parse_args() args = parser.parse_args()
@@ -32,4 +37,4 @@ if __name__ == "__main__":
if args.start: if args.start:
subprocess.Popen("pwsp-daemon") subprocess.Popen("pwsp-daemon")
elif args.kill: elif args.kill:
subprocess.Popen(["pwsp-cli", "action", "kill"]) subprocess.Popen(["pwsp-cli", "action", "kill"])
@@ -7,3 +7,4 @@ Terminal=false
Type=Application Type=Application
Categories=AudioVideo;Audio; Categories=AudioVideo;Audio;
Keywords=soundpad;pipewire;audio; Keywords=soundpad;pipewire;audio;
MimeType=x-scheme-handler/soundpad;
+19 -2
View File
@@ -1,14 +1,31 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use eframe::{App, Frame as EFrame}; use eframe::{App, Frame as EFrame};
use egui::{CentralPanel, Context}; use egui::{CentralPanel, Context, ThemePreference};
use pwsp::{ use pwsp::{
types::socket::Request, types::{config::PreferredTheme, socket::Request},
utils::{daemon::get_daemon_config, gui::make_request_async}, utils::{daemon::get_daemon_config, gui::make_request_async},
}; };
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
impl App for SoundpadGui { impl App for SoundpadGui {
fn logic(&mut self, ctx: &Context, _frame: &mut EFrame) { fn logic(&mut self, ctx: &Context, _frame: &mut EFrame) {
// Update theme
let current_theme = match ctx.options(|r| r.theme_preference) {
ThemePreference::System => PreferredTheme::System,
ThemePreference::Light => PreferredTheme::Light,
ThemePreference::Dark => PreferredTheme::Dark,
};
if !self.config.preferred_theme.eq(&current_theme) {
ctx.options_mut(|w| {
w.theme_preference = match self.config.preferred_theme {
PreferredTheme::System => ThemePreference::System,
PreferredTheme::Light => ThemePreference::Light,
PreferredTheme::Dark => ThemePreference::Dark,
}
})
}
// Remove directories // Remove directories
for path in self.app_state.dirs_to_remove.drain() { for path in self.app_state.dirs_to_remove.drain() {
self.app_state.dirs.retain(|x| x != &path); self.app_state.dirs.retain(|x| x != &path);
+5 -5
View File
@@ -76,16 +76,16 @@ impl SoundpadGui {
.map(|s| s.to_string_lossy().to_string()) .map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string_lossy().to_string()); .unwrap_or_else(|| path.to_string_lossy().to_string());
let mut dir_button_text = RichText::new(name.clone()); let mut dir_button =
Button::new(RichText::new(name.clone()).atom_max_width(area_size.x))
.frame(false);
if let Some(current_dir) = &self.app_state.current_dir if let Some(current_dir) = &self.app_state.current_dir
&& current_dir.eq(&*path) && current_dir.eq(&*path)
{ {
dir_button_text = dir_button_text.color(Color32::WHITE); dir_button = dir_button.selected(true);
} }
let dir_button =
Button::new(dir_button_text.atom_max_width(area_size.x)).frame(false);
let dir_button_response = ui.add(dir_button); let dir_button_response = ui.add(dir_button);
if dir_button_response.clicked() { if dir_button_response.clicked() {
dir_to_open = Some(path.clone()); dir_to_open = Some(path.clone());
+1 -2
View File
@@ -1,5 +1,5 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use egui::{Button, CollapsingHeader, Color32, FontFamily, Label, RichText, Slider, Ui}; use egui::{Button, CollapsingHeader, FontFamily, Label, RichText, Slider, Ui};
use egui_material_icons::icons::*; use egui_material_icons::icons::*;
use pwsp::types::{audio_player::TrackInfo, gui::AppState}; use pwsp::types::{audio_player::TrackInfo, gui::AppState};
use pwsp::utils::gui::format_time_pair; use pwsp::utils::gui::format_time_pair;
@@ -32,7 +32,6 @@ impl SoundpadGui {
.to_str() .to_str()
.unwrap_or_default(), .unwrap_or_default(),
) )
.color(Color32::WHITE)
.family(FontFamily::Monospace), .family(FontFamily::Monospace),
) )
.default_open(true) .default_open(true)
+5 -12
View File
@@ -146,32 +146,28 @@ impl SoundpadGui {
ui.label( ui.label(
RichText::new(t!("gui.hotkeys.column_slot")) RichText::new(t!("gui.hotkeys.column_slot"))
.strong() .strong()
.monospace() .monospace(),
.color(Color32::LIGHT_GRAY),
); );
}); });
header.col(|ui| { header.col(|ui| {
ui.label( ui.label(
RichText::new(t!("gui.hotkeys.column_sound")) RichText::new(t!("gui.hotkeys.column_sound"))
.strong() .strong()
.monospace() .monospace(),
.color(Color32::LIGHT_GRAY),
); );
}); });
header.col(|ui| { header.col(|ui| {
ui.label( ui.label(
RichText::new(t!("gui.hotkeys.column_key_chord")) RichText::new(t!("gui.hotkeys.column_key_chord"))
.strong() .strong()
.monospace() .monospace(),
.color(Color32::LIGHT_GRAY),
); );
}); });
header.col(|ui| { header.col(|ui| {
ui.label( ui.label(
RichText::new(t!("gui.hotkeys.column_actions")) RichText::new(t!("gui.hotkeys.column_actions"))
.strong() .strong()
.monospace() .monospace(),
.color(Color32::LIGHT_GRAY),
); );
}); });
}) })
@@ -180,10 +176,7 @@ impl SoundpadGui {
body.row(30.0, |mut row| { body.row(30.0, |mut row| {
row.col(|_| {}); row.col(|_| {});
row.col(|ui| { row.col(|ui| {
ui.label( ui.label(RichText::new(t!("gui.hotkeys.no_hotkeys_configured")));
RichText::new(t!("gui.hotkeys.no_hotkeys_configured"))
.color(Color32::GRAY),
);
}); });
row.col(|_| {}); row.col(|_| {});
row.col(|_| {}); row.col(|_| {});
+36 -1
View File
@@ -1,6 +1,7 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use egui::{Align, Button, Color32, Layout, RichText, Ui}; use egui::{Align, Button, Color32, ComboBox, Layout, RichText, Ui};
use egui_material_icons::icons::ICON_ARROW_BACK; use egui_material_icons::icons::ICON_ARROW_BACK;
use pwsp::types::config::PreferredTheme;
use rust_i18n::t; use rust_i18n::t;
impl SoundpadGui { impl SoundpadGui {
@@ -53,6 +54,40 @@ impl SoundpadGui {
} }
// -------------------------------- // --------------------------------
ui.separator();
// ---------- Selectors -----------
let mut selected_theme = self.config.preferred_theme.clone();
ComboBox::from_label(t!("gui.settings.theme.label"))
.selected_text(match self.config.preferred_theme {
PreferredTheme::System => t!("gui.settings.theme.system"),
PreferredTheme::Light => t!("gui.settings.theme.light"),
PreferredTheme::Dark => t!("gui.settings.theme.dark"),
})
.show_ui(ui, |ui| {
ui.selectable_value(
&mut selected_theme,
PreferredTheme::System,
t!("gui.settings.theme.system"),
);
ui.selectable_value(
&mut selected_theme,
PreferredTheme::Light,
t!("gui.settings.theme.light"),
);
ui.selectable_value(
&mut selected_theme,
PreferredTheme::Dark,
t!("gui.settings.theme.dark"),
);
});
if selected_theme != self.config.preferred_theme {
self.config.preferred_theme = selected_theme;
self.config.save_to_file().ok();
}
// --------------------------------
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",
+48 -2
View File
@@ -1,7 +1,9 @@
mod gui; mod gui;
use anyhow::Result; use anyhow::{Context, Result};
use pwsp::utils::gui::ensure_pwsp_audio_dir;
use rust_i18n::i18n; use rust_i18n::i18n;
use std::{env, path::PathBuf};
i18n!("locales", fallback = "en"); i18n!("locales", fallback = "en");
@@ -10,5 +12,49 @@ async fn main() -> Result<()> {
let locale = sys_locale::get_locale().unwrap_or(String::from("en-US")); let locale = sys_locale::get_locale().unwrap_or(String::from("en-US"));
rust_i18n::set_locale(&locale); rust_i18n::set_locale(&locale);
gui::run().await let args = env::args().skip(1).collect::<Vec<String>>();
if let Some(uri) = args.first() {
match download_audio_from_url(uri).await {
Ok(path) => println!("Successfully downloaded to: {:?}", path),
Err(e) => eprintln!("Error downloading file: {}", e),
}
} else {
gui::run().await?;
}
Ok(())
}
async fn download_audio_from_url(uri: &str) -> Result<PathBuf> {
let prefix = "soundpad://sound/url/";
let target_url = uri
.strip_prefix(prefix)
.ok_or_else(|| anyhow::anyhow!("URI does not containt an expected prefix: {}", prefix))?;
let file_name_encoded = target_url
.split('/')
.next_back()
.unwrap_or("downloaded_audio.mp3");
let file_name = percent_encoding::percent_decode_str(file_name_encoded)
.decode_utf8()
.unwrap_or_else(|_| file_name_encoded.into())
.into_owned();
let save_path = ensure_pwsp_audio_dir().join(file_name);
let response = reqwest::get(target_url)
.await?
.error_for_status()
.context("Failed to fetch file")?;
let bytes = response.bytes().await?;
tokio::fs::write(&save_path, bytes)
.await
.context("Failed to save file to disk")?;
Ok(save_path)
} }
+16 -2
View File
@@ -1,4 +1,7 @@
use crate::{types::socket::Request, utils::config::get_config_path}; use crate::{
types::socket::Request,
utils::{config::get_config_path, gui::ensure_pwsp_audio_dir},
};
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf}; use std::{collections::HashMap, fs, path::PathBuf};
@@ -35,6 +38,13 @@ impl DaemonConfig {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PreferredTheme {
System,
Light,
Dark,
}
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct GuiConfig { pub struct GuiConfig {
@@ -47,6 +57,8 @@ pub struct GuiConfig {
pub pause_on_exit: bool, pub pause_on_exit: bool,
pub dirs: Vec<PathBuf>, pub dirs: Vec<PathBuf>,
pub preferred_theme: PreferredTheme,
} }
impl Default for GuiConfig { impl Default for GuiConfig {
@@ -60,7 +72,9 @@ impl Default for GuiConfig {
save_scale_factor: false, save_scale_factor: false,
pause_on_exit: false, pause_on_exit: false,
dirs: vec![], dirs: vec![ensure_pwsp_audio_dir()],
preferred_theme: PreferredTheme::System,
} }
} }
} }
+12
View File
@@ -9,6 +9,7 @@ use crate::{
}; };
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use std::{ use std::{
path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
time::Instant, time::Instant,
}; };
@@ -36,6 +37,17 @@ pub fn make_request_async(request: Request) {
}); });
} }
pub fn ensure_pwsp_audio_dir() -> PathBuf {
let audio_dir = dirs::audio_dir().unwrap_or("~/Music".into());
let pwsp_audio_dir = audio_dir.join("PWSP");
if !pwsp_audio_dir.exists() {
std::fs::create_dir_all(&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 {
fn format_time(seconds: f32) -> String { fn format_time(seconds: f32) -> String {
let total_seconds = seconds.round() as u32; let total_seconds = seconds.round() as u32;