Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions PenguinWave/src-tauri/src/audio/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
use crate::event::{CHAT_SINK, GAME_SINK};

pub mod equalizer;

pub struct SinkConfig {
pub(crate) name: &'static str,
pub(crate) display_name: &'static str,
}

pub const SINK_CONFIGS: &[SinkConfig] = &[
SinkConfig { name: GAME_SINK, display_name: GAME_SINK },
SinkConfig { name: CHAT_SINK, display_name: CHAT_SINK },
];
22 changes: 2 additions & 20 deletions PenguinWave/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ mod headsets;
mod system;
mod utils;
use crate::event::chatmix_listener::{init_chatmix_monitor, ChatMixMonitoringState};
use crate::system::pipewire::PipeWireManager;
use crate::system::pipewire::{init_virtual_channels, PipeWireManager};
use crate::utils::hardware_utils::*;
use crate::utils::state::AppStateManager;
use serde::{Deserialize, Serialize};
use tauri::{Manager};
use crate::event::{CHAT_SINK, GAME_SINK};
use crate::headsets::steelseries;

#[tauri::command]
fn greet(name: &str) -> String {
Expand All @@ -36,23 +34,6 @@ fn greet(name: &str) -> String {
name, device_count
);

// Verify if game/chat sinks exist, if not create them and link with the devices

if let Ok(sinks) = PipeWireManager::list_sinks() {
if !sinks.contains(&GAME_SINK.to_string()) && !sinks.contains(&CHAT_SINK.to_string())
{
let game_sink_id = PipeWireManager::create_virtual_sink(&GAME_SINK.to_string(), &GAME_SINK.to_string());
let chat_sink_id = PipeWireManager::create_virtual_sink(&CHAT_SINK.to_string(), &CHAT_SINK.to_string());

if let (Ok(_), Ok(_)) = (game_sink_id, chat_sink_id) {
let _ = PipeWireManager::link_ports(&format!("{}:monitor_FL", CHAT_SINK), &format!("{}:playback_FL", steelseries::SINK_NAME));
let _ = PipeWireManager::link_ports(&format!("{}:monitor_FL", CHAT_SINK), &format!("{}:playback_FR", steelseries::SINK_NAME));
let _ = PipeWireManager::link_ports(&format!("{}:monitor_FL", GAME_SINK), &format!("{}:playback_FL", steelseries::SINK_NAME));
let _ = PipeWireManager::link_ports(&format!("{}:monitor_FL", GAME_SINK), &format!("{}:playback_FR", steelseries::SINK_NAME));
}
};
}

for (i, device_list) in devices.iter().enumerate() {
let battery = device_list.device.request_battery();
let chatmix = device_list.device.request_chatmix();
Expand Down Expand Up @@ -153,6 +134,7 @@ pub fn run() {
builder
.manage(ChatMixMonitoringState::default())
.setup(|app| {
init_virtual_channels(app.handle());
init_chatmix_monitor(app.handle());
Ok(())
})
Expand Down
82 changes: 82 additions & 0 deletions PenguinWave/src-tauri/src/system/pipewire.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::process::Command;
use serde::{Serialize, Deserialize};
use anyhow::{Result, anyhow};
use tauri::AppHandle;
use crate::audio::SINK_CONFIGS;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PipeWireNode {
Expand Down Expand Up @@ -311,4 +313,84 @@ impl PipeWireManager {

Ok(())
}
pub fn get_default_sink() -> Result<String> {
let output = Command::new("pactl")
.args(&["get-default-sink"])
.output()?;

if !output.status.success() {
return Err(anyhow!("Failed to get default sink: {}",
String::from_utf8_lossy(&output.stderr)));
}

let sink_name = String::from_utf8_lossy(&output.stdout)
.trim()
.to_string();

if sink_name.is_empty() {
return Err(anyhow!("No default sink found"));
}

Ok(sink_name)
}
}

pub fn init_virtual_channels(app_handle: &AppHandle) {
match try_init_virtual_channels() {
Ok(()) => println!("Virtual channels initialized successfully"),
Err(e) => eprintln!("Failed to initialize virtual channels: {}", e),
}
}

fn try_init_virtual_channels() -> Result<(), Box<dyn std::error::Error>> {
let existing_sinks = PipeWireManager::list_sinks()
.map_err(|e| format!("Failed to list existing sinks: {}", e))?;

let sinks_to_create: Vec<_> = SINK_CONFIGS
.iter()
.filter(|config| !existing_sinks.contains(&config.name.to_string()))
.collect();

if sinks_to_create.is_empty() {
println!("All required sinks already exist");
return Ok(());
}

let mut successfully_created = Vec::new();

for config in sinks_to_create {
match PipeWireManager::create_virtual_sink(&config.name.to_string(), &config.display_name.to_string()) {
Ok(_) => {
successfully_created.push(config.name.to_string());
println!("Created virtual sink: {}", config.name);
}
Err(e) => {
eprintln!("Failed to create sink {}: {}", config.name, e);
}
}
}

for sink_name in &successfully_created {
link_sink_to_default(sink_name)?;
}

Ok(())
}

fn link_sink_to_default(sink_name: &str) -> Result<(), Box<dyn std::error::Error>> {
// Get the current default sink
let default_sink = PipeWireManager::get_default_sink()
.map_err(|e| format!("Failed to get default sink: {}", e))?;

let connections = [
(format!("{}:monitor_FL", sink_name), format!("{}:playback_FL", default_sink)),
(format!("{}:monitor_FR", sink_name), format!("{}:playback_FR", default_sink)),
];

for (source, target) in connections {
PipeWireManager::link_ports(&source, &target)
.map_err(|e| format!("Failed to link {} to {}: {}", source, target, e))?;
}

Ok(())
}