dbus refactor

This commit is contained in:
galister
2025-12-14 20:28:15 +09:00
parent f2f02855e3
commit c732424e7d
12 changed files with 215 additions and 150 deletions

View File

@@ -115,7 +115,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, headless)?; let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, headless)?;
let mut notifications = NotificationManager::new(); let mut notifications = NotificationManager::new();
notifications.run_dbus(); notifications.run_dbus(&mut app.dbus);
notifications.run_udp(); notifications.run_udp();
let mut playspace = playspace::PlayspaceMover::new(); 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); next_device_update = Instant::now() + Duration::from_secs(30);
} }
app.dbus.tick();
notifications.submit_pending(&mut app); notifications.submit_pending(&mut app);
app.tasks.retrieve_due(&mut due_tasks); app.tasks.retrieve_due(&mut due_tasks);

View File

@@ -96,7 +96,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let mut lines = LinePool::new(&app)?; let mut lines = LinePool::new(&app)?;
let mut notifications = NotificationManager::new(); let mut notifications = NotificationManager::new();
notifications.run_dbus(); notifications.run_dbus(&mut app.dbus);
notifications.run_udp(); notifications.run_udp();
let mut delete_queue = vec![]; let mut delete_queue = vec![];
@@ -480,6 +480,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
)?; )?;
// End layer submit // End layer submit
app.dbus.tick();
notifications.submit_pending(&mut app); notifications.submit_pending(&mut app);
app.tasks.retrieve_due(&mut due_tasks); app.tasks.retrieve_due(&mut due_tasks);

View File

@@ -37,11 +37,12 @@ use std::{
}; };
use clap::Parser; use clap::Parser;
use subsystem::notifications::DbusNotificationSender;
use sysinfo::Pid; use sysinfo::Pid;
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use crate::subsystem::dbus::DbusConnector;
pub static FRAME_COUNTER: AtomicUsize = AtomicUsize::new(0); pub static FRAME_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub static RUNNING: AtomicBool = AtomicBool::new(true); 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 instructions = format!("Could not connect to runtime.\n{instructions}");
let _ = DbusNotificationSender::new() let _ = DbusConnector::default().notify_send("WlxOverlay-S", &instructions, 1, 0, 0, false);
.and_then(|s| s.notify_send("WlxOverlay-S", &instructions, 1, 0, 0, false));
#[cfg(not(any(feature = "openvr", feature = "openxr")))] #[cfg(not(any(feature = "openvr", feature = "openxr")))]
compile_error!("No VR support! Enable either openvr or openxr features!"); compile_error!("No VR support! Enable either openvr or openxr features!");

View File

