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
15 changes: 14 additions & 1 deletion crates/cardwire-daemon/src/analyzer/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use zbus::object_server::SignalEmitter;
use crate::{
analyzer::{
dynamic_analysis::{check_env, get_app_id_wayland_with_retry, get_steam_app_id}, helpers::{comm_to_string, get_real_process_name, is_proc_still_alive}, static_analysis::{self, AppMetadata, watch_fdo_folders}
}, file::GpuPolicy, interface::{LogEntry, LoggerInterfaceSignals}
}, file::{DbusAppMetadata, GpuPolicy}, interface::{LogEntry, LoggerInterfaceSignals, SmartPolicyInterface}
};
#[repr(C)]
#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -51,6 +51,7 @@ pub struct CardwireAnalyzer {
reported_pids: Arc<RwLock<HashSet<u32>>>,
report_semaphore: Arc<Semaphore>,
signal: Arc<OnceLock<SignalEmitter<'static>>>,
new_app_signal: Arc<OnceLock<SignalEmitter<'static>>>,
}

// Bound the number of concurrent report tasks
Expand All @@ -65,6 +66,7 @@ impl CardwireAnalyzer {
signal: Arc<OnceLock<SignalEmitter<'static>>>,
db_cache: Arc<RwLock<HashMap<String, GpuPolicy>>>,
db_tx: mpsc::Sender<(String, AppMetadata, oneshot::Sender<bool>)>,
new_app_signal: Arc<OnceLock<SignalEmitter<'static>>>,
) -> anyhow::Result<CardwireAnalyzer> {
let mut blocker = blocker.write().await;
let exec_ring = blocker.get_exec_ring()?;
Expand Down Expand Up @@ -103,6 +105,7 @@ impl CardwireAnalyzer {
reported_pids: Arc::new(RwLock::new(HashSet::new())),
report_semaphore: Arc::new(Semaphore::new(REPORT_SEMAPHORE_PERMITS)),
signal,
new_app_signal,
})
}
pub async fn run(self) -> anyhow::Result<()> {
Expand Down Expand Up @@ -350,6 +353,7 @@ impl CardwireAnalyzer {
}
}

