git.lucas.co / cce-status-interface
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 }