Refactor to use common watchers loop

This commit is contained in:
Demmie 2023-05-11 18:42:18 -04:00
parent cdc05e0d5f
commit 79f8e53850
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
8 changed files with 199 additions and 197 deletions

View File

@ -13,54 +13,113 @@ mod x11_connection;
mod x11_screensaver_idle;
mod x11_window;
use crate::report_client::ReportClient;
use crate::{config::Config, report_client::ReportClient};
use std::{
sync::{atomic::AtomicBool, Arc},
fmt::Display,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, JoinHandle},
time::Duration,
};
pub enum WatcherType {
Idle,
ActiveWindow,
}
impl WatcherType {
fn sleep_time(&self, config: &Config) -> Duration {
match self {
WatcherType::Idle => config.poll_time_idle,
WatcherType::ActiveWindow => config.poll_time_idle,
}
}
}
impl Display for WatcherType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WatcherType::Idle => write!(f, "idle"),
WatcherType::ActiveWindow => write!(f, "active window"),
}
}
}
pub trait Watcher: Send {
fn new() -> anyhow::Result<Self>
fn new(client: &Arc<ReportClient>) -> anyhow::Result<Self>
where
Self: Sized;
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>);
fn run(
&mut self,
watcher_type: &WatcherType,
client: &Arc<ReportClient>,
is_stopped: Arc<AtomicBool>,
) {
info!("Starting {watcher_type} watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down {watcher_type}");
break;
}
self.run_iteration(client);
thread::sleep(watcher_type.sleep_time(&client.config));
}
}
fn run_iteration(&mut self, client: &Arc<ReportClient>);
}
type BoxedWatcher = Box<dyn Watcher>;
type WatcherConstructor = (&'static str, fn() -> anyhow::Result<BoxedWatcher>);
type WatcherConstructors = [WatcherConstructor];
type WatcherConstructors = [(
&'static str,
WatcherType,
fn(&Arc<ReportClient>) -> anyhow::Result<BoxedWatcher>,
)];
pub trait ConstructorFilter {
fn filter_first_supported(&self) -> Option<BoxedWatcher>;
fn filter_first_supported(
&self,
client: &Arc<ReportClient>,
) -> Option<(&WatcherType, BoxedWatcher)>;
fn run_first_supported(
&self,
&'static self,
client: &Arc<ReportClient>,
is_stopped: Arc<AtomicBool>,
) -> Option<JoinHandle<()>>;
}
impl ConstructorFilter for WatcherConstructors {
fn filter_first_supported(&self) -> Option<BoxedWatcher> {
self.iter().find_map(|(name, watcher)| match watcher() {
Ok(watcher) => Some(watcher),
Err(e) => {
debug!("{name} cannot run: {e}");
None
}
})
fn filter_first_supported(
&self,
client: &Arc<ReportClient>,
) -> Option<(&WatcherType, BoxedWatcher)> {
self.iter()
.find_map(|(name, watcher_type, watcher)| match watcher(client) {
Ok(watcher) => {
info!("Selected {name} as {watcher_type} watcher");
Some((watcher_type, watcher))
}
Err(e) => {
debug!("{name} cannot run: {e}");
None
}
})
}
fn run_first_supported(
&self,
&'static self,
client: &Arc<ReportClient>,
is_stopped: Arc<AtomicBool>,
) -> Option<JoinHandle<()>> {
let idle_watcher = self.filter_first_supported();
if let Some(mut watcher) = idle_watcher {
let idle_watcher = self.filter_first_supported(client);
if let Some((watcher_type, mut watcher)) = idle_watcher {
let thread_client = Arc::clone(client);
let idle_handler = thread::spawn(move || watcher.watch(&thread_client, is_stopped));
let idle_handler =
thread::spawn(move || watcher.run(watcher_type, &thread_client, is_stopped));
Some(idle_handler)
} else {
None
@ -69,26 +128,29 @@ impl ConstructorFilter for WatcherConstructors {
}
macro_rules! watcher {
($watcher_struct:ty) => {
(stringify!($watcher_struct), || {
Ok(Box::new(<$watcher_struct>::new()?))
($watcher_struct:ty, $watcher_type:expr) => {
(stringify!($watcher_struct), $watcher_type, |client| {
Ok(Box::new(<$watcher_struct>::new(client)?))
})
};
}
pub const IDLE: &WatcherConstructors = &[
watcher!(wl_kwin_idle::IdleWatcher),
watcher!(x11_screensaver_idle::IdleWatcher),
watcher!(wl_kwin_idle::IdleWatcher, WatcherType::Idle),
watcher!(x11_screensaver_idle::IdleWatcher, WatcherType::Idle),
#[cfg(feature = "gnome")]
watcher!(gnome_idle::IdleWatcher),
watcher!(gnome_idle::IdleWatcher, WatcherType::Idle),
];
pub const ACTIVE_WINDOW: &WatcherConstructors = &[
watcher!(wl_foreign_toplevel::WindowWatcher),
watcher!(
wl_foreign_toplevel::WindowWatcher,
WatcherType::ActiveWindow
),
// XWayland gives _NET_WM_NAME on some windows in KDE, but not on others
#[cfg(feature = "kwin_window")]
watcher!(kwin_window::WindowWatcher),
watcher!(x11_window::WindowWatcher),
watcher!(kwin_window::WindowWatcher, WatcherType::ActiveWindow),
watcher!(x11_window::WindowWatcher, WatcherType::ActiveWindow),
#[cfg(feature = "gnome")]
watcher!(gnome_window::WindowWatcher),
watcher!(gnome_window::WindowWatcher, WatcherType::ActiveWindow),
];

View File

@ -1,13 +1,12 @@
use super::{idle, Watcher};
use crate::report_client::ReportClient;
use anyhow::Context;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use zbus::blocking::Connection;
pub struct IdleWatcher {
dbus_connection: Connection,
is_idle: bool,
}
impl idle::SinceLastInput for IdleWatcher {
@ -27,30 +26,22 @@ impl idle::SinceLastInput for IdleWatcher {
}
impl Watcher for IdleWatcher {
fn new() -> anyhow::Result<Self> {
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut watcher = Self {
dbus_connection: Connection::session()?,
is_idle: false,
};
idle::SinceLastInput::seconds_since_input(&mut watcher)?;
Ok(watcher)
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
let mut is_idle = false;
info!("Starting idle watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
match idle::ping_since_last_input(self, self.is_idle, client) {
Ok(is_idle_again) => {
self.is_idle = is_idle_again;
}
match idle::ping_since_last_input(self, is_idle, client) {
Ok(is_idle_again) => {
is_idle = is_idle_again;
}
Err(e) => error!("Error on idle iteration: {e}"),
};
thread::sleep(client.config.poll_time_idle);
}
Err(e) => error!("Error on idle iteration: {e}"),
};
}
}

View File

@ -1,9 +1,7 @@
use crate::report_client::ReportClient;
use anyhow::Context;
use serde::Deserialize;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use zbus::blocking::Connection;
use super::Watcher;
@ -77,7 +75,7 @@ impl WindowWatcher {
}
impl Watcher for WindowWatcher {
fn new() -> anyhow::Result<Self> {
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let watcher = Self {
dbus_connection: Connection::session()?,
last_app_id: String::new(),
@ -87,17 +85,9 @@ impl Watcher for WindowWatcher {
Ok(watcher)
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
info!("Starting active window watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
}
if let Err(error) = self.send_active_window(client) {
error!("Error on active window: {error}");
}
thread::sleep(client.config.poll_time_window);
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
if let Err(error) = self.send_active_window(client) {
error!("Error on active window: {error}");
}
}
}

View File

@ -8,7 +8,6 @@ use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::env::temp_dir;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc::channel, Arc, Mutex};
use std::thread;
use zbus::blocking::{Connection, ConnectionBuilder};
@ -151,22 +150,19 @@ impl ActiveWindowInterface {
}
pub struct WindowWatcher {
kwin_script: KWinScript,
active_window: Arc<Mutex<ActiveWindow>>,
// Prolong its lifetime
_kwin_script: KWinScript,
}
impl Watcher for WindowWatcher {
fn new() -> anyhow::Result<Self> {
let kwin_script = KWinScript::new(Connection::session()?);
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut kwin_script = KWinScript::new(Connection::session()?);
if kwin_script.is_loaded()? {
debug!("KWin script is already loaded, unloading");
kwin_script.unload()?;
}
Ok(Self { kwin_script })
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
self.kwin_script.load().unwrap();
kwin_script.load().unwrap();
let active_window = Arc::new(Mutex::new(ActiveWindow {
caption: String::new(),
@ -199,16 +195,15 @@ impl Watcher for WindowWatcher {
panic!("Failed to run a DBus interface: {error}");
}
info!("Starting active window watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
}
if let Err(error) = send_active_window(client, &active_window) {
error!("Error on sending active window: {error}");
}
thread::sleep(client.config.poll_time_window);
Ok(Self {
active_window,
_kwin_script: kwin_script,
})
}
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
if let Err(error) = send_active_window(client, &self.active_window) {
error!("Error on sending active window: {error}");
}
}
}

View File

@ -9,8 +9,7 @@ use super::{wl_connection::subscribe_state, Watcher};
use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{sync::Arc, thread};
use std::sync::Arc;
use wayland_client::{
event_created_child, globals::GlobalListContents, protocol::wl_registry, Connection, Dispatch,
Proxy, QueueHandle,
@ -24,15 +23,13 @@ struct WindowData {
struct ToplevelState {
windows: HashMap<String, WindowData>,
current_window_id: Option<String>,
client: Arc<ReportClient>,
}
impl ToplevelState {
fn new(client: Arc<ReportClient>) -> Self {
fn new() -> Self {
Self {
windows: HashMap::new(),
current_window_id: None,
client,
}
}
}
@ -114,55 +111,59 @@ impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for ToplevelState {
}
}
impl ToplevelState {
fn send_active_window(&self) -> anyhow::Result<()> {
pub struct WindowWatcher {
connection: WlEventConnection<ToplevelState>,
toplevel_state: ToplevelState,
}
impl WindowWatcher {
fn send_active_window(&self, client: &Arc<ReportClient>) -> anyhow::Result<()> {
let active_window_id = self
.toplevel_state
.current_window_id
.as_ref()
.ok_or(anyhow!("Current window is unknown"))?;
let active_window = self.windows.get(active_window_id).ok_or(anyhow!(
"Current window is not found by ID {active_window_id}"
))?;
let active_window = self
.toplevel_state
.windows
.get(active_window_id)
.ok_or(anyhow!(
"Current window is not found by ID {active_window_id}"
))?;
self.client
client
.send_active_window(&active_window.app_id, &active_window.title)
.with_context(|| "Failed to send heartbeat for active window")
}
}
pub struct WindowWatcher {
connection: WlEventConnection<ToplevelState>,
}
impl Watcher for WindowWatcher {
fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?;
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?;
connection.get_foreign_toplevel_manager()?;
Ok(Self { connection })
}
let mut toplevel_state = ToplevelState::new();
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
let mut toplevel_state = ToplevelState::new(Arc::clone(client));
self.connection
connection
.event_queue
.roundtrip(&mut toplevel_state)
.unwrap();
info!("Starting wlr foreign toplevel watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
}
if let Err(e) = self.connection.event_queue.roundtrip(&mut toplevel_state) {
error!("Event queue is not processed: {e}");
} else if let Err(e) = toplevel_state.send_active_window() {
error!("Error on iteration: {e}");
}
Ok(Self {
connection,
toplevel_state,
})
}
thread::sleep(client.config.poll_time_window);
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
if let Err(e) = self
.connection
.event_queue
.roundtrip(&mut self.toplevel_state)
{
error!("Event queue is not processed: {e}");
} else if let Err(e) = self.send_active_window(client) {
error!("Error on iteration: {e}");
}
}
}

View File

@ -3,8 +3,7 @@ use super::wl_connection::{subscribe_state, WlEventConnection};
use super::Watcher;
use crate::report_client::ReportClient;
use chrono::{DateTime, Duration, Utc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{sync::Arc, thread};
use std::sync::Arc;
use wayland_client::{
globals::GlobalListContents,
protocol::{wl_registry, wl_seat::WlSeat},
@ -20,7 +19,6 @@ struct IdleState {
last_input_time: DateTime<Utc>,
is_idle: bool,
is_changed: bool,
client: Arc<ReportClient>,
}
impl Drop for IdleState {
@ -31,13 +29,12 @@ impl Drop for IdleState {
}
impl IdleState {
fn new(idle_timeout: OrgKdeKwinIdleTimeout, client: Arc<ReportClient>) -> Self {
fn new(idle_timeout: OrgKdeKwinIdleTimeout) -> Self {
Self {
idle_timeout,
last_input_time: Utc::now(),
is_idle: false,
is_changed: false,
client,
}
}
@ -54,7 +51,7 @@ impl IdleState {
debug!("Resumed");
}
fn send_ping(&mut self) -> anyhow::Result<()> {
fn send_ping(&mut self, client: &Arc<ReportClient>) -> anyhow::Result<()> {
let now = Utc::now();
if !self.is_idle {
self.last_input_time = now;
@ -63,9 +60,8 @@ impl IdleState {
if self.is_changed {
let result = if self.is_idle {
debug!("Reporting as changed to idle");
self.client
.ping(false, self.last_input_time, Duration::zero())?;
self.client.ping(
client.ping(false, self.last_input_time, Duration::zero())?;
client.ping(
true,
self.last_input_time + Duration::milliseconds(1),
Duration::zero(),
@ -73,9 +69,8 @@ impl IdleState {
} else {
debug!("Reporting as no longer idle");
self.client
.ping(true, self.last_input_time, Duration::zero())?;
self.client.ping(
client.ping(true, self.last_input_time, Duration::zero())?;
client.ping(
false,
self.last_input_time + Duration::milliseconds(1),
Duration::zero(),
@ -85,12 +80,10 @@ impl IdleState {
result
} else if self.is_idle {
trace!("Reporting as idle");
self.client
.ping(true, self.last_input_time, now - self.last_input_time)
client.ping(true, self.last_input_time, now - self.last_input_time)
} else {
trace!("Reporting as not idle");
self.client
.ping(false, self.last_input_time, Duration::zero())
client.ping(false, self.last_input_time, Duration::zero())
}
}
}
@ -119,41 +112,30 @@ impl Dispatch<OrgKdeKwinIdleTimeout, ()> for IdleState {
pub struct IdleWatcher {
connection: WlEventConnection<IdleState>,
idle_state: IdleState,
}
impl Watcher for IdleWatcher {
fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<IdleState> = WlEventConnection::connect()?;
fn new(client: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut connection: WlEventConnection<IdleState> = WlEventConnection::connect()?;
connection.get_kwin_idle()?;
Ok(Self { connection })
let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000);
let mut idle_state =
IdleState::new(connection.get_kwin_idle_timeout(timeout.unwrap()).unwrap());
connection.event_queue.roundtrip(&mut idle_state).unwrap();
Ok(Self {
connection,
idle_state,
})
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000);
let mut idle_state = IdleState::new(
self.connection
.get_kwin_idle_timeout(timeout.unwrap())
.unwrap(),
Arc::clone(client),
);
self.connection
.event_queue
.roundtrip(&mut idle_state)
.unwrap();
info!("Starting idle watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
}
if let Err(e) = self.connection.event_queue.roundtrip(&mut idle_state) {
error!("Event queue is not processed: {e}");
} else if let Err(e) = idle_state.send_ping() {
error!("Error on idle iteration: {e}");
}
thread::sleep(client.config.poll_time_idle);
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
if let Err(e) = self.connection.event_queue.roundtrip(&mut self.idle_state) {
error!("Event queue is not processed: {e}");
} else if let Err(e) = self.idle_state.send_ping(client) {
error!("Error on idle iteration: {e}");
}
}
}

View File

@ -1,11 +1,10 @@
use super::{idle, x11_connection::X11Client, Watcher};
use crate::report_client::ReportClient;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
pub struct IdleWatcher {
client: X11Client,
is_idle: bool,
}
impl idle::SinceLastInput for IdleWatcher {
@ -15,31 +14,24 @@ impl idle::SinceLastInput for IdleWatcher {
}
impl Watcher for IdleWatcher {
fn new() -> anyhow::Result<Self> {
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut client = X11Client::new()?;
// Check if screensaver extension is supported
client.seconds_since_last_input()?;
Ok(IdleWatcher { client })
Ok(IdleWatcher {
client,
is_idle: false,
})
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
info!("Starting idle watcher");
let mut is_idle = false;
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
match idle::ping_since_last_input(self, self.is_idle, client) {
Ok(is_idle_again) => {
self.is_idle = is_idle_again;
}
match idle::ping_since_last_input(self, is_idle, client) {
Ok(is_idle_again) => {
is_idle = is_idle_again;
}
Err(e) => error!("Error on idle iteration: {e}"),
};
thread::sleep(client.config.poll_time_idle);
}
Err(e) => error!("Error on idle iteration: {e}"),
};
}
}

View File

@ -1,9 +1,7 @@
use super::{x11_connection::X11Client, Watcher};
use crate::report_client::ReportClient;
use anyhow::Context;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
pub struct WindowWatcher {
client: X11Client,
@ -31,7 +29,7 @@ impl WindowWatcher {
}
impl Watcher for WindowWatcher {
fn new() -> anyhow::Result<Self> {
fn new(_: &Arc<ReportClient>) -> anyhow::Result<Self> {
let mut client = X11Client::new()?;
client.active_window_data()?;
@ -42,18 +40,9 @@ impl Watcher for WindowWatcher {
})
}
fn watch(&mut self, client: &Arc<ReportClient>, is_stopped: Arc<AtomicBool>) {
info!("Starting active window watcher");
loop {
if is_stopped.load(Ordering::Relaxed) {
warn!("Received an exit signal, shutting down");
break;
}
if let Err(error) = self.send_active_window(client) {
error!("Error on sending active window: {error}");
}
thread::sleep(client.config.poll_time_window);
fn run_iteration(&mut self, client: &Arc<ReportClient>) {
if let Err(error) = self.send_active_window(client) {
error!("Error on sending active window: {error}");
}
}
}