use std::collections::HashMap;
use std::num::NonZeroU32;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::OnceLock;
use nix::sys::inotify::{InitFlags, Inotify};
use cache::{drain_inotify_events, ensure_watches, AppsCache, APPS_CACHE};
use cgroup::running_xdg_conformant_apps;
use desktop::installed_apps_impl;
use matching::running_apps_by_process;
mod cache;
mod cgroup;
mod desktop;
mod env;
mod exec;
mod matching;
fn app_id_replacement() -> &'static HashMap<&'static str, &'static str> {
static APP_ID_REPLACEMENT: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
APP_ID_REPLACEMENT
.get_or_init(|| HashMap::from([("gnome-system-monitor-kde", "org.gnome.SystemMonitor")]))
}
fn normalize_app_id(id: &str) -> &str {
if let Some(real_id) = app_id_replacement().get(id) {
real_id
} else {
id
}
}
pub trait Process {
fn pid(&self) -> NonZeroU32;
fn executable_path(&self) -> Option<PathBuf>;
fn name(&self) -> &str;
fn cmdline(&self) -> &[String] {
&[]
}
}
#[derive(Clone, Debug)]
pub struct ApplicationEntry {
pub id: Rc<str>,
pub name: Rc<str>,
pub exec: Option<Rc<str>>,
pub exec_args: Vec<Rc<str>>,
pub icon: Option<Rc<str>>,
}
pub fn installed_apps() -> HashMap<Rc<str>, ApplicationEntry> {
APPS_CACHE.with(|cell| {
let mut slot = cell.borrow_mut();
if let Some(cache) = slot.as_mut() {
let dirty = drain_inotify_events(cache);
ensure_watches(cache, env::xdg_data_dirs());
if dirty {
cache.apps = installed_apps_impl(env::xdg_data_dirs(), env::path());
}
return cache.apps.clone();
}
let apps = installed_apps_impl(env::xdg_data_dirs(), env::path());
match Inotify::init(InitFlags::IN_NONBLOCK | InitFlags::IN_CLOEXEC) {
Ok(inotify) => {
let mut cache = AppsCache {
apps: apps.clone(),
inotify,
watched: HashMap::new(),
};
ensure_watches(&mut cache, env::xdg_data_dirs());
*slot = Some(cache);
}
Err(e) => {
log::warn!("inotify init failed ({e}); installed_apps() will not be cached");
}
}
apps
})
}
pub fn running_apps<'a, P: Process + 'a>(
available_applications: &'a HashMap<Rc<str>, ApplicationEntry>,
processes: impl IntoIterator<Item = &'a P>,
) -> Vec<(&'a ApplicationEntry, Vec<NonZeroU32>)> {
let mut running_apps = running_xdg_conformant_apps(available_applications);
let mut apps_by_proc = running_apps_by_process(available_applications, processes);
for (app_id, (app, pids)) in apps_by_proc.drain() {
let normalized_id = normalize_app_id(app_id.as_ref());
if !running_apps.contains_key(normalized_id) {
let key: Rc<str> = if normalized_id == app_id.as_ref() {
app_id
} else {
Rc::from(normalized_id)
};
running_apps.insert(key, (app, pids));
}
}
running_apps
.values_mut()
.map(|(app, pids)| (*app, std::mem::take(pids)))
.collect()
}