Compare commits

...

28 Commits

Author SHA1 Message Date
arabianq 81da36f03c bump version to 1.6.0 2026-02-14 15:50:06 +03:00
arabianq 8bfa5daf78 feat: show pwsp-gui version in settings 2026-02-14 15:46:56 +03:00
arabianq b816d2aa88 feat: get daemon's version using pwsp-cli
pwsp-cli get daemon-version
2026-02-14 15:43:17 +03:00
arabianq 23ae562849 refactor: better Cargo.toml formatting 2026-02-14 15:20:03 +03:00
arabianq e3bc1fd55f deps: cargo update 2026-02-14 15:16:43 +03:00
arabianq 15964f205b deps: bump clap version to 4.5.58 2026-02-14 15:15:36 +03:00
arabianq 6a0ac61033 refactor: removed icons:: everywhere 2026-02-14 15:14:03 +03:00
arabianq 4b802273f4 Merge branch 'main' of github.com:arabianq/pipewire-soundpad 2026-02-14 15:09:25 +03:00
arabianq baae7a1ccf feat: you can now open dirs/files in system's file manager using context menus 2026-02-14 15:09:05 +03:00
arabianq 654694cecf feat: dirs and files now support context menu (right mouse button) 2026-02-14 14:58:47 +03:00
Tarasov Aleksandr 04ecf66beb Add custom funding link to FUNDING.yml 2026-02-08 21:55:40 +03:00
Tarasov Aleksandr 0fe94f9112 Update README.md
add deepwiki.com badge
2026-02-03 04:33:04 +03:00
arabianq 9fbe42c201 update version to 1.5.1 2026-01-28 23:34:12 +03:00
arabianq fac04c4533 docs: update AUR installation command to include binary option 2026-01-28 23:32:09 +03:00
arabianq f93852bf8e add submodules for aur/standart and aur/bin 2026-01-28 23:31:15 +03:00
arabianq 1bb0aa959a rename workflow from Flatpak CI to Git Flatpak 2026-01-28 22:56:47 +03:00
arabianq 7a1723fbcb fix: enable build-bundle option in Flatpak workflows and adjust source path 2026-01-28 22:50:15 +03:00
arabianq 712a0968a7 fix: remove unnecessary file sources from Flatpak manifest 2026-01-28 22:45:33 +03:00
arabianq e98e6bc2f3 fix: remove submodule checkout option from Flatpak workflows 2026-01-28 22:41:40 +03:00
arabianq 5007b483aa refactor 2026-01-28 22:38:10 +03:00
arabianq b936b58e75 add Flatpak CI and release workflows, update paths in manifest 2026-01-28 22:37:36 +03:00
arabianq 502ef2ed89 create wrapper for flatpak, flatpak yaml, desktop entry and metainfo 2026-01-28 22:33:34 +03:00
arabianq ce5910b9a6 fix: improve device lookup in get_device function and update daemon device name 2026-01-28 22:30:33 +03:00
arabianq b0c670235e fix: impossible to remove directories 2026-01-28 22:07:05 +03:00
arabianq f1d4ffd7fa fix: handle errors when opening a directory in SoundpadGui 2026-01-28 21:44:00 +03:00
arabianq 6f35ab7b8b update github workflows 2026-01-28 21:20:56 +03:00
arabianq 9a67f5479a add submodule for AUR package 2026-01-28 21:16:58 +03:00
arabianq b727eba988 move pwsp.spec for rpm into packages/rpm directory 2026-01-28 21:15:37 +03:00
28 changed files with 1300 additions and 182 deletions
+1
View File
@@ -0,0 +1 @@
custom: ['https://boosty.to/arabian']
+84
View File
@@ -0,0 +1,84 @@
name: Git archive
permissions:
contents: write
packages: write
on:
push:
branches: [main, master]
workflow_dispatch:
jobs:
build-and-upload:
runs-on: ubuntu-latest
steps:
- name: Install apt deps (jq/zip + dev-libs)
run: |
sudo apt-get update
sudo apt-get install -y \
zip jq \
libpipewire-0.3-dev \
libclang-dev \
libasound2-dev
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Extract all binary names
id: cargo-meta
run: |
set -euo pipefail
BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[0].targets[] | select(.kind[] | contains("bin")) | .name')
# сохраним построчно в выход
echo "bin_names<<EOF" >> $GITHUB_OUTPUT
echo "$BIN_NAMES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Build all release binaries
run: cargo build --release --locked
- name: Package all binaries into one archive
shell: bash
run: |
set -euo pipefail
COMMIT_SHA="${{ github.sha }}"
ARCHIVE_NAME="pwsp-${COMMIT_SHA}-linux-x64.zip"
echo "Creating archive: $ARCHIVE_NAME"
FILES=()
while IFS= read -r BIN; do
[ -z "$BIN" ] && continue
FILES+=("target/release/$BIN")
done <<< "${{ steps.cargo-meta.outputs.bin_names }}"
if [ "${#FILES[@]}" -eq 0 ]; then
echo "Error: no binaries were discovered via cargo metadata." >&2
exit 1
fi
for f in "${FILES[@]}"; do
if [ ! -f "$f" ]; then
echo "Error: expected binary not found: $f" >&2
exit 1
fi
echo "Will add: $f"
done
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
- name: Upload archive as artifact
uses: actions/upload-artifact@v4
with:
name: archive
path: pwsp-*.zip
retention-days: 7
+53
View File
@@ -0,0 +1,53 @@
name: Git deb
permissions:
contents: write
packages: write
on:
push:
branches: [main, master]
workflow_dispatch:
jobs:
build-and-upload:
runs-on: ubuntu-latest
steps:
- name: Install apt deps (jq/zip + dev-libs)
run: |
sudo apt-get update
sudo apt-get install -y \
zip jq \
libpipewire-0.3-dev \
libclang-dev \
libasound2-dev
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all release binaries
run: cargo build --release --locked
- name: Install cargo-deb and create .deb
shell: bash
run: |
set -euo pipefail
cargo install --locked cargo-deb
export PATH="$HOME/.cargo/bin:$PATH"
cargo-deb
- name: Upload .deb(s) as artifacts
uses: actions/upload-artifact@v4
with:
name: deb-packages
path: target/debian/*.deb
retention-days: 7
+27
View File
@@ -0,0 +1,27 @@
name: Git Flatpak
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:
jobs:
flatpak-build:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: ru.arabianq.pwsp.flatpak
manifest-path: packages/flatpak/ru.arabianq.pwsp.yaml
cache: true
branch: master
build-bundle: true
+1 -9
View File
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to attach assets to (e.g. v1.0.0)'
description: "Tag to attach assets to (e.g. v1.0.0)"
required: false
jobs:
@@ -32,7 +32,6 @@ jobs:
run: |
set -euo pipefail
# приоритет 1: входной параметр workflow_dispatch
INPUT_TAG="${{ github.event.inputs.tag || '' }}"
if [ -n "$INPUT_TAG" ]; then
echo "Using input tag: $INPUT_TAG"
@@ -40,7 +39,6 @@ jobs:
exit 0
fi
# приоритет 2: если запущено событием release
EVENT_TAG="${{ github.event.release.tag_name || '' }}"
if [ -n "$EVENT_TAG" ]; then
echo "Using event tag: $EVENT_TAG"
@@ -48,14 +46,12 @@ jobs:
exit 0
fi
# приоритет 3: если GITHUB_REF — refs/tags/...
if [[ "${GITHUB_REF:-}" =~ ^refs/tags/(.+)$ ]]; then
echo "Using GITHUB_REF tag: ${BASH_REMATCH[1]}"
echo "tag=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
exit 0
fi
# приоритет 4: пробуем получить последний релиз через API
echo "No tag in input/event/GITHUB_REF — querying latest release via API..."
LATEST_JSON=$(curl -sSf -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" "https://api.github.com/repos/${{ github.repository }}/releases/latest" || true)
TAG_NAME=$(echo "$LATEST_JSON" | jq -r '.tag_name // empty')
@@ -91,7 +87,6 @@ jobs:
set -euo pipefail
BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[0].targets[] | select(.kind[] | contains("bin")) | .name')
# сохраним построчно в выход
echo "bin_names<<EOF" >> $GITHUB_OUTPUT
echo "$BIN_NAMES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
@@ -107,7 +102,6 @@ jobs:
ARCHIVE_NAME="pwsp-${TAG}-linux-x64.zip"
echo "Creating archive: $ARCHIVE_NAME"
# читаем построчно список бинарников и формируем массив файлов
FILES=()
while IFS= read -r BIN; do
[ -z "$BIN" ] && continue
@@ -119,7 +113,6 @@ jobs:
exit 1
fi
# проверим, что все бинарники действительно есть
for f in "${FILES[@]}"; do
if [ ! -f "$f" ]; then
echo "Error: expected binary not found: $f" >&2
@@ -128,7 +121,6 @@ jobs:
echo "Will add: $f"
done
# создаём архив с бинарниками внутри как просто pwsp-gui, pwsp-daemon, pwsp-cli
zip -j "$ARCHIVE_NAME" "${FILES[@]}"
- name: Upload release archive
+1 -1
View File
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to attach assets to (e.g. v1.0.0)'
description: "Tag to attach assets to (e.g. v1.0.0)"
required: false
jobs:
+72
View File
@@ -0,0 +1,72 @@
name: Release Flatpak
permissions:
contents: write
on:
release:
types: [created]
workflow_dispatch:
inputs:
tag:
description: "Tag to attach assets to (e.g. v1.0.0)"
required: false
jobs:
flatpak-release:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:freedesktop-24.08
options: --privileged
steps:
- name: Determine tag to use
id: tag
run: |
set -euo pipefail
INPUT_TAG="${{ github.event.inputs.tag || '' }}"
if [ -n "$INPUT_TAG" ]; then
echo "tag=$INPUT_TAG" >> $GITHUB_OUTPUT
exit 0
fi
EVENT_TAG="${{ github.event.release.tag_name || '' }}"
if [ -n "$EVENT_TAG" ]; then
echo "tag=$EVENT_TAG" >> $GITHUB_OUTPUT
exit 0
fi
if [[ "${GITHUB_REF:-}" =~ ^refs/tags/(.+)$ ]]; then
echo "tag=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
exit 0
fi
echo "No tag found"
echo "tag=" >> $GITHUB_OUTPUT
- name: Fail if no tag determined
if: ${{ steps.tag.outputs.tag == '' }}
run: |
echo "ERROR: No tag determined."
exit 1
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: ru.arabianq.pwsp.flatpak
manifest-path: packages/flatpak/ru.arabianq.pwsp.yaml
cache: true
branch: master
build-bundle: true
- name: Upload Flatpak to release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ steps.tag.outputs.tag }}
files: ru.arabianq.pwsp.flatpak
+9
View File
@@ -0,0 +1,9 @@
[submodule "packages/aur"]
path = packages/aur
url = ssh://aur@aur.archlinux.org/pwsp.git
[submodule "packages/aur/standart"]
path = packages/aur/standart
url = ssh://aur@aur.archlinux.org/pwsp.git
[submodule "packages/aur/bin"]
path = packages/aur/bin
url = ssh://aur@aur.archlinux.org/pwsp-bin.git
Generated
+765 -117
View File
File diff suppressed because it is too large Load Diff
+57 -12
View File
@@ -1,6 +1,6 @@
[package]
name = "pwsp"
version = "1.5.0"
version = "1.6.0"
edition = "2024"
authors = ["arabian"]
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_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"
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"
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"] }
eframe = { version = "0.33.3", default-features = false, features = ["default_fonts", "glow", "x11", "wayland"] }
egui = { version = "0.33.3", default-features = false, features = [
"default_fonts",
"rayon",
] }
eframe = { version = "0.33.3", default-features = false, features = [
"default_fonts",
"glow",
"x11",
"wayland",
] }
egui_material_icons = "0.5.0"
egui_dnd = "0.14.0"
@@ -52,10 +73,34 @@ panic = "abort"
[package.metadata.deb]
assets = [
["target/release/pwsp-daemon", "usr/bin/", "755"],
["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"],
[
"target/release/pwsp-daemon",
"usr/bin/",
"755",
],
[
"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",
],
]
+4 -1
View File
@@ -76,7 +76,7 @@ sudo dnf install pwsp
There is pwsp package in AUR.
You can install it using yay, paru or any other AUR helper.
```bash
paru pwsp
paru pwsp-bin # or paru pwsp to build it locally
```
## **Installing using cargo**
@@ -208,3 +208,6 @@ a [pull request](https://github.com/arabianq/pipewire-soundpad/pulls).
This project is licensed under
the [MIT License](https://github.com/arabianq/pipewire-soundpad/blob/main/LICENSE).
# **🤖 AI Wiki**
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/arabianq/pipewire-soundpad)
+1
Submodule packages/aur/bin added at 534cbc33ba
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
pwsp-daemon &
exec pwsp-gui "$@"
@@ -0,0 +1,9 @@
[Desktop Entry]
Name=PWSP (Soundpad)
Comment=Let's you play audio files through you microphone
Exec=pwsp-wrapper.sh %u
Icon=ru.arabianq.pwsp
Terminal=false
Type=Application
Categories=Audio;Utility;
Keywords=soundpad;pipewire;audio;
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>ru.arabianq.pwsp</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>
<name>PWSP</name>
<summary>Play audio files through your microphone using PipeWire</summary>
<description>
<p>
PWSP (PipeWire Soundpad) is a tool that allows you to play audio files through your
microphone.
It features both a graphical user interface and a command-line interface.
</p>
</description>
<launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>
https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://pwsp.arabianq.ru</url>
<developer_name>arabian</developer_name>
<content_rating type="oars-1.1" />
</component>
+47
View File
@@ -0,0 +1,47 @@
app-id: ru.arabianq.pwsp
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
- org.freedesktop.Sdk.Extension.llvm20
command: pwsp-wrapper.sh
finish-args:
- --share=ipc
- --socket=fallback-x11
- --socket=wayland
- --socket=pulseaudio
- --filesystem=xdg-run/pipewire-0
- --filesystem=xdg-run/pwsp:create
- --filesystem=xdg-run/app/ru.arabianq.pwsp:create
- --filesystem=host
- --device=all
- --device=dri
- --share=network
- --talk-name=org.freedesktop.portal.Desktop
- --talk-name=org.freedesktop.portal.Documents
build-options:
append-path: /usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm20/bin
env:
CARGO_HOME: /run/build/pwsp/cargo
LIBCLANG_PATH: /usr/lib/sdk/llvm20/lib
modules:
- name: pwsp
buildsystem: simple
build-options:
build-args:
- --share=network
build-commands:
- cargo build --release
- install -Dm755 target/release/pwsp-daemon /app/bin/pwsp-daemon
- install -Dm755 target/release/pwsp-cli /app/bin/pwsp-cli
- install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui
- install -Dm755 packages/flatpak/pwsp-wrapper.sh /app/bin/pwsp-wrapper.sh
- install -Dm644 assets/icon.png /app/share/icons/hicolor/256x256/apps/ru.arabianq.pwsp.png
- install -Dm644 packages/flatpak/ru.arabianq.pwsp.desktop /app/share/applications/ru.arabianq.pwsp.desktop
- install -Dm644 packages/flatpak/ru.arabianq.pwsp.metainfo.xml /app/share/metainfo/ru.arabianq.pwsp.metainfo.xml
sources:
- type: dir
path: ../../
+1 -1
View File
@@ -4,7 +4,7 @@
%global cargo_install_lib 0
Name: pwsp
Version: 1.5.0
Version: 1.6.0
Release: %autorelease
Summary: Lets you play audio files through your microphone
+3
View File
@@ -92,6 +92,8 @@ enum GetCommands {
Input,
/// All audio inputs
Inputs,
/// Version of the daemon
DaemonVersion,
/// Full player state
FullState,
}
@@ -148,6 +150,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
GetCommands::Tracks => Request::get_tracks(),
GetCommands::Input => Request::get_input(),
GetCommands::Inputs => Request::get_inputs(),
GetCommands::DaemonVersion => Request::get_daemon_version(),
GetCommands::FullState => Request::get_full_state(),
},
Commands::Set { parameter } => match parameter {
+90 -20
View File
@@ -4,13 +4,11 @@ use egui::{
Layout, RichText, ScrollArea, Sense, Slider, TextEdit, Ui, Vec2,
};
use egui_dnd::dnd;
use egui_material_icons::icons;
use pwsp::types::audio_player::TrackInfo;
use egui_material_icons::icons::*;
use pwsp::types::{audio_player::TrackInfo, gui::AppState};
use pwsp::utils::gui::format_time_pair;
use std::{error::Error, time::Instant};
use pwsp::types::gui::AppState;
enum TrackAction {
Pause(u32),
Resume(u32),
@@ -21,13 +19,13 @@ enum TrackAction {
impl SoundpadGui {
fn get_volume_icon(volume: f32) -> &'static str {
if volume > 0.7 {
icons::ICON_VOLUME_UP
ICON_VOLUME_UP
} else if volume <= 0.0 {
icons::ICON_VOLUME_OFF
ICON_VOLUME_OFF
} else if volume < 0.3 {
icons::ICON_VOLUME_MUTE
ICON_VOLUME_MUTE
} else {
icons::ICON_VOLUME_DOWN
ICON_VOLUME_DOWN
}
}
@@ -46,7 +44,7 @@ impl SoundpadGui {
ui.spacing_mut().item_spacing.y = 5.0;
// --------- Back Button and Title ----------
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);
if back_button_response.clicked() {
self.app_state.show_settings = false;
@@ -83,6 +81,10 @@ impl SoundpadGui {
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| {
// ---------- Play Button ----------
let play_button = Button::new(if track.paused {
icons::ICON_PLAY_ARROW
ICON_PLAY_ARROW
} else {
icons::ICON_PAUSE
ICON_PAUSE
})
.corner_radius(15.0);
@@ -188,9 +190,9 @@ impl SoundpadGui {
// ---------- Loop Button ----------
let loop_button = Button::new(
RichText::new(if track.looped {
icons::ICON_REPEAT_ONE
ICON_REPEAT_ONE
} else {
icons::ICON_REPEAT
ICON_REPEAT
})
.size(18.0),
)
@@ -248,7 +250,7 @@ impl SoundpadGui {
// --------------------------------
// ---------- 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);
if stop_button_response.clicked() {
action = Some(TrackAction::Stop(track.id));
@@ -311,7 +313,7 @@ impl SoundpadGui {
let path = item.clone();
ui.horizontal(|ui| {
handle.ui(ui, |ui| {
ui.label(icons::ICON_DRAG_INDICATOR);
ui.label(ICON_DRAG_INDICATOR);
});
let name = path
.file_name()
@@ -333,18 +335,46 @@ impl SoundpadGui {
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 =
ui.add_sized([18.0, 18.0], delete_dir_button);
if delete_dir_button_response.clicked() {
self.remove_dir(&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;
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);
if add_dirs_button_response.clicked() {
self.add_dirs();
@@ -416,8 +446,48 @@ impl SoundpadGui {
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 ----------
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);
if settings_button_response.clicked() {
self.app_state.show_settings = true;
+9 -15
View File
@@ -96,27 +96,21 @@ impl SoundpadGui {
}
}
pub fn remove_dir(&mut self, path: &PathBuf) {
self.app_state.dirs.retain(|x| x != path);
if let Some(current_dir) = &self.app_state.current_dir
&& current_dir == path
{
self.app_state.current_dir = None;
self.app_state.files.clear();
}
self.config.dirs = self.app_state.dirs.clone();
self.config.save_to_file().ok();
}
pub fn open_dir(&mut self, path: &PathBuf) {
self.app_state.current_dir = Some(path.clone());
self.app_state.files = path
.read_dir()
.unwrap()
match path.read_dir() {
Ok(read_dir) => {
self.app_state.files = read_dir
.filter_map(|res| res.ok())
.map(|entry| entry.path())
.collect();
}
Err(e) => {
eprintln!("Failed to read directory {:?}: {}", path, e);
self.app_state.files.clear();
}
}
}
pub fn play_file(&mut self, path: &PathBuf, concurrent: bool) {
make_request_async(Request::play(path.to_str().unwrap(), concurrent));
+11
View File
@@ -9,6 +9,17 @@ use std::time::{Duration, Instant};
impl App for SoundpadGui {
fn update(&mut self, ctx: &Context, _frame: &mut EFrame) {
// Remove directories
for path in self.app_state.dirs_to_remove.drain() {
self.app_state.dirs.retain(|x| x != &path);
if let Some(current_dir) = &self.app_state.current_dir
&& current_dir == &path
{
self.app_state.current_dir = None;
self.app_state.files.clear();
}
}
// Save directories if changed
if !self.config.dirs.eq(&self.app_state.dirs) {
self.config.dirs = self.app_state.dirs.clone();
+10 -1
View File
@@ -82,6 +82,8 @@ pub struct ToggleLoopCommand {
pub id: Option<u32>,
}
pub struct GetDaemonVersionCommand {}
pub struct GetFullStateCommand {}
#[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]
impl Executable for GetFullStateCommand {
async fn execute(&self) -> Response {
@@ -374,7 +383,7 @@ impl Executable for GetFullStateCommand {
tracks: audio_player.get_tracks(),
volume: audio_player.volume,
current_input: current_input_nick,
all_inputs,
all_inputs: all_inputs,
};
Response::new(true, serde_json::to_string(&full_state).unwrap())
+1
View File
@@ -38,6 +38,7 @@ pub struct AppState {
pub current_dir: Option<PathBuf>,
pub dirs: Vec<PathBuf>,
pub dirs_to_remove: HashSet<PathBuf>,
pub selected_file: Option<PathBuf>,
pub files: HashSet<PathBuf>,
+4
View File
@@ -156,6 +156,10 @@ impl Request {
Request::new("toggle_loop", args)
}
pub fn get_daemon_version() -> Self {
Request::new("get_daemon_version", vec![])
}
pub fn get_full_state() -> Self {
Request::new("get_full_state", vec![])
}
+1
View File
@@ -69,6 +69,7 @@ pub fn parse_command(request: &Request) -> Option<Box<dyn Executable + Send>> {
Some(Box::new(SetLoopCommand { enabled, id }))
}
"toggle_loop" => Some(Box::new(ToggleLoopCommand { id })),
"get_daemon_version" => Some(Box::new(GetDaemonVersionCommand {})),
"get_full_state" => Some(Box::new(GetFullStateCommand {})),
_ => None,
}
+1 -1
View File
@@ -36,7 +36,7 @@ pub fn get_daemon_config() -> DaemonConfig {
pub async fn link_player_to_virtual_mic() -> Result<(), Box<dyn Error>> {
let pwsp_daemon_output;
if let Ok(device) = get_device("alsa_playback.pwsp-daemon").await {
if let Ok(device) = get_device("pwsp-daemon").await {
pwsp_daemon_output = device;
} else {
eprintln!("Could not find alsa_playback.pwsp-daemon device, skipping device linking");
+5 -1
View File
@@ -210,7 +210,11 @@ pub async fn get_device(device_name: &str) -> Result<AudioDevice, Box<dyn Error>
input_devices.extend(output_devices);
for device in input_devices {
if device.name == device_name {
if device.name == device_name
|| device.nick == device_name
|| device.name.contains(device_name)
|| device.nick.contains(device_name)
{
return Ok(device);
}
}