* Add Live Activities as configurable bar items * Fix activity owner monitoring and Morph list interactions * Keep activity rows stable during updates and tighten their layout * Use subtle theme tint for activity cards * Tighten activity title and progress spacing * Use theme accent for active progress rings * Fit compact activities to measured titles and badge * Expose Live Activities display controls in Settings * Open activity terminals and streamline the Morph header * Give Morph continuous icons and prioritize workspace navigation * Show activity progress inside its ring * Inset activity progress rings and strengthen their stroke * Fix activity lifecycle, command output handling and list navigation * Reduce activity update work and harden publisher lifetimes * Set up Live Activities for the detected user shell during installation * Document Activities setup, daily use and troubleshooting * Preserve Morph idle width across empty activity updates * Fix dimming flicker, hide inactive music tab and consolidate Activities docs * Fix panel clearance across styles and match previews to theme settings * Prevent panel blur from repainting neighboring islands --------- Co-authored-by: Samuel Onoja <[email protected]>
102 lines
4.7 KiB
Rust
102 lines
4.7 KiB
Rust
//! Explicitly enabled fallback. Bounded /proc sampling and regex work stay on
|
|
//! one worker; disappearance emits owner exit, never an inferred exit status.
|
|
use crate::ipc::IpcEvent;
|
|
use ferese_activities::Owner;
|
|
use smithay::reexports::calloop::channel;
|
|
use std::collections::BTreeMap;
|
|
use std::io::Read;
|
|
use std::sync::{
|
|
Arc,
|
|
atomic::{AtomicBool, Ordering},
|
|
};
|
|
use std::time::Duration;
|
|
|
|
pub(crate) struct Watcher(Arc<AtomicBool>);
|
|
impl Drop for Watcher {
|
|
fn drop(&mut self) {
|
|
self.0.store(true, Ordering::Release);
|
|
}
|
|
}
|
|
impl Watcher {
|
|
pub fn start(
|
|
rules: crate::activities::SharedRules,
|
|
sender: channel::SyncSender<IpcEvent>,
|
|
) -> std::io::Result<Self> {
|
|
let stop = Arc::new(AtomicBool::new(false));
|
|
let worker_stop = stop.clone();
|
|
std::thread::Builder::new()
|
|
.name("ferese-activity-processes".into())
|
|
.spawn(move || {
|
|
let mut observed =
|
|
BTreeMap::<Owner, (Arc<std::os::fd::OwnedFd>, ferese_config::activities::Matched)>::new();
|
|
while !worker_stop.load(Ordering::Acquire) {
|
|
let rules = rules.read().expect("activity rules").clone();
|
|
let dead: Vec<_> = observed
|
|
.iter()
|
|
.filter_map(|(owner, (fd, _))| crate::ipc::activity_owner::exited(fd).then_some(*owner))
|
|
.collect();
|
|
for owner in dead {
|
|
observed.remove(&owner);
|
|
if sender.send(IpcEvent::ActivityOwnerExited(owner)).is_err() {
|
|
return;
|
|
}
|
|
}
|
|
// Reconcile every detection: admission may have failed or its lease expired.
|
|
for (owner, (fd, matched)) in &observed {
|
|
let _ = sender.try_send(IpcEvent::ActivityDetected(
|
|
*owner,
|
|
Box::new(matched.clone()),
|
|
fd.clone(),
|
|
));
|
|
}
|
|
if let Ok(processes) = std::fs::read_dir("/proc") {
|
|
for process in processes.take(4_096).flatten() {
|
|
if worker_stop.load(Ordering::Acquire) || observed.len() >= 128 {
|
|
break;
|
|
}
|
|
let Some(pid) = process.file_name().to_str().and_then(|name| name.parse::<u32>().ok())
|
|
else {
|
|
continue;
|
|
};
|
|
let Ok((owner, fd, parents)) = crate::ipc::activity_owner::inspect(pid) else {
|
|
continue;
|
|
};
|
|
if observed.contains_key(&owner) || parents.iter().any(|parent| parent == "feresectl") {
|
|
continue;
|
|
}
|
|
let Ok(file) = std::fs::File::open(process.path().join("cmdline")) else {
|
|
continue;
|
|
};
|
|
let mut bytes = Vec::new();
|
|
if file.take(4_097).read_to_end(&mut bytes).is_err() || bytes.len() > 4_096 {
|
|
continue;
|
|
}
|
|
let Ok(command) = String::from_utf8(bytes) else {
|
|
continue;
|
|
};
|
|
let command = command.trim_end_matches('\0').replace('\0', " ");
|
|
let Ok(cwd) = std::fs::read_link(process.path().join("cwd")) else {
|
|
continue;
|
|
};
|
|
let cwd = cwd.to_string_lossy();
|
|
let Some(matched) = rules.matcher.matches(&command, &cwd, &parents) else {
|
|
continue;
|
|
};
|
|
let fd = Arc::new(fd);
|
|
// Keep the inspected process identity alive through dispatch.
|
|
// No raw command, cwd or environment crosses the dispatcher.
|
|
if sender
|
|
.try_send(IpcEvent::ActivityDetected(owner, Box::new(matched.clone()), fd.clone()))
|
|
.is_ok()
|
|
{
|
|
observed.insert(owner, (fd, matched));
|
|
}
|
|
}
|
|
}
|
|
std::thread::sleep(Duration::from_secs(2));
|
|
}
|
|
})?;
|
|
Ok(Self(stop))
|
|
}
|
|
}
|