Compare commits

...

8 Commits

Author SHA1 Message Date
arabianq 809b1a8490 fix: update Copr build command to use correct repository name 2026-06-04 20:51:36 +03:00
arabianq d1a5275173 fix: remove redundant name parameter from Copr build command 2026-06-04 20:42:11 +03:00
arabianq e67f174a59 change version to 1.12.0 2026-06-04 20:18:56 +03:00
arabianq 6545431ac2 deps: update cargo-sources.json 2026-06-04 20:17:27 +03:00
arabianq 026ef97a72 deps: cargo update 2026-06-04 20:16:59 +03:00
arabianq 9f833cc30b deps: update rodio 2026-06-04 20:16:35 +03:00
Tarasov Aleksandr 410a2c7959 feat(gui): sorting options (#134)
* feat(gui): added an ability to copy ```pwsp-cli action play``` command for every sound

* feat(gui): added files sorting options
2026-06-04 20:14:28 +03:00
Tarasov Aleksandr c173e602ad feat(gui): added an ability to copy ``pwsp-cli action play`` command for every sound (#133) 2026-06-04 20:06:35 +03:00
14 changed files with 279 additions and 48 deletions
+1 -2
View File
@@ -257,6 +257,5 @@ jobs:
copr-cli buildscm --clone-url https://github.com/arabianq/pipewire-soundpad.git \
--commit ${{ needs.prepare.outputs.tag }} \
--spec packages/rpm/pwsp.spec \
--name pwsp \
arabianq/pipewire-soundpad
arabianq/pwsp
Generated
+10 -10
View File
@@ -776,7 +776,7 @@ dependencies = [
[[package]]
name = "cpal"
version = "0.18.0"
source = "git+https://github.com/RustAudio/cpal#d2a268839bf9ada7c2477c8a5f9483c28aa12d0c"
source = "git+https://github.com/RustAudio/cpal#004897773f17fa15afbc3270b7cca37cfbbdef2a"
dependencies = [
"alsa",
"block2 0.6.2",
@@ -2306,9 +2306,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.31"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "lru-slab"
@@ -3280,7 +3280,7 @@ checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
[[package]]
name = "pwsp-cli"
version = "1.11.0"
version = "1.12.0"
dependencies = [
"anyhow",
"clap",
@@ -3291,7 +3291,7 @@ dependencies = [
[[package]]
name = "pwsp-daemon"
version = "1.11.0"
version = "1.12.0"
dependencies = [
"anyhow",
"clap",
@@ -3303,7 +3303,7 @@ dependencies = [
[[package]]
name = "pwsp-gui"
version = "1.11.0"
version = "1.12.0"
dependencies = [
"anyhow",
"eframe",
@@ -3327,7 +3327,7 @@ dependencies = [
[[package]]
name = "pwsp-lib"
version = "1.11.0"
version = "1.12.0"
dependencies = [
"anyhow",
"async-trait",
@@ -3686,7 +3686,7 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rodio"
version = "0.22.2"
source = "git+https://github.com/arabianq/rodio.git?rev=a634dd471e9d59196e19bf01323fb45f2f899821#a634dd471e9d59196e19bf01323fb45f2f899821"
source = "git+https://github.com/arabianq/rodio.git?rev=c6a81b5a46e00a6a682c0c431dff62e86f57d819#c6a81b5a46e00a6a682c0c431dff62e86f57d819"
dependencies = [
"cpal",
"dasp_sample",
@@ -5967,9 +5967,9 @@ checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
+2 -2
View File
@@ -8,7 +8,7 @@ members = [
resolver = "2"
[workspace.package]
version = "1.11.0"
version = "1.12.0"
edition = "2024"
authors = ["arabian"]
homepage = "https://pwsp.arabianq.ru"
@@ -52,7 +52,7 @@ rustix = { version = "1.1.4", features = ["process"] }
rust-i18n = "4.0.0"
sys-locale = "0.3.2"
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "a634dd471e9d59196e19bf01323fb45f2f899821", default-features = false, features = [
rodio = { git = "https://github.com/arabianq/rodio.git", rev = "c6a81b5a46e00a6a682c0c431dff62e86f57d819", default-features = false, features = [
"symphonia-all",
"symphonia-libopus",
"playback",
+4 -4
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp-bin
pkgdesc = Lets you play audio files through your microphone (Pre-built binaries)
pkgver = 1.11.0
pkgver = 1.12.0
pkgrel = 1
url = https://github.com/arabianq/pipewire-soundpad
arch = x86_64
@@ -10,11 +10,11 @@ pkgbase = pwsp-bin
depends = alsa-lib
provides = pwsp
conflicts = pwsp
source = pipewire-soundpad-1.11.0.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.11.0.tar.gz
source = pipewire-soundpad-1.12.0.tar.gz :: https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.0.tar.gz
sha256sums = SKIP
source_x86_64 = pwsp-1.11.0-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.11.0/pwsp-v1.11.0-linux-x64.zip
source_x86_64 = pwsp-1.12.0-x86_64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.0/pwsp-v1.12.0-linux-x64.zip
sha256sums_x86_64 = SKIP
source_aarch64 = pwsp-1.11.0-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.11.0/pwsp-v1.11.0-linux-arm64.zip
source_aarch64 = pwsp-1.12.0-aarch64.zip :: https://github.com/arabianq/pipewire-soundpad/releases/download/v1.12.0/pwsp-v1.12.0-linux-arm64.zip
sha256sums_aarch64 = SKIP
pkgname = pwsp-bin
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgname=pwsp-bin
_pkgname=pipewire-soundpad
pkgver=1.11.0
pkgver=1.12.0
pkgrel=1
pkgdesc="Lets you play audio files through your microphone (Pre-built binaries)"
arch=('x86_64' 'aarch64')
+2 -2
View File
@@ -1,6 +1,6 @@
pkgbase = pwsp
pkgdesc = Lets you play audio files through your microphone
pkgver = 1.11.0
pkgver = 1.12.0
pkgrel = 1
url = https://github.com/arabianq/pipewire-soundpad
arch = x86_64
@@ -12,7 +12,7 @@ pkgbase = pwsp
makedepends = cmake
makedepends = pipewire
makedepends = alsa-lib
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.11.0.tar.gz
source = https://github.com/arabianq/pipewire-soundpad/archive/refs/tags/v1.12.0.tar.gz
sha256sums = SKIP
pkgname = pwsp
+1 -1
View File
@@ -1,7 +1,7 @@
# Maintainer: Alexander Tarasov <a.tevg@ya.ru>
pkgsubn=pwsp
pkgname=pwsp
pkgver=1.11.0
pkgver=1.12.0
pkgrel=1
pkgdesc="Lets you play audio files through your microphone"
arch=('x86_64' 'aarch64')
File diff suppressed because one or more lines are too long
@@ -25,7 +25,7 @@
<name>arabian</name>
</developer>
<releases>
<release version="1.11.0" date="2026-06-02" />
<release version="1.12.0" date="2026-06-04" />
</releases>
<content_rating type="oars-1.1" />
</component>
+1 -1
View File
@@ -13,7 +13,7 @@
Name: pwsp
Version: 1.11.0
Version: 1.12.0
Release: %autorelease
Summary: Lets you play audio files through your microphone
+66
View File
@@ -70,6 +70,61 @@ kz = "Жою"
he = "הסר"
pt-BR = "Remover"
[gui.context.dirs.sort_by]
en = "Sort by"
ru = "Сортировка"
es = "Ordenar por"
fr = "Trier par"
zh = "排序方式"
ar = "ترتيب حسب"
kz = "Сұрыптау"
he = "מיין לפי"
pt-BR = "Ordenar por"
[gui.sort.alpha_asc]
en = "Alphabetical (A-Z)"
ru = "По алфавиту (А-Я)"
es = "Alfabético (A-Z)"
fr = "Alphabétique (A-Z)"
zh = "字母顺序 (A-Z)"
ar = "أبجدي (A-Z)"
kz = "Әліпби бойынша (А-Я)"
he = "אלפביתי (A-Z)"
pt-BR = "Alfabético (A-Z)"
[gui.sort.alpha_desc]
en = "Alphabetical (Z-A)"
ru = "По алфавиту (Я-А)"
es = "Alfabético (Z-A)"
fr = "Alphabétique (Z-A)"
zh = "字母顺序 (Z-A)"
ar = "أبجدي (Z-A)"
kz = "Әліпби бойынша (Я-А)"
he = "אלפביתי (Z-A)"
pt-BR = "Alfabético (Z-A)"
[gui.sort.date_newest]
en = "Date modified (Newest first)"
ru = "Дата изменения (Сначала новые)"
es = "Fecha de modificación (Más nuevo primero)"
fr = "Date de modification (Plus récent en premier)"
zh = "修改日期 (最新优先)"
ar = "تاريخ التعديل (الأحدث أولاً)"
kz = "Өзгертілген күні (Жаңалары бірінші)"
he = "תאריך שינוי (החדש ביותר ראשון)"
pt-BR = "Data de modificação (Mais novo primeiro)"
[gui.sort.date_oldest]
en = "Date modified (Oldest first)"
ru = "Дата изменения (Сначала старые)"
es = "Fecha de modificación (Más antiguo primero)"
fr = "Date de modification (Plus ancien en premier)"
zh = "修改日期 (最旧优先)"
ar = "تاريخ التعديل (الأقدم أولاً)"
kz = "Өзгертілген күні (Ескілері бірінші)"
he = "תאריך שינוי (הישן ביותר ראשון)"
pt-BR = "Data de modificação (Mais antigo primeiro)"
[gui.context.files.play_solo]
en = "Play Solo"
ru = "Играть"
@@ -125,6 +180,17 @@ kz = "Ыстық пернені тағайындау"
he = "הקצה מקש קיצור"
pt-BR = "Definir tecla de atalho"
[gui.context.files.copy_cli_command]
en = "Copy PWSP-CLI command"
ru = "Скопировать команду для PWSP-CLI"
es = "Copiar comando de PWSP-CLI"
fr = "Copier la commande PWSP-CLI"
zh = "复制 PWSP-CLI 命令"
ar = "نسخ أمر PWSP-CLI"
kz = "PWSP-CLI командасын көшіру"
he = "העתק פקודת PWSP-CLI"
pt-BR = "Copiar comando PWSP-CLI"
# ----------------
# Settings
# ----------------
+22 -1
View File
@@ -159,6 +159,13 @@ impl SoundpadGui {
pub fn get_filtered_files(&self) -> Vec<PathBuf> {
let mut files: Vec<PathBuf> = self.app_state.listed_files.iter().cloned().collect();
let sort_order = self
.app_state
.current_dir
.as_ref()
.map(|d| self.config.get_sort_order(d))
.unwrap_or_default();
files.sort_by(|a, b| {
let a_is_dir = a.is_dir();
let b_is_dir = b.is_dir();
@@ -167,7 +174,7 @@ impl SoundpadGui {
} else if !a_is_dir && b_is_dir {
Ordering::Greater
} else {
a.cmp(b)
sort_order.compare(a, b)
}
});
@@ -334,5 +341,19 @@ mod tests {
let filtered_search = gui.get_filtered_files();
assert_eq!(filtered_search.len(), 1);
assert_eq!(filtered_search[0], file_c);
// Test sort order descending
gui.app_state.current_dir = Some(PathBuf::from("dummy_dir"));
gui.config.dirs_settings.insert(
PathBuf::from("dummy_dir"),
pwsp_lib::types::config::DirSettings {
sort_order: pwsp_lib::types::config::SortOrder::AlphabeticalDesc,
},
);
gui.app_state.search_query = String::new();
let filtered_desc = gui.get_filtered_files();
assert_eq!(filtered_desc.len(), 2);
assert_eq!(filtered_desc[0], file_c);
assert_eq!(filtered_desc[1], file_b);
}
}
+88 -4
View File
@@ -5,7 +5,10 @@ use egui::{
};
use egui_dnd::dnd;
use egui_material_icons::icons::*;
use pwsp_lib::types::{gui::AppState, gui::AudioPlayerState};
use pwsp_lib::types::{
config::{GuiConfig, SortOrder},
gui::{AppState, AudioPlayerState},
};
use rust_i18n::t;
use std::{cmp::Ordering, path::Path, path::PathBuf};
@@ -135,6 +138,65 @@ impl SoundpadGui {
{
self.app_state.dirs_to_remove.insert(path.clone());
}
ui.separator();
ui.label(t!("gui.context.dirs.sort_by"));
let current_order = self
.config
.dirs_settings
.get(path)
.map(|s| s.sort_order)
.unwrap_or_default();
let mut new_order = None;
if ui
.radio(
current_order == SortOrder::AlphabeticalAsc,
t!("gui.sort.alpha_asc"),
)
.clicked()
{
new_order = Some(SortOrder::AlphabeticalAsc);
}
if ui
.radio(
current_order == SortOrder::AlphabeticalDesc,
t!("gui.sort.alpha_desc"),
)
.clicked()
{
new_order = Some(SortOrder::AlphabeticalDesc);
}
if ui
.radio(
current_order == SortOrder::DateModifiedNewest,
t!("gui.sort.date_newest"),
)
.clicked()
{
new_order = Some(SortOrder::DateModifiedNewest);
}
if ui
.radio(
current_order == SortOrder::DateModifiedOldest,
t!("gui.sort.date_oldest"),
)
.clicked()
{
new_order = Some(SortOrder::DateModifiedOldest);
}
if let Some(order) = new_order {
self.config
.dirs_settings
.entry(path.clone())
.or_default()
.sort_order = order;
self.config.save_to_file().ok();
self.app_state.dir_cache.remove(path);
self.open_dir(path);
}
});
});
});
@@ -192,6 +254,7 @@ impl SoundpadGui {
Self::draw_tree_node(
ui,
entry_path,
&self.config,
&mut self.app_state,
&self.audio_player_state,
&mut actions,
@@ -226,6 +289,7 @@ impl SoundpadGui {
fn draw_tree_node_dir(
ui: &mut Ui,
path: std::path::PathBuf,
config: &GuiConfig,
app_state: &mut AppState,
audio_player_state: &AudioPlayerState,
actions: &mut Vec<FileAction>,
@@ -247,6 +311,7 @@ impl SoundpadGui {
read.push(entry.path());
}
}
let sort_order = config.get_sort_order(&path);
read.sort_by(|a, b| {
let a_is_dir = a.is_dir();
let b_is_dir = b.is_dir();
@@ -255,7 +320,7 @@ impl SoundpadGui {
} else if !a_is_dir && b_is_dir {
Ordering::Greater
} else {
a.cmp(b)
sort_order.compare(a, b)
}
});
app_state.dir_cache.insert(path.clone(), read.clone());
@@ -287,7 +352,7 @@ impl SoundpadGui {
}
}
}
Self::draw_tree_node(ui, child, app_state, audio_player_state, actions);
Self::draw_tree_node(ui, child, config, app_state, audio_player_state, actions);
}
});
}
@@ -412,6 +477,24 @@ impl SoundpadGui {
actions.push(FileAction::AssignHotkey(path.clone()));
ui.close();
}
ui.separator();
if ui
.button(format!(
"{} {}",
ICON_FILE_COPY.codepoint,
t!("gui.context.files.copy_cli_command")
))
.clicked()
{
ui.ctx().copy_text(format!(
"pwsp-cli action play \"{}\"",
path.to_string_lossy()
.replace('\\', "\\\\")
.replace('"', "\\\"")
));
}
});
});
}
@@ -419,12 +502,13 @@ impl SoundpadGui {
fn draw_tree_node(
ui: &mut Ui,
path: std::path::PathBuf,
config: &GuiConfig,
app_state: &mut AppState,
audio_player_state: &AudioPlayerState,
actions: &mut Vec<FileAction>,
) {
if path.is_dir() {
Self::draw_tree_node_dir(ui, path, app_state, audio_player_state, actions);
Self::draw_tree_node_dir(ui, path, config, app_state, audio_player_state, actions);
} else {
Self::draw_tree_node_file(ui, path, app_state, audio_player_state, actions);
}
+62 -1
View File
@@ -4,7 +4,13 @@ use crate::{
};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf};
use std::{
cmp::Ordering,
collections::HashMap,
fs,
path::{Path, PathBuf},
time::SystemTime,
};
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(default)]
@@ -45,6 +51,21 @@ pub enum PreferredTheme {
Dark,
}
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum SortOrder {
#[default]
AlphabeticalAsc,
AlphabeticalDesc,
DateModifiedNewest,
DateModifiedOldest,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct DirSettings {
pub sort_order: SortOrder,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct GuiConfig {
@@ -57,10 +78,38 @@ pub struct GuiConfig {
pub pause_on_exit: bool,
pub dirs: Vec<PathBuf>,
pub dirs_settings: HashMap<PathBuf, DirSettings>,
pub preferred_theme: PreferredTheme,
}
impl SortOrder {
pub fn compare(&self, a: &Path, b: &Path) -> Ordering {
match self {
SortOrder::AlphabeticalAsc => a.cmp(b),
SortOrder::AlphabeticalDesc => b.cmp(a),
SortOrder::DateModifiedNewest => {
let a_time = fs::metadata(a)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
let b_time = fs::metadata(b)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
b_time.cmp(&a_time)
}
SortOrder::DateModifiedOldest => {
let a_time = fs::metadata(a)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
let b_time = fs::metadata(b)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
a_time.cmp(&b_time)
}
}
}
}
impl Default for GuiConfig {
fn default() -> Self {
GuiConfig {
@@ -75,11 +124,23 @@ impl Default for GuiConfig {
dirs: vec![ensure_pwsp_audio_dir()],
preferred_theme: PreferredTheme::System,
dirs_settings: HashMap::new(),
}
}
}
impl GuiConfig {
pub fn get_sort_order(&self, path: &Path) -> SortOrder {
let mut current = Some(path);
while let Some(p) = current {
if let Some(settings) = self.dirs_settings.get(p) {
return settings.sort_order;
}
current = p.parent();
}
SortOrder::default()
}
pub fn save_to_file(&mut self) -> Result<()> {
let config_path = get_config_path()?.join("gui.json");