mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-06-19 12:13:32 +00:00
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:
committed by
GitHub
parent
6c59137639
commit
0476329798
@@ -0,0 +1,197 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use pwsp_lib::{
|
||||
types::socket::{MAX_MESSAGE_SIZE, Request, Response},
|
||||
utils::{
|
||||
commands::parse_command,
|
||||
daemon::{
|
||||
create_runtime_dir, get_audio_player, get_daemon_config, get_runtime_dir,
|
||||
is_daemon_running,
|
||||
},
|
||||
global_hotkeys::start_global_hotkey_listener,
|
||||
pipewire::create_virtual_mic,
|
||||
},
|
||||
};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::{fs, time::Duration};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{UnixListener, UnixStream},
|
||||
time::sleep,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
create_runtime_dir()?;
|
||||
|
||||
if is_daemon_running()? {
|
||||
return Err(anyhow!("Another instance is already running."));
|
||||
}
|
||||
|
||||
get_daemon_config(); // Initialize daemon config
|
||||
create_virtual_mic()?;
|
||||
if let Err(err) = get_audio_player().await {
|
||||
eprintln!("Failed to initialize audio player: {}", err);
|
||||
} // Initialize audio player
|
||||
|
||||
tokio::spawn(async {
|
||||
start_global_hotkey_listener().await;
|
||||
});
|
||||
|
||||
let runtime_dir = get_runtime_dir();
|
||||
|
||||
let lock_file = fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(runtime_dir.join("daemon.lock"))?;
|
||||
lock_file.lock()?;
|
||||
|
||||
let socket_path = runtime_dir.join("daemon.sock");
|
||||
if let Err(e) = fs::remove_file(&socket_path)
|
||||
&& e.kind() != std::io::ErrorKind::NotFound
|
||||
{
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
let listener = UnixListener::bind(&socket_path)?;
|
||||
fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600))?;
|
||||
|
||||
println!(
|
||||
"Daemon started. Listening on {}",
|
||||
socket_path.to_str().unwrap_or_default()
|
||||
);
|
||||
|
||||
let commands_loop_handle = tokio::spawn(async {
|
||||
commands_loop(listener).await.ok();
|
||||
});
|
||||
|
||||
let player_loop_handle = tokio::spawn(async {
|
||||
player_loop().await;
|
||||
});
|
||||
|
||||
tokio::select! {
|
||||
_ = commands_loop_handle => {
|
||||
eprint!("Commands loop was finished, stopping program...");
|
||||
}
|
||||
_ = player_loop_handle => {
|
||||
eprint!("Audio Player loop was finished, stopping program...");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn commands_loop(listener: UnixListener) -> Result<()> {
|
||||
loop {
|
||||
let (stream, _addr) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
handle_connection(stream).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(mut stream: UnixStream) {
|
||||
// ---------- Read request (start) ----------
|
||||
let mut len_bytes = [0u8; 4];
|
||||
if stream.read_exact(&mut len_bytes).await.is_err() {
|
||||
eprintln!("Failed to read message length from client!");
|
||||
return;
|
||||
}
|
||||
|
||||
let request_len = u32::from_le_bytes(len_bytes) as usize;
|
||||
|
||||
if request_len > MAX_MESSAGE_SIZE {
|
||||
eprintln!(
|
||||
"Failed to read message from client: request too large ({} bytes)!",
|
||||
request_len
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
if (&mut stream)
|
||||
.take(request_len as u64)
|
||||
.read_to_end(&mut buffer)
|
||||
.await
|
||||
.is_err()
|
||||
|| buffer.len() != request_len
|
||||
{
|
||||
eprintln!("Failed to read message from client!");
|
||||
return;
|
||||
}
|
||||
|
||||
let request: Request = match serde_json::from_slice(&buffer) {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
let response = Response::new(false, format!("Failed to parse request: {}", err));
|
||||
let response_data = match serde_json::to_vec(&response) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return, // Should not happen with this simple Response
|
||||
};
|
||||
let response_len = response_data.len() as u32;
|
||||
let _ = stream.write_all(&response_len.to_le_bytes()).await;
|
||||
let _ = stream.write_all(&response_data).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
// ---------- Read request (end) ----------
|
||||
|
||||
// ---------- Generate response (start) ----------
|
||||
let command = parse_command(&request);
|
||||
let response: Response;
|
||||
if let Some(command) = command {
|
||||
response = command.execute().await;
|
||||
} else {
|
||||
response = Response::new(false, "Unknown command");
|
||||
}
|
||||
// ---------- Generate response (end) ----------
|
||||
|
||||
// ---------- Send response (start) ----------
|
||||
let response_data = match serde_json::to_vec(&response) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to serialize response: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let response_len = response_data.len() as u32;
|
||||
|
||||
if stream.write_all(&response_len.to_le_bytes()).await.is_err() {
|
||||
eprintln!("Failed to write response length to client!");
|
||||
return;
|
||||
}
|
||||
if stream.write_all(&response_data).await.is_err() {
|
||||
eprintln!("Failed to write response to client!");
|
||||
return;
|
||||
}
|
||||
// ---------- Send response (end) ----------
|
||||
|
||||
if response.status && response.message.eq("killed") {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
async fn player_loop() {
|
||||
let mut device_check_counter: u32 = 0;
|
||||
loop {
|
||||
let is_idle = match get_audio_player().await {
|
||||
Ok(player_mutex) => {
|
||||
let mut audio_player = player_mutex.lock().await;
|
||||
let check_devices = device_check_counter == 0;
|
||||
audio_player.update(check_devices).await;
|
||||
audio_player.tracks.is_empty()
|
||||
}
|
||||
Err(_err) => true,
|
||||
};
|
||||
|
||||
if is_idle {
|
||||
device_check_counter = 0;
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
} else {
|
||||
// Check devices every ~5 seconds (50 * 100ms) while playing
|
||||
device_check_counter = (device_check_counter + 1) % 50;
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user