let dbus_meta = DbusAppMetadata::from_app_metadata(&meta, GpuPolicy::Blocked as u32);
let (reply_tx, reply_rx) = tokio::sync::oneshot::channel();
let res = self
.db_tx
Expand All @@ -363,6 +367,15 @@ impl CardwireAnalyzer {
.write()
.await
.insert(lookup_name.to_string(), GpuPolicy::Blocked);
if let Some(emitter) = self.new_app_signal.get()
&& let Err(e) = SmartPolicyInterface::new_app_added(
emitter,
(lookup_name.to_string(), dbus_meta),
)
.await
{
error!("failed to emit process_blocked_changed: {}", e);
}
info!(
"Discovered a new app: {}, adding to the database",
lookup_name
Expand Down
16 changes: 16 additions & 0 deletions crates/cardwire-daemon/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ async fn spawn_dbus_api(
object_server
.at(path, daemon.smart_policy_interface.clone())
.await?;
match object_server
.interface::<_, crate::interface::SmartPolicyInterface>(path)
.await
{
Ok(smart_ref) => {
daemon
.smart_policy_interface
.new_app_signal
.get_or_init(|| smart_ref.signal_emitter().to_owned());
}
Err(e) => {
log::warn!(
"Failed to get the Smart Policy interface ({e}); New App notifications will not be emitted"
);
}
}

drop(power_tasks);
// drop gpu list to prevent deadlock
Expand Down
11 changes: 11 additions & 0 deletions crates/cardwire-daemon/src/file/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ pub struct DbusAppMetadata {
pub gpu_policy: u32,
}

impl DbusAppMetadata {
pub fn from_app_metadata(meta: &AppMetadata, gpu_policy: u32) -> Self {
Self {
display_name: meta.display_name.clone(),
desktop_file_id: meta.desktop_file_id.clone(),
icon_name: meta.icon_name.clone(),
gpu_policy,
}
}
}

#[derive(Debug, Clone)]
pub struct CardwireDatabase {
pub cache: Arc<RwLock<HashMap<String, GpuPolicy>>>,
Expand Down
14 changes: 12 additions & 2 deletions crates/cardwire-daemon/src/interface/smart.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use aya::maps::{HashMap as AyaHashMap, MapError as AyaMapError};
use cardwire_ebpf_userspace::EbpfBlocker;
use std::{collections::HashMap, path::Path, sync::Arc};
use std::{
collections::HashMap, path::Path, sync::{Arc, OnceLock}
};

use tokio::sync::{Mutex, RwLock};
use zbus::{
fdo::{self, Error::Failed}, interface
fdo::{self, Error::Failed}, interface, object_server::SignalEmitter
};

use crate::file::{CardwireDatabase, DbusAppMetadata, GpuPolicy};
Expand All @@ -15,6 +17,7 @@ pub struct SmartPolicyInterface {
forced_map: Arc<RwLock<AyaHashMap<aya::maps::MapData, u32, u32>>>,
pub database: CardwireDatabase,
policy_lock: Arc<Mutex<()>>,
pub new_app_signal: Arc<OnceLock<SignalEmitter<'static>>>,
}

impl SmartPolicyInterface {
Expand All @@ -27,6 +30,7 @@ impl SmartPolicyInterface {
forced_map,
database: db,
policy_lock: Arc::new(Mutex::new(())),
new_app_signal: Arc::new(OnceLock::new()),
}
}
}
Expand Down Expand Up @@ -178,4 +182,10 @@ impl SmartPolicyInterface {

Ok(())
}

#[zbus(signal)]
pub async fn new_app_added(
emitter: &SignalEmitter<'_>,
new_app: (String, DbusAppMetadata),
) -> zbus::Result<()>;
}
15 changes: 9 additions & 6 deletions crates/cardwire-daemon/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,16 @@ impl DaemonManager {
let db_cache = self.smart_policy_interface.database.cache.clone();
let tx = self.smart_policy_interface.database.tx.clone();

let new_app_signal = Arc::clone(&self.smart_policy_interface.new_app_signal);

async move {
let cardwire_analyzer = CardwireAnalyzer::build(blocker, logger, signal, db_cache, tx)
.await
.map_err(|err| {
error!("Failed to build CardwireAnalyzer: {}", err);
err
})?;
let cardwire_analyzer =
CardwireAnalyzer::build(blocker, logger, signal, db_cache, tx, new_app_signal)
.await
.map_err(|err| {
error!("Failed to build CardwireAnalyzer: {}", err);
err
})?;
let res = cardwire_analyzer.run().await;
if let Err(ref e) = res {
error!("CardwireAnalyzer task failed: {}", e);
Expand Down
4 changes: 4 additions & 0 deletions crates/cardwire-gui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ impl AppState {
self.error = Some(format!("Error fetching app policies: {}", err));
}
},
Message::NewAppDiscovered((app_id, meta)) => {
let resolved = crate::helpers::resolve_app_metadata(&app_id, &meta);
self.smart_state.app_policies.insert(app_id, resolved);
}
Message::SetAppPolicy(app_id, policy) => {
let conn = self.zbus_conn.clone();
let app_id_clone = app_id.clone();
Expand Down
30 changes: 28 additions & 2 deletions crates/cardwire-gui/src/helpers/app_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ pub fn resolve_app_metadata(app_id: &str, raw: &DbusAppMetadata) -> ResolvedApp
}
}
candidate_filenames.push(format!("{}.desktop", app_id));
candidate_filenames.push(format!("{}.desktop", app_id.to_lowercase()));
let mut chars = app_id.chars();
if let Some(first) = chars.next() {
let capitalized = format!("{}{}.desktop", first.to_uppercase(), chars.as_str());
if !candidate_filenames.contains(&capitalized) {
candidate_filenames.push(capitalized);
}
}

'search_desktop: for data_dir in &data_dirs {
let apps_dir = data_dir.join("applications");
Expand Down Expand Up @@ -137,9 +145,27 @@ fn resolve_icon_path(
if p.is_absolute() && p.exists() {
return Some(p.to_path_buf());
}
names_to_check.push(name);
names_to_check.push(name.to_string());
names_to_check.push(name.to_lowercase());
let mut chars = name.chars();
if let Some(first) = chars.next() {
let capitalized = format!("{}{}", first.to_uppercase(), chars.as_str());
if !names_to_check.contains(&capitalized) {
names_to_check.push(capitalized);
}
}
}
if !names_to_check.contains(&app_id.to_string()) {
names_to_check.push(app_id.to_string());
names_to_check.push(app_id.to_lowercase());
let mut chars = app_id.chars();
if let Some(first) = chars.next() {
let capitalized = format!("{}{}", first.to_uppercase(), chars.as_str());
if !names_to_check.contains(&capitalized) {
names_to_check.push(capitalized);
}
}
}
names_to_check.push(app_id);

let extensions = ["png", "svg", "xpm"];
let icon_subdirs = [
Expand Down
1 change: 1 addition & 0 deletions crates/cardwire-gui/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub enum Message {
FetchedAppPolicies(
Result<std::collections::HashMap<String, crate::models::DbusAppMetadata>, String>,
),
NewAppDiscovered((String, crate::models::DbusAppMetadata)),
SetAppPolicy(String, i32),
AppPolicyResult(Result<(String, i32), String>),
UpdateSmartSearch(String),
Expand Down
80 changes: 70 additions & 10 deletions crates/cardwire-gui/src/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,20 +597,80 @@ fn logger_sub() -> Subscription<Message> {
})
}

#[proxy(
default_service = "org.opengamingcollective.cardwire",
default_path = "/org/opengamingcollective/cardwire",
interface = "org.opengamingcollective.cardwire.SmartPolicy"
)]
trait CardwireSmartPolicy {
fn get_app_policies(&self) -> zbus::Result<HashMap<String, crate::models::DbusAppMetadata>>;
#[zbus(signal)]
fn new_app_added(&self, new_app: (String, crate::models::DbusAppMetadata)) -> zbus::Result<()>;
}

async fn run_smart_sub(connection: &Connection, output: &mut Sender<Message>) {
let proxy = match CardwireSmartPolicyProxy::new(connection).await {
Ok(p) => p,
Err(e) => {
warn!("Failed to create D-Bus SmartPolicy proxy: {}", e);
let _ = output
.send(Message::FetchedAppPolicies(Err(e.to_string())))
.await;
return;
}
};

let mut new_app_stream = match proxy.receive_new_app_added().await {
Ok(stream) => stream,
Err(err) => {
warn!("Failed to subscribe to D-Bus new_app_added signal: {}", err);
let _ = output
.send(Message::FetchedAppPolicies(Err(err.to_string())))
.await;
return;
}
};

match proxy.get_app_policies().await {
Ok(policies) => {
let _ = output.send(Message::FetchedAppPolicies(Ok(policies))).await;
}
Err(err) => {
let _ = output
.send(Message::FetchedAppPolicies(Err(err.to_string())))
.await;
}
}

while let Some(signal) = new_app_stream.next().await {
if let Ok(args) = signal.args() {
let new_app = args.new_app().clone();
let _ = output.send(Message::NewAppDiscovered(new_app)).await;
}
}

let _ = output
.send(Message::FetchedAppPolicies(Err(
"Cardwire daemon disconnected".to_string(),
)))
.await;
}

fn smart_sub() -> Subscription<Message> {
Subscription::run_with("cardwire_smart_subscription", |_| {
stream::channel(10, |mut output: Sender<Message>| async move {
let conn = CardwireDbus::new();
match conn.get_app_policies().await {
Ok(policies) => {
let _ = output.send(Message::FetchedAppPolicies(Ok(policies))).await;
}
Err(err) => {
Subscription::run_with("cardwire_smart_subscription", |_id| {
stream::channel(100, |mut output: Sender<Message>| async move {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(e) => {
warn!("Failed to connect to D-Bus: {}", e);
let _ = output
.send(Message::FetchedAppPolicies(Err(err.to_string())))
.send(Message::FetchedAppPolicies(Err(e.to_string())))
.await;
return;
}
}
};

run_smart_sub(&connection, &mut output).await;
})
})
}
Expand Down