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
17 changes: 17 additions & 0 deletions PenguinWave/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions PenguinWave/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ dirs = "6.0.0"
anyhow = "1.0.98"
subprocess = "0.2"
tauri-plugin-shell = "2"
itertools = "0.14.0"
base64 = "0.22.1"


[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
Expand Down
9 changes: 8 additions & 1 deletion PenguinWave/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::utils::hardware_utils::*;
use crate::utils::state::AppStateManager;
use serde::{Deserialize, Serialize};
use tauri::{Manager};
use std::collections::HashMap;
use itertools::Itertools;

#[tauri::command]
fn greet(name: &str) -> String {
Expand Down Expand Up @@ -127,8 +129,13 @@ fn get_custom_virtual_sinks() -> Result<Vec<PipeWireNode>, String> {
}

#[tauri::command]
fn get_application_streams() -> Result<Vec<ApplicationStream>, String> {
fn get_application_streams() -> Result<HashMap<String, Vec<ApplicationStream>>, String> {
PipeWireManager::list_application_streams()
.map(|streams| {
streams
.into_iter()
.into_group_map_by(|stream| stream.name.clone()) // Replace 'name' with your actual field
})
.map_err(|e| e.to_string())
}

Expand Down
87 changes: 85 additions & 2 deletions PenguinWave/src-tauri/src/system/pipewire.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use serde::{Serialize, Deserialize};
use anyhow::{Result, anyhow};
Expand Down Expand Up @@ -33,7 +35,8 @@ pub struct ApplicationStream {
pub id: u32,
pub name: String,
pub icon: String,
pub assigned_sink_id: u32
pub assigned_sink_id: u32,
pub icon_path: String
}

pub struct PipeWireManager;
Expand Down Expand Up @@ -444,6 +447,7 @@ impl PipeWireManager {
let mut name = String::new();
let mut icon = String::new();
let mut assigned_sink_id : u32 = 0;
let mut icon_path = String::new();

let detailed_inputs = format!("{}\n", details_output);

Expand All @@ -466,6 +470,19 @@ impl PipeWireManager {
} else if line.contains("application.icon_name = ") {
if let Some(value) = Self::extract_quoted_value(line, "application.icon_name = ") {
icon = value;
let icon_path_value = Self::find_icon_file(icon.as_str(), 48)
.map(|path| path.to_string_lossy().to_string())
.or_else(|| {
// If first method fails, try desktop file method
Self::get_application_icon_from_desktop(icon.as_str())
.and_then(|icon_name| Self::find_icon_file(&icon_name, 48))
.map(|path| path.to_string_lossy().to_string())
})
.unwrap_or_default();

icon_path = Self::get_application_icon_base64(icon_path_value)
.map(|path| path.to_string())
.unwrap_or_default();
}
}

Expand All @@ -477,10 +494,15 @@ impl PipeWireManager {
id,
name: name.clone(),
icon: icon.clone(),
assigned_sink_id
assigned_sink_id: assigned_sink_id,
icon_path: icon_path.clone()
};

application_streams.push(application_stream);
name = String::new();
icon = String::new();
Comment thread
RiceaRaul marked this conversation as resolved.
assigned_sink_id = 0;
icon_path = String::new();
}
}
}
Expand Down Expand Up @@ -508,6 +530,67 @@ impl PipeWireManager {

Ok(sink_name)
}

pub fn find_icon_file(icon_name: &str, size: u32) -> Option<PathBuf> {
let icon_dirs = vec![
format!("/usr/share/icons/hicolor/{}x{}/apps", size, size),
format!("/usr/share/icons/Adwaita/{}x{}/apps", size, size),
format!("/usr/share/pixmaps"),
format!("{}/.local/share/icons/hicolor/{}x{}/apps",
std::env::var("HOME").unwrap_or_default(), size, size),
];

let extensions = vec!["png", "svg", "xpm"];

for dir in icon_dirs {
for ext in &extensions {
let icon_path = format!("{}/{}.{}", dir, icon_name, ext);
if std::path::Path::new(&icon_path).exists() {
return Some(PathBuf::from(icon_path));
}
}
}
None
}

pub fn get_application_icon_from_desktop(app_name: &str) -> Option<String> {
let desktop_dirs = vec![
"/usr/share/applications".to_string(),
"/usr/local/share/applications".to_string(),
format!("{}/.local/share/applications", std::env::var("HOME").unwrap_or_default()),
];

for dir in desktop_dirs {
let desktop_file = format!("{}/{}.desktop", dir, app_name.to_lowercase());
if let Ok(content) = fs::read_to_string(&desktop_file) {
for line in content.lines() {
if line.starts_with("Icon=") {
let icon_name = line.strip_prefix("Icon=").unwrap().trim();
return Some(icon_name.to_string());
}
}
}
}
None
}

pub fn get_application_icon_base64(icon_path: String) -> Result<String, String> {
use base64::{Engine as _, engine::general_purpose};

let icon_data = fs::read(&icon_path)
.map_err(|e| format!("Failed to read icon file: {}", e))?;

let mime_type = if icon_path.ends_with(".svg") {
"image/svg+xml"
} else if icon_path.ends_with(".png") {
"image/png"
} else {
"image/png" // default
};

let base64_data = general_purpose::STANDARD.encode(&icon_data);
Ok(format!("data:{};base64,{}", mime_type, base64_data))
}
}

pub fn init_virtual_channels(app_handle: &AppHandle) {
Expand Down
Loading