1use dbus::arg::PropMap;
20use dbus::blocking::LocalConnection;
21use dbus::message::MatchRule;
22use std::cell::{Cell, RefCell};
23use std::rc::Rc;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::mpsc::{Receiver, Sender, TryRecvError};
26use std::sync::{Arc, Mutex};
27use std::time::{Duration, Instant};
28
29const SERVER: &str = "org.freedesktop.Notifications";
31const SERVER_PATH: &str = "/org/freedesktop/Notifications";
32const BUS: &str = "org.freedesktop.DBus";
34const BUS_PATH: &str = "/org/freedesktop/DBus";
35const DCONF_WRITER: &str = "ca.desrt.dconf.Writer";
37
38const CALL_WAIT: Duration = Duration::from_secs(3);
40const LISTENED: Duration = Duration::from_millis(250);
42const START_WAIT: Duration = Duration::from_secs(5);
44const HELD: Duration = Duration::from_secs(1);
46const PROPERTIES: &str = "org.freedesktop.DBus.Properties";
48
49#[derive(Clone, Debug, Default, PartialEq, Eq)]
51pub struct Server {
52 pub name: String,
54 pub vendor: String,
56 pub version: String,
57 pub capabilities: Vec<String>,
58 pub inhibits: bool,
61}
62
63pub fn server() -> Option<Server> {
66 let bus = LocalConnection::new_session().ok()?;
67 if !has_owner(&bus, SERVER) {
68 return None;
69 }
70 let proxy = bus.with_proxy(SERVER, SERVER_PATH, CALL_WAIT);
71 let (name, vendor, version, _spec): (String, String, String, String) = proxy.method_call(SERVER, "GetServerInformation", ()).ok()?;
72 let capabilities = proxy.method_call(SERVER, "GetCapabilities", ()).map(|(found,): (Vec<String>,)| found).unwrap_or_default();
73 let mut inhibits = capabilities.iter().any(|c| c == "inhibitions");
74 if !inhibits && vendor == "KDE" {
76 use dbus::blocking::stdintf::org_freedesktop_dbus::Introspectable;
77 inhibits = proxy.introspect().is_ok_and(|xml| xml.contains("name=\"Inhibit\""));
78 }
79 Some(Server { name, vendor, version, capabilities, inhibits })
80}
81
82fn has_owner(bus: &LocalConnection, name: &str) -> bool {
84 bus.with_proxy(BUS, BUS_PATH, CALL_WAIT).method_call(BUS, "NameHasOwner", (name,)).is_ok_and(|(owned,): (bool,)| owned)
85}
86
87fn owner_of(bus: &LocalConnection, name: &str) -> Option<String> {
89 bus.with_proxy(BUS, BUS_PATH, CALL_WAIT).method_call(BUS, "GetNameOwner", (name,)).ok().map(|(owner,): (String,)| owner)
90}
91
92pub fn inhibited() -> Option<bool> {
97 let bus = LocalConnection::new_session().ok()?;
98 if !has_owner(&bus, SERVER) {
99 return None;
100 }
101 read_inhibited(&bus)
102}
103
104fn read_inhibited(bus: &LocalConnection) -> Option<bool> {
105 use dbus::blocking::stdintf::org_freedesktop_dbus::Properties;
106 bus.with_proxy(SERVER, SERVER_PATH, CALL_WAIT).get::<bool>(SERVER, "Inhibited").ok()
107}
108
109pub struct Watch {
115 stop: Sender<()>,
116 seen: Arc<Mutex<Option<bool>>>,
117 alive: Arc<AtomicBool>,
118}
119
120impl Watch {
121 pub fn seen(&self) -> Option<bool> {
123 self.seen.lock().ok().and_then(|seen| *seen)
124 }
125
126 pub fn alive(&self) -> bool {
128 self.alive.load(Ordering::Relaxed)
129 }
130}
131
132impl Drop for Watch {
133 fn drop(&mut self) {
134 let _ = self.stop.send(());
135 }
136}
137
138pub fn watch_inhibited(quiet: Arc<Mutex<Instant>>, told: Box<dyn Fn(bool) + Send>) -> Result<Watch, String> {
141 let (stop, stopped) = std::sync::mpsc::channel::<()>();
142 let (ready, started) = std::sync::mpsc::channel();
143 let seen: Arc<Mutex<Option<bool>>> = Arc::default();
144 let alive = Arc::new(AtomicBool::new(true));
145 let (noted, living) = (Arc::clone(&seen), Arc::clone(&alive));
146 std::thread::spawn(move || {
147 follow_inhibited(¬ed, &quiet, &*told, &stopped, &ready);
148 living.store(false, Ordering::Relaxed);
149 });
150 match started.recv_timeout(START_WAIT) {
151 Ok(Ok(())) => Ok(Watch { stop, seen, alive }),
152 Ok(Err(e)) => Err(e),
153 Err(_) => Err("the session bus did not answer".to_string()),
154 }
155}
156
157fn follow_inhibited(seen: &Mutex<Option<bool>>, quiet: &Mutex<Instant>, told: &dyn Fn(bool), stopped: &Receiver<()>, ready: &Sender<Result<(), String>>) {
158 let bus = match LocalConnection::new_session() {
159 Ok(bus) => bus,
160 Err(e) => {
161 let _ = ready.send(Err(said(&e)));
162 return;
163 }
164 };
165 let heard: Rc<Cell<Option<(Instant, bool)>>> = Rc::default();
168 let owner = Rc::new(RefCell::new(owner_of(&bus, SERVER).unwrap_or_default()));
169 let (new_owner, server_changed) = (Rc::clone(&owner), Rc::clone(&heard));
170 let owners = bus.add_match(MatchRule::new_signal(BUS, "NameOwnerChanged").with_sender(BUS), move |(name, _old, new): (String, String, String), _: &LocalConnection, _: &dbus::Message| {
171 if name == SERVER {
172 *new_owner.borrow_mut() = new;
173 server_changed.set(Some((Instant::now(), true)));
174 }
175 true
176 });
177 let (from, property_changed) = (Rc::clone(&owner), Rc::clone(&heard));
178 let changes = bus.add_match(MatchRule::new_signal(PROPERTIES, "PropertiesChanged").with_path(SERVER_PATH), move |(interface, changed, _gone): (String, PropMap, Vec<String>), _: &LocalConnection, message: &dbus::Message| {
179 let ours = message.sender().is_some_and(|sender| *from.borrow() == sender.to_string());
180 if ours && interface == SERVER && changed.contains_key("Inhibited") {
181 let fresh = property_changed.get().is_some_and(|(_, fresh)| fresh);
182 property_changed.set(Some((Instant::now(), fresh)));
183 }
184 true
185 });
186 if let Err(e) = owners.and(changes) {
187 let _ = ready.send(Err(said(&e)));
188 return;
189 }
190 if let Ok(mut noted) = seen.lock() {
192 *noted = read_inhibited(&bus);
193 }
194 let _ = ready.send(Ok(()));
195 loop {
196 match stopped.try_recv() {
197 Ok(()) | Err(TryRecvError::Disconnected) => return,
198 Err(TryRecvError::Empty) => {}
199 }
200 if let Some((at, fresh)) = heard.get() {
201 let settled = quiet.lock().map_or(true, |quiet| Instant::now() >= *quiet);
202 if at.elapsed() >= HELD && settled {
203 heard.set(None);
204 let now = read_inhibited(&bus);
205 let before = seen.lock().ok().and_then(|mut noted| std::mem::replace(&mut *noted, now));
206 if !fresh
207 && let (Some(before), Some(now)) = (before, now)
208 && before != now
209 {
210 told(now);
211 }
212 }
213 }
214 if bus.process(LISTENED).is_err() {
215 return;
216 }
217 }
218}
219
220pub struct Inhibition {
223 orders: Sender<Sender<Result<(), String>>>,
224 alive: Arc<AtomicBool>,
225 cookie: u32,
226}
227
228impl Inhibition {
229 pub fn alive(&self) -> bool {
231 self.alive.load(Ordering::Relaxed)
232 }
233
234 pub fn cookie(&self) -> u32 {
236 self.cookie
237 }
238
239 pub fn release(self, wait: Duration) -> Result<(), String> {
243 let (done, answer) = std::sync::mpsc::channel();
244 if self.orders.send(done).is_err() {
245 return Ok(());
246 }
247 answer.recv_timeout(wait).unwrap_or_else(|_| Err("no answer from the notification server".to_string()))
248 }
249}
250
251pub fn inhibit(desktop_entry: &str, reason: &str) -> Result<Inhibition, String> {
256 let (orders, asked) = std::sync::mpsc::channel();
257 let (ready, started) = std::sync::mpsc::channel();
258 let alive = Arc::new(AtomicBool::new(true));
259 let (entry, reason, living) = (desktop_entry.to_string(), reason.to_string(), Arc::clone(&alive));
260 std::thread::spawn(move || {
262 hold(&entry, &reason, &asked, &ready);
263 living.store(false, Ordering::Relaxed);
264 });
265 match started.recv_timeout(START_WAIT) {
266 Ok(Ok(cookie)) => Ok(Inhibition { orders, alive, cookie }),
267 Ok(Err(e)) => Err(e),
268 Err(_) => Err("no answer from the notification server".to_string()),
269 }
270}
271
272fn hold(entry: &str, reason: &str, asked: &Receiver<Sender<Result<(), String>>>, ready: &Sender<Result<u32, String>>) {
275 let bus = match LocalConnection::new_session() {
276 Ok(bus) => bus,
277 Err(e) => {
278 let _ = ready.send(Err(said(&e)));
279 return;
280 }
281 };
282 let restarted: Rc<Cell<bool>> = Rc::default();
286 let heard = Rc::clone(&restarted);
287 let _ = bus.add_match(MatchRule::new_signal(BUS, "NameOwnerChanged").with_sender(BUS), move |(name, _old, new): (String, String, String), _: &LocalConnection, _: &dbus::Message| {
288 if name == SERVER && !new.is_empty() {
289 heard.set(true);
290 }
291 true
292 });
293 let server = bus.with_proxy(SERVER, SERVER_PATH, CALL_WAIT);
294 let ask = || server.method_call(SERVER, "Inhibit", (entry, reason, PropMap::new())).map(|(cookie,): (u32,)| cookie);
295 let mut cookie = match ask() {
296 Ok(cookie) => cookie,
297 Err(e) => {
298 let _ = ready.send(Err(said(&e)));
299 return;
300 }
301 };
302 let _ = ready.send(Ok(cookie));
303 let lift = |cookie: u32| -> Result<(), String> {
304 if cookie == 0 {
305 return Ok(());
306 }
307 server.method_call(SERVER, "UnInhibit", (cookie,)).map_err(|e| said(&e))
308 };
309 loop {
310 match asked.try_recv() {
311 Ok(done) => {
312 let _ = done.send(lift(cookie));
313 return;
314 }
315 Err(TryRecvError::Disconnected) => {
316 let _ = lift(cookie);
317 return;
318 }
319 Err(TryRecvError::Empty) => {}
320 }
321 if restarted.replace(false) {
322 cookie = ask().unwrap_or(0);
324 }
325 if bus.process(LISTENED).is_err() {
326 return;
327 }
328 }
329}
330
331fn said(e: &dbus::Error) -> String {
333 e.message().or(e.name()).unwrap_or("D-Bus").to_string()
334}
335
336pub struct Changes {
340 stop: Sender<()>,
341 heard: Arc<Mutex<Vec<Instant>>>,
342 alive: Arc<AtomicBool>,
343}
344
345impl Changes {
346 pub fn since(&self, at: Instant) -> usize {
348 self.heard.lock().map_or(0, |heard| heard.iter().filter(|when| **when > at).count())
349 }
350
351 pub fn alive(&self) -> bool {
353 self.alive.load(Ordering::Relaxed)
354 }
355}
356
357impl Drop for Changes {
358 fn drop(&mut self) {
359 let _ = self.stop.send(());
360 }
361}
362
363pub fn watch_dconf(key: &str) -> Result<Changes, String> {
365 watch(key, None)
366}
367
368pub fn watch_dconf_told(key: &str, told: Box<dyn Fn() + Send>) -> Result<Changes, String> {
371 watch(key, Some(told))
372}
373
374fn watch(key: &str, told: Option<Box<dyn Fn() + Send>>) -> Result<Changes, String> {
375 let (stop, stopped) = std::sync::mpsc::channel::<()>();
376 let (ready, started) = std::sync::mpsc::channel();
377 let heard: Arc<Mutex<Vec<Instant>>> = Arc::default();
378 let alive = Arc::new(AtomicBool::new(true));
379 let (key, noted, living) = (key.to_string(), Arc::clone(&heard), Arc::clone(&alive));
380 std::thread::spawn(move || {
381 listen(&key, ¬ed, told, &stopped, &ready);
382 living.store(false, Ordering::Relaxed);
383 });
384 match started.recv_timeout(START_WAIT) {
385 Ok(Ok(())) => Ok(Changes { stop, heard, alive }),
386 Ok(Err(e)) => Err(e),
387 Err(_) => Err("the session bus did not answer".to_string()),
388 }
389}
390
391fn listen(key: &str, heard: &Arc<Mutex<Vec<Instant>>>, told: Option<Box<dyn Fn() + Send>>, stopped: &Receiver<()>, ready: &Sender<Result<(), String>>) {
392 let bus = match LocalConnection::new_session() {
393 Ok(bus) => bus,
394 Err(e) => {
395 let _ = ready.send(Err(said(&e)));
396 return;
397 }
398 };
399 let (key, noted) = (key.to_string(), Arc::clone(heard));
400 let matched = bus.add_match(MatchRule::new_signal(DCONF_WRITER, "Notify"), move |(prefix, changes, _tag): (String, Vec<String>, String), _: &LocalConnection, _: &dbus::Message| {
401 if touches(&key, &prefix, &changes) {
402 if let Ok(mut noted) = noted.lock() {
403 noted.push(Instant::now());
404 }
405 if let Some(told) = &told {
406 told();
407 }
408 }
409 true
410 });
411 if let Err(e) = matched {
412 let _ = ready.send(Err(said(&e)));
413 return;
414 }
415 let _ = ready.send(Ok(()));
416 loop {
417 match stopped.try_recv() {
418 Ok(()) | Err(TryRecvError::Disconnected) => return,
419 Err(TryRecvError::Empty) => {}
420 }
421 if bus.process(LISTENED).is_err() {
422 return;
423 }
424 }
425}
426
427fn touches(key: &str, prefix: &str, changes: &[String]) -> bool {
431 let reaches = |path: &str| path == key || (path.ends_with('/') && key.starts_with(path));
432 if changes.is_empty() {
433 return reaches(prefix);
434 }
435 changes.iter().any(|change| reaches(&format!("{prefix}{change}")))
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use dbus::blocking::stdintf::org_freedesktop_dbus::RequestNameReply;
442 use dbus::channel::{MatchingReceiver, Sender as _};
443 use std::sync::mpsc::channel;
444
445 #[test]
446 fn a_dconf_change_touches_its_key_or_a_folder_reset() {
447 let key = "/org/gnome/desktop/notifications/show-banners";
448 assert!(touches(key, key, &[String::new()]));
449 assert!(touches(key, "/org/gnome/desktop/notifications/", &["show-banners".into(), "show-in-lock-screen".into()]));
450 assert!(touches(key, "/org/gnome/desktop/notifications/", &[String::new()]), "the folder reset whole");
451 assert!(touches(key, "/org/gnome/", &["desktop/notifications/".into()]));
452 assert!(!touches(key, "/org/gnome/desktop/notifications/", &["show-in-lock-screen".into()]));
453 assert!(!touches(key, "/org/gnome/desktop/notifications/show-banners-x", &[String::new()]));
454 assert!(!touches(key, "/org/gnome/desktop/interface/", &["color-scheme".into()]));
455 }
456
457 #[derive(Debug, PartialEq)]
459 enum Asked {
460 Inhibit { entry: String, reason: String, caller: String },
461 UnInhibit(u32),
462 }
463
464 static ON_THE_BUS: Mutex<()> = Mutex::new(());
467
468 fn stand_in(first: u32, told: Sender<Asked>, stop: Arc<AtomicBool>, life: Duration) -> Option<()> {
474 stand_in_saying(first, told, stop, life, None)
475 }
476
477 struct Says {
480 value: Arc<AtomicBool>,
481 set: Receiver<bool>,
482 }
483
484 fn stand_in_saying(first: u32, told: Sender<Asked>, stop: Arc<AtomicBool>, life: Duration, says: Option<Says>) -> Option<()> {
486 let (ready, started) = channel::<bool>();
487 std::thread::spawn(move || {
488 let until = Instant::now() + Duration::from_secs(2);
491 let owner = loop {
492 let bus = LocalConnection::new_session().ok().filter(|bus| matches!(bus.request_name(SERVER, false, false, true), Ok(RequestNameReply::PrimaryOwner)));
493 if bus.is_some() || Instant::now() >= until {
494 break bus;
495 }
496 std::thread::sleep(Duration::from_millis(50));
497 };
498 let Some(bus) = owner else {
499 let _ = ready.send(false);
500 return;
501 };
502 let next = Rc::new(Cell::new(first));
503 let value = says.as_ref().map(|says| Arc::clone(&says.value));
504 bus.start_receive(
505 MatchRule::new_method_call(),
506 Box::new(move |call: dbus::Message, bus: &LocalConnection| {
507 let reply = match call.member().as_deref() {
508 Some("GetServerInformation") => call.method_return().append3("Plasma", "KDE", "6.7.5").append1("1.2"),
509 Some("GetCapabilities") => call.method_return().append1(vec!["body", "actions", "persistence", "inhibitions"]),
510 Some("Inhibit") => {
511 let (entry, reason, _hints): (String, String, PropMap) = call.read3().unwrap();
512 let caller = call.sender().map(|s| s.to_string()).unwrap_or_default();
513 let _ = told.send(Asked::Inhibit { entry, reason, caller });
514 let cookie = next.get();
515 next.set(cookie + 1);
516 call.method_return().append1(cookie)
517 }
518 Some("UnInhibit") => {
519 let (cookie,): (u32,) = call.read_all().unwrap();
520 let _ = told.send(Asked::UnInhibit(cookie));
521 call.method_return()
522 }
523 Some("Get") if call.interface().as_deref() == Some(PROPERTIES) => {
524 let (_interface, name): (String, String) = call.read2().unwrap();
525 match &value {
526 Some(value) if name == "Inhibited" => call.method_return().append1(dbus::arg::Variant(value.load(Ordering::Relaxed))),
527 _ => return true,
528 }
529 }
530 _ => return true,
531 };
532 let _ = bus.send(reply);
533 true
534 }),
535 );
536 let _ = ready.send(true);
537 let until = Instant::now() + life;
538 while !stop.load(Ordering::Relaxed) && Instant::now() < until {
539 while let Some(on) = says.as_ref().and_then(|says| says.set.try_recv().ok()) {
541 if let Some(says) = &says {
542 says.value.store(on, Ordering::Relaxed);
543 }
544 let mut changed = PropMap::new();
545 changed.insert("Inhibited".to_string(), dbus::arg::Variant(Box::new(on) as Box<dyn dbus::arg::RefArg>));
546 let signal = dbus::Message::new_signal(SERVER_PATH, PROPERTIES, "PropertiesChanged").unwrap().append3(SERVER, changed, Vec::<String>::new());
547 let _ = bus.send(signal);
548 }
549 let _ = bus.process(Duration::from_millis(50));
550 }
551 let _ = bus.release_name(SERVER);
552 });
553 started.recv_timeout(Duration::from_secs(5)).unwrap_or(false).then_some(())
554 }
555
556 fn connected(name: &str) -> bool {
558 LocalConnection::new_session().is_ok_and(|bus| has_owner(&bus, name))
559 }
560
561 #[test]
565 fn plasma_inhibition_is_held_while_the_pause_lasts_and_lifted_after() {
566 let _alone = ON_THE_BUS.lock().unwrap_or_else(|e| e.into_inner());
567 let (told, asked) = channel();
568 let stop = Arc::new(AtomicBool::new(false));
569 if stand_in(42, told, Arc::clone(&stop), Duration::from_secs(30)).is_none() {
570 eprintln!("No bus of the test's own (dbus-run-session), or a notification server on it already: skipped.");
571 return;
572 }
573 let wait = Duration::from_secs(5);
574 let found = server().expect("the stand-in answers");
575 assert_eq!((found.name.as_str(), found.vendor.as_str(), found.inhibits), ("Plasma", "KDE", true));
576 let held = inhibit("com.aurelienpierre.Sioul", "Pause").expect("inhibited");
577 assert_eq!(held.cookie(), 42);
578 let Asked::Inhibit { entry, reason, caller } = asked.recv_timeout(wait).unwrap() else { panic!("Inhibit first") };
579 assert_eq!((entry.as_str(), reason.as_str()), ("com.aurelienpierre.Sioul", "Pause"));
580 std::thread::sleep(Duration::from_millis(600));
583 assert!(held.alive() && connected(&caller), "the inhibition's connection is kept");
584 held.release(wait).expect("lifted");
585 assert_eq!(asked.recv_timeout(wait).unwrap(), Asked::UnInhibit(42));
586 let until = Instant::now() + wait;
588 while connected(&caller) && Instant::now() < until {
589 std::thread::sleep(Duration::from_millis(50));
590 }
591 assert!(!connected(&caller), "the connection closed once lifted");
592 let dropped = inhibit("com.aurelienpierre.Sioul", "Free time").expect("inhibited");
594 assert!(matches!(asked.recv_timeout(wait).unwrap(), Asked::Inhibit { reason, .. } if reason == "Free time"));
595 drop(dropped);
596 assert_eq!(asked.recv_timeout(wait).unwrap(), Asked::UnInhibit(43));
597 stop.store(true, Ordering::Relaxed);
598 }
599
600 #[test]
603 fn plasma_restarted_is_asked_again() {
604 let _alone = ON_THE_BUS.lock().unwrap_or_else(|e| e.into_inner());
605 let (told, asked) = channel();
606 let first_stop = Arc::new(AtomicBool::new(false));
607 if stand_in(7, told.clone(), Arc::clone(&first_stop), Duration::from_secs(30)).is_none() {
608 eprintln!("No bus of the test's own (dbus-run-session), or a notification server on it already: skipped.");
609 return;
610 }
611 let wait = Duration::from_secs(5);
612 let held = inhibit("com.aurelienpierre.Sioul", "Pause").expect("inhibited");
613 assert!(matches!(asked.recv_timeout(wait).unwrap(), Asked::Inhibit { .. }));
614 first_stop.store(true, Ordering::Relaxed);
616 let until = Instant::now() + wait;
617 let second_stop = Arc::new(AtomicBool::new(false));
618 while stand_in(100, told.clone(), Arc::clone(&second_stop), Duration::from_secs(30)).is_none() {
619 assert!(Instant::now() < until, "the second stand-in could not take the name");
620 std::thread::sleep(Duration::from_millis(50));
621 }
622 assert!(matches!(asked.recv_timeout(wait).unwrap(), Asked::Inhibit { reason, .. } if reason == "Pause"), "asked again of the new server");
623 held.release(wait).expect("lifted");
624 assert_eq!(asked.recv_timeout(wait).unwrap(), Asked::UnInhibit(100));
625 second_stop.store(true, Ordering::Relaxed);
626 }
627
628 #[test]
633 fn the_session_s_do_not_disturb_is_followed() {
634 let _alone = ON_THE_BUS.lock().unwrap_or_else(|e| e.into_inner());
635 let (told, _asked) = channel();
636 let stop = Arc::new(AtomicBool::new(false));
637 let (set, announced) = channel();
638 let says = Says { value: Arc::new(AtomicBool::new(false)), set: announced };
639 if stand_in_saying(1, told.clone(), Arc::clone(&stop), Duration::from_secs(40), Some(says)).is_none() {
640 eprintln!("No bus of the test's own (dbus-run-session), or a notification server on it already: skipped.");
641 return;
642 }
643 assert_eq!(inhibited(), Some(false));
644 let quiet = Arc::new(Mutex::new(Instant::now()));
645 let (heard, hears) = channel();
646 let watch = watch_inhibited(Arc::clone(&quiet), Box::new(move |on| {
647 let _ = heard.send(on);
648 }))
649 .expect("following");
650 assert_eq!(watch.seen(), Some(false), "read at the start, not told");
651 set.send(true).unwrap();
653 assert_eq!(hears.recv_timeout(Duration::from_secs(5)), Ok(true));
654 assert_eq!(watch.seen(), Some(true));
655 set.send(false).unwrap();
657 set.send(true).unwrap();
658 assert!(hears.recv_timeout(Duration::from_millis(2_500)).is_err(), "a flicker is no change");
659 *quiet.lock().unwrap() = Instant::now() + Duration::from_secs(2);
661 set.send(false).unwrap();
662 assert!(hears.recv_timeout(Duration::from_millis(1_500)).is_err(), "not before Sioul's change settled");
663 assert_eq!(hears.recv_timeout(Duration::from_secs(4)), Ok(false));
664 set.send(true).unwrap();
667 assert_eq!(hears.recv_timeout(Duration::from_secs(5)), Ok(true));
668 *quiet.lock().unwrap() = Instant::now() + Duration::from_secs(2);
669 set.send(false).unwrap();
670 std::thread::sleep(Duration::from_millis(300));
671 set.send(true).unwrap();
672 assert!(hears.recv_timeout(Duration::from_millis(4_000)).is_err(), "on before, on after: nothing");
673 assert_eq!(watch.seen(), Some(true));
674 stop.store(true, Ordering::Relaxed);
676 let wait = Duration::from_secs(5);
677 let second_stop = Arc::new(AtomicBool::new(false));
678 let (_set_again, announced_again) = channel();
679 let says_again = Says { value: Arc::new(AtomicBool::new(true)), set: announced_again };
680 assert!(stand_in_saying(50, told.clone(), Arc::clone(&second_stop), Duration::from_secs(30), Some(says_again)).is_some(), "the second stand-in took the name");
682 let until = Instant::now() + wait;
683 while watch.seen() != Some(true) && Instant::now() < until {
684 std::thread::sleep(Duration::from_millis(50));
685 }
686 assert_eq!(watch.seen(), Some(true), "the new server read");
687 assert!(hears.recv_timeout(Duration::from_millis(500)).is_err(), "and not told");
688 assert!(watch.alive());
689 second_stop.store(true, Ordering::Relaxed);
690 }
691
692 #[test]
694 fn dconf_tells_each_change_of_its_key() {
695 let _alone = ON_THE_BUS.lock().unwrap_or_else(|e| e.into_inner());
696 let free = |bus: &LocalConnection| {
697 let until = Instant::now() + Duration::from_secs(1);
698 while has_owner(bus, SERVER) && Instant::now() < until {
699 std::thread::sleep(Duration::from_millis(50));
700 }
701 !has_owner(bus, SERVER)
702 };
703 let Some(bus) = LocalConnection::new_session().ok().filter(|bus| free(bus)) else {
704 eprintln!("No bus of the test's own (dbus-run-session): skipped.");
705 return;
706 };
707 let key = "/org/gnome/desktop/notifications/show-banners";
708 let (heard, hears) = channel();
709 let _changes = watch_dconf_told(key, Box::new(move || {
710 let _ = heard.send(());
711 }))
712 .expect("listening");
713 let notify = |prefix: &str, changed: Vec<&str>| {
714 let signal = dbus::Message::new_signal("/ca/desrt/dconf/Writer/user", DCONF_WRITER, "Notify").unwrap().append3(prefix, changed, "tag");
715 let _ = bus.send(signal);
716 bus.process(Duration::from_millis(10)).ok();
717 };
718 notify("/org/gnome/desktop/interface/", vec!["color-scheme"]);
719 notify(key, vec![""]);
720 assert_eq!(hears.recv_timeout(Duration::from_secs(5)), Ok(()));
721 assert!(hears.recv_timeout(Duration::from_millis(300)).is_err(), "another key's change is not told");
722 }
723
724 #[test]
728 fn dconf_changes_of_a_key_are_heard() {
729 let _alone = ON_THE_BUS.lock().unwrap_or_else(|e| e.into_inner());
730 let free = |bus: &LocalConnection| {
732 let until = Instant::now() + Duration::from_secs(1);
733 while has_owner(bus, SERVER) && Instant::now() < until {
734 std::thread::sleep(Duration::from_millis(50));
735 }
736 !has_owner(bus, SERVER)
737 };
738 let Some(bus) = LocalConnection::new_session().ok().filter(|bus| free(bus)) else {
739 eprintln!("No bus of the test's own (dbus-run-session): skipped.");
740 return;
741 };
742 let key = "/org/gnome/desktop/notifications/show-banners";
743 let changes = watch_dconf(key).expect("listening");
744 let start = Instant::now();
745 let notify = |prefix: &str, changed: Vec<&str>| {
746 let signal = dbus::Message::new_signal("/ca/desrt/dconf/Writer/user", DCONF_WRITER, "Notify").unwrap().append3(prefix, changed, "tag");
747 let _ = bus.send(signal);
748 bus.process(Duration::from_millis(10)).ok();
749 };
750 notify("/org/gnome/desktop/interface/", vec!["color-scheme"]);
751 notify(key, vec![""]);
752 notify("/org/gnome/desktop/notifications/", vec!["show-in-lock-screen", "show-banners"]);
753 let until = Instant::now() + Duration::from_secs(5);
754 while changes.since(start) < 2 && Instant::now() < until {
755 std::thread::sleep(Duration::from_millis(20));
756 }
757 std::thread::sleep(Duration::from_millis(200));
758 assert_eq!(changes.since(start), 2, "two changes of the key, one of another");
759 assert_eq!(changes.since(Instant::now()), 0);
760 assert!(changes.alive());
761 }
762}