mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-28 06:21:23 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81da36f03c | |||
| 8bfa5daf78 | |||
| b816d2aa88 | |||
| 23ae562849 | |||
| e3bc1fd55f | |||
| 15964f205b | |||
| 6a0ac61033 | |||
| 4b802273f4 | |||
| baae7a1ccf | |||
| 654694cecf | |||
| 04ecf66beb | |||
| 0fe94f9112 |
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://boosty.to/arabian']
|
||||||
Generated
+765
-117
File diff suppressed because it is too large
Load Diff
+57
-12
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pwsp"
|
name = "pwsp"
|
||||||
version = "1.5.1"
|
version = "1.6.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["arabian"]
|
authors = ["arabian"]
|
||||||
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
|
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
|
||||||
@@ -18,16 +18,37 @@ async-trait = "0.1.89"
|
|||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
clap = { version = "4.5.55", default-features = false, features = ["std", "suggestions", "help", "usage", "error-context", "derive"] }
|
clap = { version = "4.5.58", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"suggestions",
|
||||||
|
"help",
|
||||||
|
"usage",
|
||||||
|
"error-context",
|
||||||
|
"derive",
|
||||||
|
] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
|
||||||
rodio = { version = "0.21.1", default-features = false, features = ["symphonia-all", "playback"] }
|
rodio = { version = "0.21.1", default-features = false, features = [
|
||||||
|
"symphonia-all",
|
||||||
|
"playback",
|
||||||
|
] }
|
||||||
pipewire = "0.9.2"
|
pipewire = "0.9.2"
|
||||||
rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"]}
|
rfd = { version = "0.17.2", default-features = false, features = [
|
||||||
|
"xdg-portal",
|
||||||
|
] }
|
||||||
|
opener = { version = "0.8.4", features = ["reveal"] }
|
||||||
|
|
||||||
egui = { version = "0.33.3", default-features = false, features = ["default_fonts", "rayon"] }
|
egui = { version = "0.33.3", default-features = false, features = [
|
||||||
eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] }
|
"default_fonts",
|
||||||
|
"rayon",
|
||||||
|
] }
|
||||||
|
eframe = { version = "0.33.3", default-features = false, features = [
|
||||||
|
"default_fonts",
|
||||||
|
"glow",
|
||||||
|
"x11",
|
||||||
|
"wayland",
|
||||||
|
] }
|
||||||
egui_material_icons = "0.5.0"
|
egui_material_icons = "0.5.0"
|
||||||
egui_dnd = "0.14.0"
|
egui_dnd = "0.14.0"
|
||||||
|
|
||||||
@@ -52,10 +73,34 @@ panic = "abort"
|
|||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
assets = [
|
assets = [
|
||||||
["target/release/pwsp-daemon", "usr/bin/", "755"],
|
[
|
||||||
["target/release/pwsp-cli", "usr/bin/", "755"],
|
"target/release/pwsp-daemon",
|
||||||
["target/release/pwsp-gui", "usr/bin/", "755"],
|
"usr/bin/",
|
||||||
["assets/pwsp-gui.desktop", "usr/share/applications/pwsp.desktop", "644"],
|
"755",
|
||||||
["assets/icon.png", "usr/share/icons/hicolor/256x256/apps/pwsp.png", "644"],
|
],
|
||||||
["assets/pwsp-daemon.service", "usr/lib/systemd/user/pwsp-daemon.service", "644"],
|
[
|
||||||
|
"target/release/pwsp-cli",
|
||||||
|
"usr/bin/",
|
||||||
|
"755",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"target/release/pwsp-gui",
|
||||||
|
"usr/bin/",
|
||||||
|
"755",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"assets/pwsp-gui.desktop",
|
||||||
|
"usr/share/applications/pwsp.desktop",
|
||||||
|
"644",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"assets/icon.png",
|
||||||
|
"usr/share/icons/hicolor/256x256/apps/pwsp.png",
|
||||||
|
"644",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"assets/pwsp-daemon.service",
|
||||||
|
"usr/lib/systemd/user/pwsp-daemon.service",
|
||||||
|
"644",
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -208,3 +208,6 @@ a [pull request](https://github.com/arabianq/pipewire-soundpad/pulls).
|
|||||||
|
|
||||||
This project is licensed under
|
This project is licensed under
|
||||||
the [MIT License](https://github.com/arabianq/pipewire-soundpad/blob/main/LICENSE).
|
the [MIT License](https://github.com/arabianq/pipewire-soundpad/blob/main/LICENSE).
|
||||||
|
|
||||||
|
# **🤖 AI Wiki**
|
||||||
|
[](https://deepwiki.com/arabianq/pipewire-soundpad)
|
||||||
|
|||||||
+1
-1
Submodule packages/aur/bin updated: 446179bb91...534cbc33ba
+1
-1
Submodule packages/aur/standart updated: d122d0719f...b381a90a50
@@ -4,7 +4,7 @@
|
|||||||
%global cargo_install_lib 0
|
%global cargo_install_lib 0
|
||||||
|
|
||||||
Name: pwsp
|
Name: pwsp
|
||||||
Version: 1.5.1
|
Version: 1.6.0
|
||||||
Release: %autorelease
|
Release: %autorelease
|
||||||
Summary: Lets you play audio files through your microphone
|
Summary: Lets you play audio files through your microphone
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ enum GetCommands {
|
|||||||
Input,
|
Input,
|
||||||
/// All audio inputs
|
/// All audio inputs
|
||||||
Inputs,
|
Inputs,
|
||||||
|
/// Version of the daemon
|
||||||
|
DaemonVersion,
|
||||||
/// Full player state
|
/// Full player state
|
||||||
FullState,
|
FullState,
|
||||||
}
|
}
|
||||||
@@ -148,6 +150,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
GetCommands::Tracks => Request::get_tracks(),
|
GetCommands::Tracks => Request::get_tracks(),
|
||||||
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::FullState => Request::get_full_state(),
|
GetCommands::FullState => Request::get_full_state(),
|
||||||
},
|
},
|
||||||
Commands::Set { parameter } => match parameter {
|
Commands::Set { parameter } => match parameter {
|
||||||
|
|||||||
+89
-19
@@ -4,13 +4,11 @@ use egui::{
|
|||||||
Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
|
Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
|
||||||
};
|
};
|
||||||
use egui_dnd::dnd;
|
use egui_dnd::dnd;
|
||||||
use egui_material_icons::icons;
|
use egui_material_icons::icons::*;
|
||||||
use pwsp::types::audio_player::TrackInfo;
|
use pwsp::types::{audio_player::TrackInfo, gui::AppState};
|
||||||
use pwsp::utils::gui::format_time_pair;
|
use pwsp::utils::gui::format_time_pair;
|
||||||
use std::{error::Error, time::Instant};
|
use std::{error::Error, time::Instant};
|
||||||
|
|
||||||
use pwsp::types::gui::AppState;
|
|
||||||
|
|
||||||
enum TrackAction {
|
enum TrackAction {
|
||||||
Pause(u32),
|
Pause(u32),
|
||||||
Resume(u32),
|
Resume(u32),
|
||||||
@@ -21,13 +19,13 @@ enum TrackAction {
|
|||||||
impl SoundpadGui {
|
impl SoundpadGui {
|
||||||
fn get_volume_icon(volume: f32) -> &'static str {
|
fn get_volume_icon(volume: f32) -> &'static str {
|
||||||
if volume > 0.7 {
|
if volume > 0.7 {
|
||||||
icons::ICON_VOLUME_UP
|
ICON_VOLUME_UP
|
||||||
} else if volume <= 0.0 {
|
} else if volume <= 0.0 {
|
||||||
icons::ICON_VOLUME_OFF
|
ICON_VOLUME_OFF
|
||||||
} else if volume < 0.3 {
|
} else if volume < 0.3 {
|
||||||
icons::ICON_VOLUME_MUTE
|
ICON_VOLUME_MUTE
|
||||||
} else {
|
} else {
|
||||||
icons::ICON_VOLUME_DOWN
|
ICON_VOLUME_DOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ impl SoundpadGui {
|
|||||||
ui.spacing_mut().item_spacing.y = 5.0;
|
ui.spacing_mut().item_spacing.y = 5.0;
|
||||||
// --------- Back Button and Title ----------
|
// --------- Back Button and Title ----------
|
||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
let back_button = Button::new(icons::ICON_ARROW_BACK).frame(false);
|
let back_button = Button::new(ICON_ARROW_BACK).frame(false);
|
||||||
let back_button_response = ui.add(back_button);
|
let back_button_response = ui.add(back_button);
|
||||||
if back_button_response.clicked() {
|
if back_button_response.clicked() {
|
||||||
self.app_state.show_settings = false;
|
self.app_state.show_settings = false;
|
||||||
@@ -83,6 +81,10 @@ impl SoundpadGui {
|
|||||||
self.config.save_to_file().ok();
|
self.config.save_to_file().ok();
|
||||||
}
|
}
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
|
ui.with_layout(Layout::bottom_up(Align::Min), |ui| {
|
||||||
|
ui.label(format!("GUI version: {}", env!("CARGO_PKG_VERSION")));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +171,9 @@ impl SoundpadGui {
|
|||||||
ui.horizontal_top(|ui| {
|
ui.horizontal_top(|ui| {
|
||||||
// ---------- Play Button ----------
|
// ---------- Play Button ----------
|
||||||
let play_button = Button::new(if track.paused {
|
let play_button = Button::new(if track.paused {
|
||||||
icons::ICON_PLAY_ARROW
|
ICON_PLAY_ARROW
|
||||||
} else {
|
} else {
|
||||||
icons::ICON_PAUSE
|
ICON_PAUSE
|
||||||
})
|
})
|
||||||
.corner_radius(15.0);
|
.corner_radius(15.0);
|
||||||
|
|
||||||
@@ -188,9 +190,9 @@ impl SoundpadGui {
|
|||||||
// ---------- Loop Button ----------
|
// ---------- Loop Button ----------
|
||||||
let loop_button = Button::new(
|
let loop_button = Button::new(
|
||||||
RichText::new(if track.looped {
|
RichText::new(if track.looped {
|
||||||
icons::ICON_REPEAT_ONE
|
ICON_REPEAT_ONE
|
||||||
} else {
|
} else {
|
||||||
icons::ICON_REPEAT
|
ICON_REPEAT
|
||||||
})
|
})
|
||||||
.size(18.0),
|
.size(18.0),
|
||||||
)
|
)
|
||||||
@@ -248,7 +250,7 @@ impl SoundpadGui {
|
|||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
|
||||||
// ---------- Stop Button ---------
|
// ---------- Stop Button ---------
|
||||||
let stop_button = Button::new(icons::ICON_CLOSE).frame(false);
|
let stop_button = Button::new(ICON_CLOSE).frame(false);
|
||||||
let stop_button_response = ui.add_sized([30.0, 30.0], stop_button);
|
let stop_button_response = ui.add_sized([30.0, 30.0], stop_button);
|
||||||
if stop_button_response.clicked() {
|
if stop_button_response.clicked() {
|
||||||
action = Some(TrackAction::Stop(track.id));
|
action = Some(TrackAction::Stop(track.id));
|
||||||
@@ -311,7 +313,7 @@ impl SoundpadGui {
|
|||||||
let path = item.clone();
|
let path = item.clone();
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
handle.ui(ui, |ui| {
|
handle.ui(ui, |ui| {
|
||||||
ui.label(icons::ICON_DRAG_INDICATOR);
|
ui.label(ICON_DRAG_INDICATOR);
|
||||||
});
|
});
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -333,18 +335,46 @@ impl SoundpadGui {
|
|||||||
self.open_dir(&path);
|
self.open_dir(&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
let delete_dir_button = Button::new(icons::ICON_DELETE).frame(false);
|
let delete_dir_button = Button::new(ICON_DELETE).frame(false);
|
||||||
let delete_dir_button_response =
|
let delete_dir_button_response =
|
||||||
ui.add_sized([18.0, 18.0], delete_dir_button);
|
ui.add_sized([18.0, 18.0], delete_dir_button);
|
||||||
if delete_dir_button_response.clicked() {
|
if delete_dir_button_response.clicked() {
|
||||||
self.app_state.dirs_to_remove.insert(path.clone());
|
self.app_state.dirs_to_remove.insert(path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context menu
|
||||||
|
dir_button_response.context_menu(|ui| {
|
||||||
|
if ui
|
||||||
|
.button(format!("{} {}", ICON_OPEN_IN_NEW, "Show"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.open_dir(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.button(format!(
|
||||||
|
"{} {}",
|
||||||
|
ICON_OPEN_IN_BROWSER, "Open in File Manager"
|
||||||
|
))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if let Err(e) = opener::open(&path) {
|
||||||
|
eprintln!("Failed to open file manager: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui.button(format!("{} {}", ICON_DELETE, "Remove")).clicked() {
|
||||||
|
self.app_state.dirs_to_remove.insert(path.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
self.app_state.dirs = dirs;
|
self.app_state.dirs = dirs;
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let add_dirs_button = Button::new(icons::ICON_ADD).frame(false);
|
let add_dirs_button = Button::new(ICON_ADD).frame(false);
|
||||||
let add_dirs_button_response = ui.add_sized([18.0, 18.0], add_dirs_button);
|
let add_dirs_button_response = ui.add_sized([18.0, 18.0], add_dirs_button);
|
||||||
if add_dirs_button_response.clicked() {
|
if add_dirs_button_response.clicked() {
|
||||||
self.add_dirs();
|
self.add_dirs();
|
||||||
@@ -416,8 +446,48 @@ impl SoundpadGui {
|
|||||||
self.play_file(&entry_path, false);
|
self.play_file(&entry_path, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.app_state.selected_file = Some(entry_path);
|
self.app_state.selected_file = Some(entry_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context menu
|
||||||
|
file_button_response.context_menu(|ui| {
|
||||||
|
if ui
|
||||||
|
.button(format!("{} {}", ICON_BOLT, "Play Solo"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.play_file(&entry_path, false);
|
||||||
|
self.app_state.selected_file = Some(entry_path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button(format!("{} {}", ICON_ADD, "Add New")).clicked() {
|
||||||
|
self.play_file(&entry_path, true);
|
||||||
|
self.app_state.selected_file = Some(entry_path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.button(format!("{} {}", ICON_SWAP_HORIZ, "Replace Last"))
|
||||||
|
.clicked()
|
||||||
|
&& let Some(last_track) = self.audio_player_state.tracks.last()
|
||||||
|
{
|
||||||
|
self.stop(Some(last_track.id));
|
||||||
|
self.play_file(&entry_path, true);
|
||||||
|
self.app_state.selected_file = Some(entry_path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.button(format!(
|
||||||
|
"{} {}",
|
||||||
|
ICON_OPEN_IN_BROWSER, "Show in File Manager"
|
||||||
|
))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if let Err(e) = opener::reveal(&entry_path) {
|
||||||
|
eprintln!("Failed to open file manager: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -486,7 +556,7 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
// ---------- Settings button ----------
|
// ---------- Settings button ----------
|
||||||
let settings_button =
|
let settings_button =
|
||||||
Button::new(icons::ICON_SETTINGS.atom_size(Vec2::new(18.0, 18.0))).frame(false);
|
Button::new(ICON_SETTINGS.atom_size(Vec2::new(18.0, 18.0))).frame(false);
|
||||||
let settings_button_response = ui.add_sized([18.0, 18.0], settings_button);
|
let settings_button_response = ui.add_sized([18.0, 18.0], settings_button);
|
||||||
if settings_button_response.clicked() {
|
if settings_button_response.clicked() {
|
||||||
self.app_state.show_settings = true;
|
self.app_state.show_settings = true;
|
||||||
|
|||||||
+10
-1
@@ -82,6 +82,8 @@ pub struct ToggleLoopCommand {
|
|||||||
pub id: Option<u32>,
|
pub id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GetDaemonVersionCommand {}
|
||||||
|
|
||||||
pub struct GetFullStateCommand {}
|
pub struct GetFullStateCommand {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -347,6 +349,13 @@ impl Executable for ToggleLoopCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Executable for GetDaemonVersionCommand {
|
||||||
|
async fn execute(&self) -> Response {
|
||||||
|
Response::new(true, env!("CARGO_PKG_VERSION"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Executable for GetFullStateCommand {
|
impl Executable for GetFullStateCommand {
|
||||||
async fn execute(&self) -> Response {
|
async fn execute(&self) -> Response {
|
||||||
@@ -374,7 +383,7 @@ impl Executable for GetFullStateCommand {
|
|||||||
tracks: audio_player.get_tracks(),
|
tracks: audio_player.get_tracks(),
|
||||||
volume: audio_player.volume,
|
volume: audio_player.volume,
|
||||||
current_input: current_input_nick,
|
current_input: current_input_nick,
|
||||||
all_inputs,
|
all_inputs: all_inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
Response::new(true, serde_json::to_string(&full_state).unwrap())
|
Response::new(true, serde_json::to_string(&full_state).unwrap())
|
||||||
|
|||||||
@@ -156,6 +156,10 @@ impl Request {
|
|||||||
Request::new("toggle_loop", args)
|
Request::new("toggle_loop", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_daemon_version() -> Self {
|
||||||
|
Request::new("get_daemon_version", vec![])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_full_state() -> Self {
|
pub fn get_full_state() -> Self {
|
||||||
Request::new("get_full_state", vec![])
|
Request::new("get_full_state", vec![])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
|
|||||||
Some(Box::new(SetLoopCommand { enabled, id }))
|
Some(Box::new(SetLoopCommand { enabled, id }))
|
||||||
}
|
}
|
||||||
"toggle_loop" => Some(Box::new(ToggleLoopCommand { id })),
|
"toggle_loop" => Some(Box::new(ToggleLoopCommand { id })),
|
||||||
|
"get_daemon_version" => Some(Box::new(GetDaemonVersionCommand {})),
|
||||||
"get_full_state" => Some(Box::new(GetFullStateCommand {})),
|
"get_full_state" => Some(Box::new(GetFullStateCommand {})),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user