From c732424e7dbd9e462632714c7cf056868d6f5a41 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sun, 14 Dec 2025 20:28:15 +0900 Subject: [PATCH] dbus refactor --- wlx-overlay-s/src/backend/openvr/mod.rs | 3 +- wlx-overlay-s/src/backend/openxr/mod.rs | 3 +- wlx-overlay-s/src/main.rs | 6 +- wlx-overlay-s/src/overlays/keyboard/mod.rs | 23 +++ wlx-overlay-s/src/overlays/screen/pw.rs | 16 +- wlx-overlay-s/src/overlays/screen/wl.rs | 2 +- wlx-overlay-s/src/overlays/screen/x11.rs | 1 + wlx-overlay-s/src/state.rs | 7 +- wlx-overlay-s/src/subsystem/dbus/mod.rs | 133 ++++++++++++++ .../notifications.rs} | 0 wlx-overlay-s/src/subsystem/mod.rs | 1 + .../mod.rs => notifications.rs} | 170 ++++-------------- 12 files changed, 215 insertions(+), 150 deletions(-) create mode 100644 wlx-overlay-s/src/subsystem/dbus/mod.rs rename wlx-overlay-s/src/subsystem/{notifications/notifications_dbus.rs => dbus/notifications.rs} (100%) rename wlx-overlay-s/src/subsystem/{notifications/mod.rs => notifications.rs} (52%) diff --git a/wlx-overlay-s/src/backend/openvr/mod.rs b/wlx-overlay-s/src/backend/openvr/mod.rs index 7308a5c..f800933 100644 --- a/wlx-overlay-s/src/backend/openvr/mod.rs +++ b/wlx-overlay-s/src/backend/openvr/mod.rs @@ -115,7 +115,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr let mut overlays = OverlayWindowManager::::new(&mut app, headless)?; let mut notifications = NotificationManager::new(); - notifications.run_dbus(); + notifications.run_dbus(&mut app.dbus); notifications.run_udp(); let mut playspace = playspace::PlayspaceMover::new(); @@ -198,6 +198,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr next_device_update = Instant::now() + Duration::from_secs(30); } + app.dbus.tick(); notifications.submit_pending(&mut app); app.tasks.retrieve_due(&mut due_tasks); diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs index bfbbc15..8ac2b2c 100644 --- a/wlx-overlay-s/src/backend/openxr/mod.rs +++ b/wlx-overlay-s/src/backend/openxr/mod.rs @@ -96,7 +96,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr let mut lines = LinePool::new(&app)?; let mut notifications = NotificationManager::new(); - notifications.run_dbus(); + notifications.run_dbus(&mut app.dbus); notifications.run_udp(); let mut delete_queue = vec![]; @@ -480,6 +480,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr )?; // End layer submit + app.dbus.tick(); notifications.submit_pending(&mut app); app.tasks.retrieve_due(&mut due_tasks); diff --git a/wlx-overlay-s/src/main.rs b/wlx-overlay-s/src/main.rs index 7d7b09a..174b6ad 100644 --- a/wlx-overlay-s/src/main.rs +++ b/wlx-overlay-s/src/main.rs @@ -37,11 +37,12 @@ use std::{ }; use clap::Parser; -use subsystem::notifications::DbusNotificationSender; use sysinfo::Pid; use tracing::level_filters::LevelFilter; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; +use crate::subsystem::dbus::DbusConnector; + pub static FRAME_COUNTER: AtomicUsize = AtomicUsize::new(0); pub static RUNNING: AtomicBool = AtomicBool::new(true); @@ -168,8 +169,7 @@ fn auto_run(args: Args) { let instructions = format!("Could not connect to runtime.\n{instructions}"); - let _ = DbusNotificationSender::new() - .and_then(|s| s.notify_send("WlxOverlay-S", &instructions, 1, 0, 0, false)); + let _ = DbusConnector::default().notify_send("WlxOverlay-S", &instructions, 1, 0, 0, false); #[cfg(not(any(feature = "openvr", feature = "openxr")))] compile_error!("No VR support! Enable either openvr or openxr features!"); diff --git a/wlx-overlay-s/src/overlays/keyboard/mod.rs b/wlx-overlay-s/src/overlays/keyboard/mod.rs index 703f85f..2f23169 100644 --- a/wlx-overlay-s/src/overlays/keyboard/mod.rs +++ b/wlx-overlay-s/src/overlays/keyboard/mod.rs @@ -17,6 +17,7 @@ use crate::{ window::OverlayWindowConfig, }, }; +use dbus::message::MatchRule; use glam::{Affine3A, Quat, Vec3, vec3}; use slotmap::{SlotMap, new_key_type}; use wgui::{ @@ -64,6 +65,7 @@ pub fn create_keyboard( }; backend.active_keymap = backend.add_new_keymap(keymap.as_ref(), app)?; + backend.watch_dbus(app); Ok(OverlayWindowConfig { name: KEYBOARD_NAME.into(), @@ -112,6 +114,27 @@ impl KeyboardBackend { Ok(id) } + fn watch_dbus(&mut self, app: &mut AppState) { + let rules = [ + MatchRule::new() + .with_member("CurrentInputMethod") + .with_interface("org.fcitx.Fcitx.Controller1") + .with_path("/controller") + .with_sender("org.fcitx.Fcitx5"), + MatchRule::new_signal("org.kde.KeyboardLayouts", "layoutChanged").with_path("/Layouts"), + ]; + + for rule in rules { + let _ = app.dbus.add_match( + rule, + Box::new(move |(), _, msg| { + log::warn!("new keymap: {msg:?}"); + true + }), + ); + } + } + fn switch_keymap(&mut self, keymap: &XkbKeymap, app: &mut AppState) -> anyhow::Result<()> { let Some(layout_name) = keymap.inner.layouts().next() else { log::error!("XKB keymap without a layout!"); diff --git a/wlx-overlay-s/src/overlays/screen/pw.rs b/wlx-overlay-s/src/overlays/screen/pw.rs index c451cba..f071bce 100644 --- a/wlx-overlay-s/src/overlays/screen/pw.rs +++ b/wlx-overlay-s/src/overlays/screen/pw.rs @@ -20,12 +20,13 @@ impl ScreenBackend { pub fn new_pw( output: &WlxOutput, token: Option<&str>, - app: &AppState, + app: &mut AppState, ) -> anyhow::Result<(Self, Option /* pipewire restore token */)> { let name = output.name.clone(); let embed_mouse = !app.session.config.double_cursor_fix; let select_screen_result = select_pw_screen( + app, &format!( "Now select: {} {} {} @ {},{}", &output.name, @@ -56,6 +57,7 @@ impl ScreenBackend { #[allow(clippy::fn_params_excessive_bools)] pub(super) fn select_pw_screen( + app: &mut AppState, instructions: &str, token: Option<&str>, embed_mouse: bool, @@ -63,7 +65,6 @@ pub(super) fn select_pw_screen( persist: bool, multiple: bool, ) -> Result { - use crate::subsystem::notifications::DbusNotificationSender; use std::time::Duration; use wlx_capture::pipewire::pipewire_select_screen; @@ -80,10 +81,8 @@ pub(super) fn select_pw_screen( task::Poll::Pending => { if Instant::now() >= print_at { log::info!("{instructions}"); - if let Ok(sender) = DbusNotificationSender::new() - && let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) - { - notify = Some((sender, id)); + if let Ok(id) = app.dbus.notify_send(instructions, "", 2, 30, 0, true) { + notify = Some(id); } break; } @@ -96,8 +95,9 @@ pub(super) fn select_pw_screen( } let result = f.await; - if let Some((sender, id)) = notify { - let _ = sender.notify_close(id); + if let Some(id) = notify { + //safe unwrap; checked above + let _ = app.dbus.notify_close(id); } result }; diff --git a/wlx-overlay-s/src/overlays/screen/wl.rs b/wlx-overlay-s/src/overlays/screen/wl.rs index e1e8b56..6530d45 100644 --- a/wlx-overlay-s/src/overlays/screen/wl.rs +++ b/wlx-overlay-s/src/overlays/screen/wl.rs @@ -45,7 +45,7 @@ pub fn create_screen_renderer_wl( has_wlr_dmabuf: bool, has_wlr_screencopy: bool, pw_token_store: &mut PwTokenMap, - app: &AppState, + app: &mut AppState, ) -> Option { let mut capture: Option = None; if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf { diff --git a/wlx-overlay-s/src/overlays/screen/x11.rs b/wlx-overlay-s/src/overlays/screen/x11.rs index 48e4257..70cb369 100644 --- a/wlx-overlay-s/src/overlays/screen/x11.rs +++ b/wlx-overlay-s/src/overlays/screen/x11.rs @@ -54,6 +54,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result, @@ -109,6 +111,8 @@ impl AppState { .and_then(|c| parse_color_hex(&c)) .unwrap_or(defaults.faded_color); + let dbus = DbusConnector::default(); + Ok(Self { session, tasks, @@ -128,6 +132,7 @@ impl AppState { &WguiFontConfig::default(), get_config_file_path(&theme), )?, + dbus, #[cfg(feature = "osc")] osc_sender, diff --git a/wlx-overlay-s/src/subsystem/dbus/mod.rs b/wlx-overlay-s/src/subsystem/dbus/mod.rs new file mode 100644 index 0000000..1590ebd --- /dev/null +++ b/wlx-overlay-s/src/subsystem/dbus/mod.rs @@ -0,0 +1,133 @@ +use std::time::Duration; + +use anyhow::Context; +use dbus::{ + Message, + arg::{PropMap, Variant}, + blocking::Connection, + channel::MatchingReceiver, + message::MatchRule, +}; + +use crate::subsystem::dbus::notifications::OrgFreedesktopNotifications; + +mod notifications; + +pub type DbusReceiveCallback = Box bool + Send>; +pub type DbusMatchCallback = Box bool + Send>; + +#[derive(Default)] +pub struct DbusConnector { + pub connection: Option, +} + +impl DbusConnector { + pub fn tick(&self) { + if let Some(c) = self.connection.as_ref() { + let _ = c.process(Duration::ZERO); + } + } + + pub fn become_monitor( + &mut self, + rule: MatchRule<'static>, + callback: DbusReceiveCallback, + ) -> anyhow::Result<()> { + let connection = self + .connection + .take() + .context("Not connected") + .or_else(|_| Connection::new_session())?; + + let proxy = connection.with_proxy( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + Duration::from_millis(5000), + ); + let result: Result<(), dbus::Error> = proxy.method_call( + "org.freedesktop.DBus.Monitoring", + "BecomeMonitor", + (vec![rule.match_str()], 0u32), + ); + + result?; + + let _ = connection.start_receive(rule, callback); + + self.connection = Some(connection); + Ok(()) + } + + pub fn add_match( + &mut self, + rule: MatchRule<'static>, + callback: DbusMatchCallback, + ) -> anyhow::Result<()> { + let connection = self + .connection + .take() + .context("Not connected") + .or_else(|_| Connection::new_session())?; + + let _ = connection.add_match(rule, callback)?; + self.connection = Some(connection); + Ok(()) + } + + pub fn notify_send( + &mut self, + summary: &str, + body: &str, + urgency: u8, + timeout: i32, + replaces_id: u32, + transient: bool, + ) -> anyhow::Result { + let connection = self + .connection + .take() + .context("Not connected") + .or_else(|_| Connection::new_session())?; + + let proxy = connection.with_proxy( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + Duration::from_millis(1000), + ); + + let mut hints = PropMap::new(); + hints.insert("urgency".to_string(), Variant(Box::new(urgency))); + hints.insert("transient".to_string(), Variant(Box::new(transient))); + + let retval = proxy.notify( + "WlxOverlay-S", + replaces_id, + "", + summary, + body, + vec![], + hints, + timeout, + )?; + self.connection = Some(connection); + + Ok(retval) + } + + pub fn notify_close(&mut self, id: u32) -> anyhow::Result<()> { + let connection = self + .connection + .take() + .context("Not connected") + .or_else(|_| Connection::new_session())?; + let proxy = connection.with_proxy( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + Duration::from_millis(1000), + ); + + proxy.close_notification(id)?; + self.connection = Some(connection); + Ok(()) + } +} diff --git a/wlx-overlay-s/src/subsystem/notifications/notifications_dbus.rs b/wlx-overlay-s/src/subsystem/dbus/notifications.rs similarity index 100% rename from wlx-overlay-s/src/subsystem/notifications/notifications_dbus.rs rename to wlx-overlay-s/src/subsystem/dbus/notifications.rs diff --git a/wlx-overlay-s/src/subsystem/mod.rs b/wlx-overlay-s/src/subsystem/mod.rs index d547991..bfeec33 100644 --- a/wlx-overlay-s/src/subsystem/mod.rs +++ b/wlx-overlay-s/src/subsystem/mod.rs @@ -1,4 +1,5 @@ pub mod audio; +pub mod dbus; pub mod hid; pub mod input; pub mod notifications; diff --git a/wlx-overlay-s/src/subsystem/notifications/mod.rs b/wlx-overlay-s/src/subsystem/notifications.rs similarity index 52% rename from wlx-overlay-s/src/subsystem/notifications/mod.rs rename to wlx-overlay-s/src/subsystem/notifications.rs index 1b85c45..a5a5dfc 100644 --- a/wlx-overlay-s/src/subsystem/notifications/mod.rs +++ b/wlx-overlay-s/src/subsystem/notifications.rs @@ -1,14 +1,5 @@ -#[allow(clippy::all)] -mod notifications_dbus; - use anyhow::Context; -use dbus::{ - arg::{PropMap, Variant}, - blocking::Connection, - channel::MatchingReceiver, - message::MatchRule, -}; -use notifications_dbus::OrgFreedesktopNotifications; +use dbus::message::MatchRule; use serde::Deserialize; use std::{ sync::{ @@ -20,12 +11,11 @@ use std::{ }; use wlx_common::overlays::ToastTopic; -use crate::{overlays::toast::Toast, state::AppState}; +use crate::{overlays::toast::Toast, state::AppState, subsystem::dbus::DbusConnector}; pub struct NotificationManager { rx_toast: mpsc::Receiver, tx_toast: mpsc::SyncSender, - dbus_data: Option, running: Arc, } @@ -35,16 +25,11 @@ impl NotificationManager { Self { rx_toast, tx_toast, - dbus_data: None, running: Arc::new(AtomicBool::new(true)), } } pub fn submit_pending(&self, app: &mut AppState) { - if let Some(c) = &self.dbus_data { - let _ = c.process(Duration::ZERO); - } - if app.session.config.notifications_enabled { self.rx_toast.try_iter().for_each(|toast| { toast.submit(app); @@ -55,78 +40,47 @@ impl NotificationManager { } } - pub fn run_dbus(&mut self) { - let Ok(conn) = Connection::new_session().context( - "Failed to connect to dbus. Desktop notifications and keymap changes will not work.", - ) else { - return; - }; + pub fn run_dbus(&mut self, dbus: &mut DbusConnector) { + let rule = MatchRule::new_method_call() + .with_member("Notify") + .with_interface("org.freedesktop.Notifications") + .with_path("/org/freedesktop/Notifications"); - let mut rule = MatchRule::new_method_call(); - rule.member = Some("Notify".into()); - rule.interface = Some("org.freedesktop.Notifications".into()); - rule.path = Some("/org/freedesktop/Notifications".into()); - rule.eavesdrop = true; - - let proxy = conn.with_proxy( - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - Duration::from_millis(5000), - ); - let result: Result<(), dbus::Error> = proxy.method_call( - "org.freedesktop.DBus.Monitoring", - "BecomeMonitor", - (vec![rule.match_str()], 0u32), - ); - - if matches!(result, Ok(())) { - let sender = self.tx_toast.clone(); - conn.start_receive( - rule, + let sender = self.tx_toast.clone(); + if let Ok(_) = dbus + .become_monitor( + rule.clone(), Box::new(move |msg, _| { if let Ok(toast) = parse_dbus(&msg) { - match sender.try_send(toast) { - Ok(()) => {} - Err(e) => { - log::error!("Failed to send notification: {e:?}"); - } - } + let _ = sender + .try_send(toast) + .inspect_err(|e| log::error!("Failed to send notification: {e:?}")); } true }), - ); - log::info!("Listening to DBus notifications via BecomeMonitor."); - } else { - let rule_with_eavesdrop = { - let mut rule = rule.clone(); - rule.eavesdrop = true; - rule - }; - - let sender2 = self.tx_toast.clone(); - let result = conn.add_match(rule_with_eavesdrop, move |(): (), _, msg| { - if let Ok(toast) = parse_dbus(msg) { - match sender2.try_send(toast) { - Ok(()) => {} - Err(e) => { - log::error!("Failed to send notification: {e:?}"); - } - } - } - true - }); - - match result { - Ok(_) => { - log::info!("Listening to DBus notifications via eavesdrop."); - } - Err(_) => { - log::error!("Failed to add DBus match. Desktop notifications will not work.",); - } - } + ) + .context("Could not register BecomeMonitor") + .inspect_err(|e| log::warn!("{e:?}")) + { + log::info!("Listening to D-Bus notifications via BecomeMonitor."); + return; } - self.dbus_data = Some(conn); + let sender = self.tx_toast.clone(); + let _ = dbus + .add_match( + rule.with_eavesdrop(), + Box::new(move |(), _, msg| { + if let Ok(toast) = parse_dbus(msg) { + let _ = sender + .try_send(toast) + .inspect_err(|e| log::error!("Failed to send notification: {e:?}")); + } + true + }), + ) + .context("Failed to register D-Bus notifications. Desktop notifications won't work.") + .inspect_err(|e| log::warn!("{e:?}")); } pub fn run_udp(&mut self) { @@ -195,60 +149,6 @@ impl Drop for NotificationManager { } } -pub struct DbusNotificationSender { - connection: Connection, -} - -impl DbusNotificationSender { - pub fn new() -> anyhow::Result { - Ok(Self { - connection: Connection::new_session()?, - }) - } - - pub fn notify_send( - &self, - summary: &str, - body: &str, - urgency: u8, - timeout: i32, - replaces_id: u32, - transient: bool, - ) -> anyhow::Result { - let proxy = self.connection.with_proxy( - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - Duration::from_millis(1000), - ); - - let mut hints = PropMap::new(); - hints.insert("urgency".to_string(), Variant(Box::new(urgency))); - hints.insert("transient".to_string(), Variant(Box::new(transient))); - - Ok(proxy.notify( - "WlxOverlay-S", - replaces_id, - "", - summary, - body, - vec![], - hints, - timeout, - )?) - } - - pub fn notify_close(&self, id: u32) -> anyhow::Result<()> { - let proxy = self.connection.with_proxy( - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - Duration::from_millis(1000), - ); - - proxy.close_notification(id)?; - Ok(()) - } -} - fn parse_dbus(msg: &dbus::Message) -> anyhow::Result { let mut args = msg.iter_init(); let app_name: String = args.read()?;