Add native Settings and complete live configuration reload
Add compact branded Settings with safe atomic edits, external config refresh and a draggable sidebar. Reconcile services, update connected displays and input devices, apply live layout policy and window rules, and resolve initial floating placement before tiled insertion. Package the Settings launcher and shared Ferese icon.
This commit is contained in:
1 parent
672b4e2b60
commit
23991d5bd8
27 files changed
+3083
-61
No files matched your search
@@ -15,11 +15,50 @@ struct Service {
|
||||
pub(crate) struct Runner(Vec<Service>);
|
||||
|
||||
impl Runner {
|
||||
pub fn reconcile(&mut self, configs: &[DaemonConfig], nested: bool) {
|
||||
let mut previous = std::mem::take(&mut self.0);
|
||||
for config in configs
|
||||
.iter()
|
||||
.filter(|c| c.enabled && (!nested || c.nested))
|
||||
{
|
||||
if self
|
||||
.0
|
||||
.iter()
|
||||
.any(|service| service.config.command == config.command)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(index) = previous
|
||||
.iter()
|
||||
.position(|service| service.config.command == config.command)
|
||||
{
|
||||
let mut service = previous.remove(index);
|
||||
if config.restart && !service.config.restart {
|
||||
service.finished = false;
|
||||
}
|
||||
service.config = config.clone();
|
||||
self.0.push(service);
|
||||
} else {
|
||||
self.0.push(Service {
|
||||
config: config.clone(),
|
||||
child: None,
|
||||
next_start: Instant::now(),
|
||||
finished: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
// TERM/grace/reaping must not pause the compositor's event loop.
|
||||
for mut removed in previous {
|
||||
if let Some(mut child) = removed.child.take() {
|
||||
std::thread::spawn(move || crate::terminate_child(&mut child));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn new(configs: &[DaemonConfig], nested: bool) -> Self {
|
||||
Self(
|
||||
configs
|
||||
.iter()
|
||||
.filter(|config| !nested || config.nested)
|
||||
.filter(|config| config.enabled && (!nested || config.nested))
|
||||
.map(|config| Service {
|
||||
config: config.clone(),
|
||||
child: None,
|
||||
@@ -31,6 +70,7 @@ impl Runner {
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, state: &mut Ferese) {
|
||||
self.reconcile(&state.autostart, state.direct_backend.is_none());
|
||||
let now = Instant::now();
|
||||
for service in &mut self.0 {
|
||||
if let Some(child) = &mut service.child {
|
||||
@@ -82,9 +122,44 @@ impl Drop for Runner {
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn live_reconcile_keeps_existing_process_and_changes_policy_without_restart() {
|
||||
let mut config = DaemonConfig {
|
||||
command: vec!["/bin/sleep".into(), "30".into()],
|
||||
enabled: true,
|
||||
restart: false,
|
||||
nested: false,
|
||||
};
|
||||
let mut runner = Runner::new(&[config.clone()], false);
|
||||
runner.0[0].child = Some(
|
||||
std::process::Command::new("/bin/sleep")
|
||||
.arg("30")
|
||||
.spawn()
|
||||
.unwrap(),
|
||||
);
|
||||
let pid = runner.0[0].child.as_ref().unwrap().id();
|
||||
config.restart = true;
|
||||
runner.reconcile(&[config.clone()], false);
|
||||
assert_eq!(runner.0[0].child.as_ref().unwrap().id(), pid);
|
||||
assert!(runner.0[0].config.restart);
|
||||
runner.reconcile(&[config.clone(), config.clone()], false);
|
||||
assert_eq!(runner.0.len(), 1);
|
||||
config.enabled = false;
|
||||
runner.reconcile(&[config], false);
|
||||
assert!(runner.0.is_empty());
|
||||
let deadline = Instant::now() + Duration::from_secs(2);
|
||||
while unsafe { libc::kill(pid as i32, 0) } == 0 {
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"removed service was not stopped/reaped"
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn previews_do_not_duplicate_session_daemons() {
|
||||
let configs = vec![DaemonConfig {
|
||||
command: vec!["awari".into()],
|
||||
enabled: true,
|
||||
restart: true,
|
||||
nested: false,
|
||||
}];
|
||||
@@ -94,4 +169,16 @@ mod tests {
|
||||
direct.stop();
|
||||
assert!(direct.0[0].finished);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_login_items_never_start_on_either_backend() {
|
||||
let configs = vec![DaemonConfig {
|
||||
command: vec!["not-executed".into()],
|
||||
enabled: false,
|
||||
restart: true,
|
||||
nested: true,
|
||||
}];
|
||||
assert!(Runner::new(&configs, true).0.is_empty());
|
||||
assert!(Runner::new(&configs, false).0.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user