Refactor to Cargo Workspace (#129)

* Refactor project into a Cargo workspace with distinct packages

- Created a root `Cargo.toml` defining a workspace.
- Moved `src/types` and `src/utils` into a new `pwsp-lib` crate for shared logic.
- Split binaries into their own crates: `pwsp-daemon`, `pwsp-cli`, and `pwsp-gui`.
- Shifted all dependencies into `[workspace.dependencies]` for centralized version management.
- Updated import paths across all crates (e.g. from `pwsp::` to `pwsp_lib::`).
- Updated build scripts, GitHub actions, Flatpak manifest, and AUR PKGBUILD to support the new workspace structure.
- Ensured no core application logic was altered.

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* Fix cargo-deb build process in GitHub actions for workspace architecture

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* Fix cargo-deb asset discovery by using exact target/release paths

Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com>

* refactor deps in Cargo.toml files

* fix incorrect assets path

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Tarasov Aleksandr
2026-06-02 21:12:44 +03:00
committed by GitHub
parent 6c59137639
commit 0476329798
47 changed files with 224 additions and 87 deletions
+3 -2
View File
@@ -42,7 +42,7 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \ BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[0].targets[] | select(.kind[] | contains("bin")) | .name') | jq -r '.packages[].targets[] | select(.kind[] | contains("bin")) | .name')
echo "bin_names<<EOF" >> $GITHUB_OUTPUT echo "bin_names<<EOF" >> $GITHUB_OUTPUT
echo "$BIN_NAMES" >> $GITHUB_OUTPUT echo "$BIN_NAMES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
@@ -93,7 +93,8 @@ jobs:
cargo install --locked cargo-deb cargo install --locked cargo-deb
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
cargo-deb cargo build --release --locked
cargo-deb -p pwsp-gui
- name: Upload .deb(s) as artifacts - name: Upload .deb(s) as artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+3 -2
View File
@@ -94,7 +94,7 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \ BIN_NAMES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[0].targets[] | select(.kind[] | contains("bin")) | .name') | jq -r '.packages[].targets[] | select(.kind[] | contains("bin")) | .name')
echo "bin_names<<EOF" >> $GITHUB_OUTPUT echo "bin_names<<EOF" >> $GITHUB_OUTPUT
echo "$BIN_NAMES" >> $GITHUB_OUTPUT echo "$BIN_NAMES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
@@ -146,7 +146,8 @@ jobs:
cargo install --locked cargo-deb cargo install --locked cargo-deb
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
cargo-deb cargo build --release --locked
cargo-deb -p pwsp-gui
- name: Upload .deb(s) to release - name: Upload .deb(s) to release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
Generated
+43 -7
View File
@@ -3279,28 +3279,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
[[package]] [[package]]
name = "pwsp" name = "pwsp-cli"
version = "1.10.0" version = "1.10.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"clap", "clap",
"dirs", "pwsp-lib",
"serde_json",
"tokio",
]
[[package]]
name = "pwsp-daemon"
version = "1.10.0"
dependencies = [
"anyhow",
"clap",
"pipewire",
"pwsp-lib",
"serde_json",
"tokio",
]
[[package]]
name = "pwsp-gui"
version = "1.10.0"
dependencies = [
"anyhow",
"eframe", "eframe",
"egui", "egui",
"egui_dnd", "egui_dnd",
"egui_extras", "egui_extras",
"egui_material_icons", "egui_material_icons",
"evdev",
"itertools 0.14.0", "itertools 0.14.0",
"opener", "opener",
"percent-encoding", "percent-encoding",
"pipewire", "pwsp-lib",
"reqwest", "reqwest",
"rfd", "rfd",
"rodio",
"rust-i18n", "rust-i18n",
"rustix 1.1.4",
"serde", "serde",
"serde_json", "serde_json",
"sys-locale", "sys-locale",
@@ -3308,6 +3325,25 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "pwsp-lib"
version = "1.10.0"
dependencies = [
"anyhow",
"async-trait",
"dirs",
"egui",
"evdev",
"itertools 0.14.0",
"pipewire",
"reqwest",
"rodio",
"rustix 1.1.4",
"serde",
"serde_json",
"tokio",
]
[[package]] [[package]]
name = "pxfm" name = "pxfm"
version = "0.1.29" version = "0.1.29"
+16 -52
View File
@@ -1,17 +1,28 @@
[package] [workspace]
name = "pwsp" members = [
"pwsp-lib",
"pwsp-daemon",
"pwsp-cli",
"pwsp-gui"
]
resolver = "2"
[workspace.package]
version = "1.10.0" version = "1.10.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."
readme = "README.md"
homepage = "https://pwsp.arabianq.ru" homepage = "https://pwsp.arabianq.ru"
repository = "https://github.com/arabianq/pipewire-soundpad" repository = "https://github.com/arabianq/pipewire-soundpad"
license = "MIT" license = "MIT"
description = "PWSP lets you play audio files through your microphone. Has both CLI and GUI clients."
keywords = ["soundpad", "pipewire", "linux", "cli", "gui"] keywords = ["soundpad", "pipewire", "linux", "cli", "gui"]
[workspace.dependencies]
pwsp-lib = { path = "pwsp-lib" }
pwsp-daemon = { path = "pwsp-daemon" }
pwsp-cli = { path = "pwsp-cli" }
pwsp-gui = { path = "pwsp-gui" }
[dependencies]
tokio = { version = "1.52.3", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
async-trait = "0.1.89" async-trait = "0.1.89"
@@ -31,9 +42,7 @@ dirs = "6.0.0"
itertools = "0.14.0" itertools = "0.14.0"
evdev = { version = "0.13.2", features = ["tokio"] } evdev = { version = "0.13.2", features = ["tokio"] }
rfd = { version = "0.17.2", default-features = false, features = [ rfd = { version = "0.17.2", default-features = false, features = [
"xdg-portal", "xdg-portal",
] } ] }
opener = { version = "0.8.4", features = ["reveal"] } opener = { version = "0.8.4", features = ["reveal"] }
system-fonts = "0.1.1" system-fonts = "0.1.1"
@@ -67,18 +76,6 @@ egui_dnd = "0.15.0"
reqwest = "0.13.4" reqwest = "0.13.4"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
[[bin]]
name = "pwsp-daemon"
path = "src/bin/daemon.rs"
[[bin]]
name = "pwsp-cli"
path = "src/bin/cli.rs"
[[bin]]
name = "pwsp-gui"
path = "src/main.rs"
[profile.release] [profile.release]
strip = true strip = true
lto = true lto = true
@@ -86,36 +83,3 @@ codegen-units = 1
opt-level = "z" opt-level = "z"
panic = "abort" 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",
],
]
+1 -1
View File
@@ -1,7 +1,7 @@
<div align="center"> <div align="center">
<h1>🎵 PipeWire Soundpad (PWSP)</h1> <h1>🎵 PipeWire Soundpad (PWSP)</h1>
<p><b>A simple, modern, and powerful soundboard for Linux, written in Rust.</b></p> <p><b>A simple, modern, and powerful soundboard for Linux, written in Rust.</b></p>
<img src="assets/screenshot.png" alt="PWSP Screenshot" width="700"/> <img src="pwsp-gui/assets/screenshot.png" alt="PWSP Screenshot" width="700"/>
</div> </div>
## 🌟 Overview ## 🌟 Overview
+3 -3
View File
@@ -40,8 +40,8 @@ package() {
install -Dm755 "target/release/pwsp-daemon" "${pkgdir}/usr/bin/pwsp-daemon" install -Dm755 "target/release/pwsp-daemon" "${pkgdir}/usr/bin/pwsp-daemon"
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 "pwsp-gui/assets/pwsp-gui.desktop" "${pkgdir}/usr/share/applications/pwsp-gui.desktop"
install -Dm644 "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 "assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service" install -Dm644 "pwsp-gui/assets/pwsp-daemon.service" "${pkgdir}/usr/lib/systemd/user/pwsp-daemon.service"
} }
@@ -15,7 +15,7 @@
<launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable> <launchable type="desktop-id">ru.arabianq.pwsp.desktop</launchable>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://raw.githubusercontent.com/arabianq/pipewire-soundpad/master/assets/screenshot.png</image> <image>https://raw.githubusercontent.com/arabianq/pipewire-soundpad/main/pwsp-gui/assets/screenshot.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<url type="homepage">https://pwsp.arabianq.ru</url> <url type="homepage">https://pwsp.arabianq.ru</url>
+1 -1
View File
@@ -35,7 +35,7 @@ modules:
- install -Dm755 target/release/pwsp-cli /app/bin/pwsp-cli - install -Dm755 target/release/pwsp-cli /app/bin/pwsp-cli
- install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui - install -Dm755 target/release/pwsp-gui /app/bin/pwsp-gui
- install -Dm755 packages/flatpak/pwsp-wrapper.py /app/bin/pwsp-wrapper.py - install -Dm755 packages/flatpak/pwsp-wrapper.py /app/bin/pwsp-wrapper.py
- install -Dm644 assets/icon.png /app/share/icons/hicolor/256x256/apps/ru.arabianq.pwsp.png - install -Dm644 pwsp-gui/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.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 - install -Dm644 packages/flatpak/ru.arabianq.pwsp.metainfo.xml /app/share/metainfo/ru.arabianq.pwsp.metainfo.xml
sources: sources:
+3 -3
View File
@@ -39,10 +39,10 @@ install -Dm755 target/release/pwsp-cli %{buildroot}%{_bindir}/pwsp-cli
install -Dm755 target/release/pwsp-daemon %{buildroot}%{_bindir}/pwsp-daemon install -Dm755 target/release/pwsp-daemon %{buildroot}%{_bindir}/pwsp-daemon
install -Dm755 target/release/pwsp-gui %{buildroot}%{_bindir}/pwsp-gui install -Dm755 target/release/pwsp-gui %{buildroot}%{_bindir}/pwsp-gui
install -Dm644 assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop install -Dm644 pwsp-gui/assets/pwsp-gui.desktop %{buildroot}%{_datadir}/applications/pwsp.desktop
install -Dm644 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 assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service install -Dm644 pwsp-gui/assets/pwsp-daemon.service %{buildroot}/usr/lib/systemd/user/pwsp-daemon.service
%files %files
%license LICENSE %license LICENSE
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "pwsp-cli"
version.workspace = true
edition.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
pwsp-lib.workspace = true
tokio.workspace = true
anyhow.workspace = true
clap.workspace = true
serde_json.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use pwsp::{ use pwsp_lib::{
types::socket::Request, types::socket::Request,
utils::daemon::{make_request, wait_for_daemon}, utils::daemon::{make_request, wait_for_daemon},
}; };
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "pwsp-daemon"
version.workspace = true
edition.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
pwsp-lib.workspace = true
tokio.workspace = true
serde_json.workspace = true
clap.workspace = true
anyhow.workspace = true
pipewire.workspace = true
@@ -1,5 +1,5 @@
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use pwsp::{ use pwsp_lib::{
types::socket::{MAX_MESSAGE_SIZE, Request, Response}, types::socket::{MAX_MESSAGE_SIZE, Request, Response},
utils::{ utils::{
commands::parse_command, commands::parse_command,
+69
View File
@@ -0,0 +1,69 @@
[package]
name = "pwsp-gui"
version.workspace = true
edition.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
description.workspace = true
[dependencies]
pwsp-lib.workspace = true
tokio.workspace = true
opener.workspace = true
rfd.workspace = true
itertools.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
egui.workspace = true
eframe.workspace = true
egui_extras.workspace = true
egui_material_icons.workspace = true
egui_dnd.workspace = true
system-fonts.workspace = true
rust-i18n.workspace = true
sys-locale.workspace = true
reqwest.workspace = true
percent-encoding.workspace = true
[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",
],
]

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

@@ -1,7 +1,7 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use egui::{Context, Id, Key, Modifiers}; use egui::{Context, Id, Key, Modifiers};
use pwsp::types::socket::Request; use pwsp_lib::types::socket::Request;
use pwsp::utils::gui::make_request_async; use pwsp_lib::utils::gui::make_request_async;
/// Convert an egui Key + Modifiers to a normalized chord string like "Ctrl+Shift+A". /// Convert an egui Key + Modifiers to a normalized chord string like "Ctrl+Shift+A".
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> { fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
+1 -1
View File
@@ -6,7 +6,7 @@ use anyhow::{Result, anyhow};
use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native}; use eframe::{HardwareAcceleration, NativeOptions, icon_data::from_png_bytes, run_native};
use egui::{Context, FontData, FontDefinitions, FontFamily, FontTweak, Vec2, ViewportBuilder}; use egui::{Context, FontData, FontDefinitions, FontFamily, FontTweak, Vec2, ViewportBuilder};
use itertools::Itertools; use itertools::Itertools;
use pwsp::{ use pwsp_lib::{
types::{ types::{
audio_player::PlayerState, audio_player::PlayerState,
config::GuiConfig, config::GuiConfig,
@@ -1,7 +1,7 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use eframe::{App, Frame as EFrame}; use eframe::{App, Frame as EFrame};
use egui::{CentralPanel, Context, ThemePreference}; use egui::{CentralPanel, Context, ThemePreference};
use pwsp::{ use pwsp_lib::{
types::{config::PreferredTheme, 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},
}; };
@@ -5,7 +5,7 @@ use egui::{
}; };
use egui_dnd::dnd; use egui_dnd::dnd;
use egui_material_icons::icons::*; use egui_material_icons::icons::*;
use pwsp::types::{gui::AppState, gui::AudioPlayerState}; use pwsp_lib::types::{gui::AppState, gui::AudioPlayerState};
use rust_i18n::t; use rust_i18n::t;
use std::{cmp::Ordering, path::Path, path::PathBuf}; use std::{cmp::Ordering, path::Path, path::PathBuf};
@@ -1,8 +1,8 @@
use crate::gui::SoundpadGui; use crate::gui::SoundpadGui;
use egui::{Button, CollapsingHeader, 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_lib::types::{audio_player::TrackInfo, gui::AppState};
use pwsp::utils::gui::format_time_pair; use pwsp_lib::utils::gui::format_time_pair;
use std::time::Instant; use std::time::Instant;
pub(crate) enum TrackAction { pub(crate) enum TrackAction {
@@ -91,7 +91,7 @@ impl SoundpadGui {
fn draw_position_control( fn draw_position_control(
ui: &mut Ui, ui: &mut Ui,
ui_state: &mut pwsp::types::gui::TrackUiState, ui_state: &mut pwsp_lib::types::gui::TrackUiState,
track: &TrackInfo, track: &TrackInfo,
default_slider_width: f32, default_slider_width: f32,
) { ) {
@@ -117,7 +117,7 @@ impl SoundpadGui {
fn draw_volume_control( fn draw_volume_control(
ui: &mut Ui, ui: &mut Ui,
ui_state: &mut pwsp::types::gui::TrackUiState, ui_state: &mut pwsp_lib::types::gui::TrackUiState,
track: &TrackInfo, track: &TrackInfo,
default_slider_width: f32, default_slider_width: f32,
) { ) {
@@ -2,8 +2,8 @@ use crate::gui::SoundpadGui;
use egui::{Button, Color32, Label, RichText, TextEdit, Ui}; use egui::{Button, Color32, Label, RichText, TextEdit, Ui};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use egui_material_icons::icons::*; use egui_material_icons::icons::*;
use pwsp::types::socket::Request; use pwsp_lib::types::socket::Request;
use pwsp::utils::gui::make_request_async; use pwsp_lib::utils::gui::make_request_async;
use rust_i18n::t; use rust_i18n::t;
use std::path::Path; use std::path::Path;
@@ -1,7 +1,7 @@
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, Ui};
use egui_material_icons::icons::ICON_ARROW_BACK; use egui_material_icons::icons::ICON_ARROW_BACK;
use pwsp::types::config::PreferredTheme; use pwsp_lib::types::config::PreferredTheme;
use rust_i18n::t; use rust_i18n::t;
impl SoundpadGui { impl SoundpadGui {
+1 -1
View File
@@ -1,7 +1,7 @@
mod gui; mod gui;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use pwsp::utils::gui::ensure_pwsp_audio_dir; use pwsp_lib::utils::gui::ensure_pwsp_audio_dir;
use rust_i18n::i18n; use rust_i18n::i18n;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "pwsp-lib"
version.workspace = true
edition.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
[dependencies]
tokio.workspace = true
async-trait.workspace = true
serde.workspace = true
serde_json.workspace = true
dirs.workspace = true
itertools.workspace = true
evdev.workspace = true
anyhow.workspace = true
rustix.workspace = true
rodio.workspace = true
pipewire.workspace = true
egui.workspace = true
reqwest.workspace = true
View File