status bar
git clone https://git.lucas.co/cce-status-interface.git
src/bin/cce-xembed-tray/main.rs (2.8K)
1 //! `cce-xembed-tray` — the legacy X11 system tray, bridged into the bar.
2 //!
3 //! X11 apps that predate StatusNotifierItem (Wine and Proton programs above
4 //! all) put their tray icons in an XEmbed tray: whichever X client owns the
5 //! `_NET_SYSTEM_TRAY_S0` selection. With no owner, Wine falls back to a
6 //! window of its own holding the icons, which on this desktop showed up as
7 //! a blank white window beside Ubisoft Connect. This process owns that
8 //! selection, adopts each icon window into a container the compositor never
9 //! shows, and republishes it as an SNI item the status bar's tray hosts:
10 //! pixels from the icon window as it redraws, clicks forwarded as X button
11 //! events (left = Activate, right = ContextMenu, so the app draws its own
12 //! menu).
13
14 mod sni;
15 mod title;
16 mod x11;
17
18 use std::sync::atomic::{AtomicI32, Ordering};
19 use std::sync::Arc;
20
21 fn connect() -> x11::Res<(Arc<x11rb::rust_connection::RustConnection>, usize)> {
22 // Xwayland comes up with the compositor, which may still be starting
23 // when the session's units are.
24 let mut last = None;
25 for attempt in 0..10 {
26 if attempt > 0 {
27 std::thread::sleep(std::time::Duration::from_secs(1));
28 }
29 match x11rb::connect(None) {
30 Ok((conn, screen)) => return Ok((Arc::new(conn), screen)),
31 Err(e) => last = Some(e),
32 }
33 }
34 Err(format!("cannot connect to X: {}", last.map(|e| e.to_string()).unwrap_or_default()).into())
35 }
36
37 #[tokio::main(flavor = "current_thread")]
38 async fn main() {
39 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
40
41 let (conn, screen) = match connect() {
42 Ok(c) => c,
43 Err(e) => {
44 log::error!("{e}");
45 std::process::exit(1);
46 }
47 };
48 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
49 // The D-Bus side's own way into the same queue: late title lookups.
50 let retry_tx = tx.downgrade();
51 let mut tray = match x11::Tray::new(conn, screen, tx) {
52 Ok(t) => t,
53 Err(e) => {
54 log::error!("X setup failed: {e}");
55 std::process::exit(1);
56 }
57 };
58 let handle = Arc::new(tray.handle());
59
60 // 0 once another tray took over cleanly; 1 when X failed, so systemd
61 // restarts us.
62 let code = Arc::new(AtomicI32::new(0));
63 let x_code = code.clone();
64 std::thread::spawn(move || {
65 let result = tray.acquire().and_then(|()| tray.run());
66 if let Err(e) = result {
67 log::error!("X side stopped: {e}");
68 x_code.store(1, Ordering::SeqCst);
69 }
70 // Dropping `tray` closes the channel, which ends `sni::run`.
71 });
72
73 if let Err(e) = sni::run(handle, rx, retry_tx).await {
74 log::error!("D-Bus side failed: {e}");
75 code.store(1, Ordering::SeqCst);
76 }
77 std::process::exit(code.load(Ordering::SeqCst));
78 }