@@ -17,6 +17,7 @@ use crate::{
window::OverlayWindowConfig, window::OverlayWindowConfig,
}, },
}; };
use dbus::message::MatchRule;
use glam::{Affine3A, Quat, Vec3, vec3}; use glam::{Affine3A, Quat, Vec3, vec3};
use slotmap::{SlotMap, new_key_type}; use slotmap::{SlotMap, new_key_type};
use wgui::{ use wgui::{
@@ -64,6 +65,7 @@ pub fn create_keyboard(
}; };
backend.active_keymap = backend.add_new_keymap(keymap.as_ref(), app)?; backend.active_keymap = backend.add_new_keymap(keymap.as_ref(), app)?;
backend.watch_dbus(app);
Ok(OverlayWindowConfig { Ok(OverlayWindowConfig {
name: KEYBOARD_NAME.into(), name: KEYBOARD_NAME.into(),
@@ -112,6 +114,27 @@ impl KeyboardBackend {
Ok(id) 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<()> { fn switch_keymap(&mut self, keymap: &XkbKeymap, app: &mut AppState) -> anyhow::Result<()> {
let Some(layout_name) = keymap.inner.layouts().next() else { let Some(layout_name) = keymap.inner.layouts().next() else {
log::error!("XKB keymap without a layout!"); log::error!("XKB keymap without a layout!");

View File

@@ -20,12 +20,13 @@ impl ScreenBackend {
pub fn new_pw( pub fn new_pw(
output: &WlxOutput, output: &WlxOutput,
token: Option<&str>, token: Option<&str>,
app: &AppState, app: &mut AppState,
) -> anyhow::Result<(Self, Option<String> /* pipewire restore token */)> { ) -> anyhow::Result<(Self, Option<String> /* pipewire restore token */)> {
let name = output.name.clone(); let name = output.name.clone();
let embed_mouse = !app.session.config.double_cursor_fix; let embed_mouse = !app.session.config.double_cursor_fix;
let select_screen_result = select_pw_screen( let select_screen_result = select_pw_screen(
app,
&format!( &format!(
"Now select: {} {} {} @ {},{}", "Now select: {} {} {} @ {},{}",
&output.name, &output.name,
@@ -56,6 +57,7 @@ impl ScreenBackend {
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(super) fn select_pw_screen( pub(super) fn select_pw_screen(
app: &mut AppState,
instructions: &str, instructions: &str,
token: Option<&str>, token: Option<&str>,
embed_mouse: bool, embed_mouse: bool,
@@ -63,7 +65,6 @@ pub(super) fn select_pw_screen(
persist: bool, persist: bool,
multiple: bool, multiple: bool,
) -> Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError> { ) -> Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError> {
use crate::subsystem::notifications::DbusNotificationSender;
use std::time::Duration; use std::time::Duration;
use wlx_capture::pipewire::pipewire_select_screen; use wlx_capture::pipewire::pipewire_select_screen;
@@ -80,10 +81,8 @@ pub(super) fn select_pw_screen(
task::Poll::Pending => { task::Poll::Pending => {
if Instant::now() >= print_at { if Instant::now() >= print_at {
log::info!("{instructions}"); log::info!("{instructions}");
if let Ok(sender) = DbusNotificationSender::new() if let Ok(id) = app.dbus.notify_send(instructions, "", 2, 30, 0, true) {
&& let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) notify = Some(id);
{
notify = Some((sender, id));
} }
break; break;
} }
@@ -96,8 +95,9 @@ pub(super) fn select_pw_screen(
} }
let result = f.await; let result = f.await;
if let Some((sender, id)) = notify { if let Some(id) = notify {
let _ = sender.notify_close(id); //safe unwrap; checked above
let _ = app.dbus.notify_close(id);
} }
result result
}; };

View File

@@ -45,7 +45,7 @@ pub fn create_screen_renderer_wl(
has_wlr_dmabuf: bool, has_wlr_dmabuf: bool,
has_wlr_screencopy: bool, has_wlr_screencopy: bool,
pw_token_store: &mut PwTokenMap, pw_token_store: &mut PwTokenMap,
app: &AppState, app: &mut AppState,
) -> Option<ScreenBackend> { ) -> Option<ScreenBackend> {
let mut capture: Option<ScreenBackend> = None; let mut capture: Option<ScreenBackend> = None;
if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf { if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf {

View File

@@ -54,6 +54,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
let embed_mouse = !app.session.config.double_cursor_fix; let embed_mouse = !app.session.config.double_cursor_fix;
let select_screen_result = select_pw_screen( let select_screen_result = select_pw_screen(
app,
"Select ALL screens on the screencast pop-up!", "Select ALL screens on the screencast pop-up!",
token, token,
embed_mouse, embed_mouse,

View File

@@ -27,7 +27,7 @@ use crate::{
config_io::{self, get_config_file_path}, config_io::{self, get_config_file_path},
graphics::WGfxExtras, graphics::WGfxExtras,
gui, gui,
subsystem::{audio::AudioOutput, input::HidWrapper}, subsystem::{audio::AudioOutput, dbus::DbusConnector, input::HidWrapper},
}; };
pub struct AppState { pub struct AppState {
@@ -49,6 +49,8 @@ pub struct AppState {
pub wgui_globals: WguiGlobals, pub wgui_globals: WguiGlobals,
pub dbus: DbusConnector,
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
pub osc_sender: Option<OscSender>, pub osc_sender: Option<OscSender>,
@@ -109,6 +111,8 @@ impl AppState {
.and_then(|c| parse_color_hex(&c)) .and_then(|c| parse_color_hex(&c))
.unwrap_or(defaults.faded_color); .unwrap_or(defaults.faded_color);
let dbus = DbusConnector::default();
Ok(Self { Ok(Self {
session, session,
tasks, tasks,
@@ -128,6 +132,7 @@ impl AppState {
&WguiFontConfig::default(), &WguiFontConfig::default(),
get_config_file_path(&theme), get_config_file_path(&theme),
)?, )?,
dbus,
#[cfg(feature = "osc")] #[cfg(feature = "osc")]
osc_sender, osc_sender,

View File

@@ -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<dyn FnMut(Message, &Connection) -> bool + Send>;
pub type DbusMatchCallback = Box<dyn FnMut((), &Connection, &Message) -> bool + Send>;
#[derive(Default)]
pub struct DbusConnector {
pub connection: Option<Connection>,
}
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<u32> {
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(())
}
}

View File

@@ -1,4 +1,5 @@
pub mod audio; pub mod audio;
pub mod dbus;
pub mod hid; pub mod hid;
pub mod input; pub mod input;
pub mod notifications; pub mod notifications;

View File

@@ -1,14 +1,5 @@
#[allow(clippy::all)]
mod notifications_dbus;
use anyhow::Context; use anyhow::Context;
use dbus::{ use dbus::message::MatchRule;
arg::{PropMap, Variant},
blocking::Connection,
channel::MatchingReceiver,
message::MatchRule,
};
use notifications_dbus::OrgFreedesktopNotifications;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
sync::{ sync::{
@@ -20,12 +11,11 @@ use std::{
}; };
use wlx_common::overlays::ToastTopic; 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 { pub struct NotificationManager {
rx_toast: mpsc::Receiver<Toast>, rx_toast: mpsc::Receiver<Toast>,
tx_toast: mpsc::SyncSender<Toast>, tx_toast: mpsc::SyncSender<Toast>,
dbus_data: Option<Connection>,
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
} }
@@ -35,16 +25,11 @@ impl NotificationManager {
Self { Self {
rx_toast, rx_toast,
tx_toast, tx_toast,
dbus_data: None,
running: Arc::new(AtomicBool::new(true)), running: Arc::new(AtomicBool::new(true)),
} }
} }
pub fn submit_pending(&self, app: &mut AppState) { 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 { if app.session.config.notifications_enabled {
self.rx_toast.try_iter().for_each(|toast| { self.rx_toast.try_iter().for_each(|toast| {
toast.submit(app); toast.submit(app);
@@ -55,78 +40,47 @@ impl NotificationManager {
} }
} }
pub fn run_dbus(&mut self) { pub fn run_dbus(&mut self, dbus: &mut DbusConnector) {
let Ok(conn) = Connection::new_session().context( let rule = MatchRule::new_method_call()
"Failed to connect to dbus. Desktop notifications and keymap changes will not work.", .with_member("Notify")
) else { .with_interface("org.freedesktop.Notifications")
return; .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(); let sender = self.tx_toast.clone();
conn.start_receive( if let Ok(_) = dbus
rule, .become_monitor(
rule.clone(),
Box::new(move |msg, _| { Box::new(move |msg, _| {
if let Ok(toast) = parse_dbus(&msg) { if let Ok(toast) = parse_dbus(&msg) {
match sender.try_send(toast) { let _ = sender
Ok(()) => {} .try_send(toast)
Err(e) => { .inspect_err(|e| log::error!("Failed to send notification: {e:?}"));
log::error!("Failed to send notification: {e:?}");
}
}
} }
true true
}), }),
); )
log::info!("Listening to DBus notifications via BecomeMonitor."); .context("Could not register BecomeMonitor")
} else { .inspect_err(|e| log::warn!("{e:?}"))
let rule_with_eavesdrop = { {
let mut rule = rule.clone(); log::info!("Listening to D-Bus notifications via BecomeMonitor.");
rule.eavesdrop = true; return;
rule }
};
let sender2 = self.tx_toast.clone(); let sender = self.tx_toast.clone();
let result = conn.add_match(rule_with_eavesdrop, move |(): (), _, msg| { let _ = dbus
.add_match(
rule.with_eavesdrop(),
Box::new(move |(), _, msg| {
if let Ok(toast) = parse_dbus(msg) { if let Ok(toast) = parse_dbus(msg) {
match sender2.try_send(toast) { let _ = sender
Ok(()) => {} .try_send(toast)
Err(e) => { .inspect_err(|e| log::error!("Failed to send notification: {e:?}"));
log::error!("Failed to send notification: {e:?}");
}
}
} }
true true
}); }),
)
match result { .context("Failed to register D-Bus notifications. Desktop notifications won't work.")
Ok(_) => { .inspect_err(|e| log::warn!("{e:?}"));
log::info!("Listening to DBus notifications via eavesdrop.");
}
Err(_) => {
log::error!("Failed to add DBus match. Desktop notifications will not work.",);
}
}
}
self.dbus_data = Some(conn);
} }
pub fn run_udp(&mut self) { 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<Self> {
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<u32> {
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<Toast> { fn parse_dbus(msg: &dbus::Message) -> anyhow::Result<Toast> {
let mut args = msg.iter_init(); let mut args = msg.iter_init();
let app_name: String = args.read()?; let app_name: String = args.read()?;