Skip to content

Commit

Permalink
feat: idle handling (#72)
Browse files Browse the repository at this point in the history
* feat: introduce `IdleManager` & `IdleNotifier`

* refactor: encapsulate `event_queue` dispatching

* refactor: rename `update_timeout` to `reset_timeout`

* feat: enable idle and encapsulate some code

* refactor: apply cargo clippy suggestions

* refactor: parse `IdleThreshold` as human readable string; add idle notifier logs

* chore(docs): document the `idle_threshold` property

* refactor: change `idle_threshold` disable value to `"none"`

---------

Co-authored-by: Pavel <work.belyavsky.p.a@gmail.com>
  • Loading branch information
jsonmaf1a and JarKz authored Dec 17, 2024
1 parent ae350bb commit a450e86
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 42 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ filetype = { path = "../filetype" }

tempfile = "3.12.0"
wayland-client = "0.31.5"
wayland-protocols = { version = "0.32.3", features = ["client", "wayland-client"] }
wayland-protocols = { version = "0.32.3", features = ["client", "wayland-client", "staging"] }
wayland-protocols-wlr = { version = "0.3.3", features = ["client", "wayland-client"] }
indexmap = "2.4.0"
humantime = "2.1.0"
Expand Down
6 changes: 2 additions & 4 deletions crates/backend/src/banner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,10 @@ impl BannerRect {
&self.created_at
}

pub(crate) fn update_timeout(&mut self) {
pub(crate) fn reset_timeout(&mut self) {
self.created_at = time::Instant::now();

// INFO: because of every tracking pointer position, it emits very frequently and it's
// annoying. So moved to 'TRACE' level for specific situations.
trace!("Banner (id={}): Updated timeout", self.data.id);
trace!("Banner (id={}): Timeout reset", self.data.id);
}

pub(crate) fn update_data(&mut self, notification: Notification) {
Expand Down
37 changes: 37 additions & 0 deletions crates/backend/src/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use wayland_client::EventQueue;

pub trait Dispatcher {
type State;

fn get_event_queue_and_state(
&mut self,
) -> Option<(&mut EventQueue<Self::State>, &mut Self::State)>;

fn dispatch(&mut self) -> anyhow::Result<bool> {
let (event_queue, state) = match self.get_event_queue_and_state() {
Some(queue) => queue,
None => return Ok(false),
};

let dispatched_count = event_queue.dispatch_pending(state)?;

if dispatched_count > 0 {
return Ok(true);
}

event_queue.flush()?;
let Some(guard) = event_queue.prepare_read() else {
return Ok(false);
};
let Ok(count) = guard.read() else {
return Ok(false);
};

Ok(if count > 0 {
event_queue.dispatch_pending(state)?;
true
} else {
false
})
}
}
57 changes: 57 additions & 0 deletions crates/backend/src/idle_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::{
dispatcher::Dispatcher,
idle_notifier::{IdleNotifier, IdleState},
};
use config::Config;
use log::debug;
use wayland_client::{Connection, EventQueue};

pub struct IdleManager {
pub event_queue: Option<EventQueue<IdleNotifier>>,
pub idle_notifier: Option<IdleNotifier>,
}

impl Dispatcher for IdleManager {
type State = IdleNotifier;

fn get_event_queue_and_state(
&mut self,
) -> Option<(&mut EventQueue<Self::State>, &mut Self::State)> {
Some((self.event_queue.as_mut()?, self.idle_notifier.as_mut()?))
}
}

impl IdleManager {
pub(crate) fn init(config: &Config) -> anyhow::Result<Self> {
let connection = Connection::connect_to_env()?;
let event_queue = connection.new_event_queue();
let qhandle = event_queue.handle();
let display = connection.display();

display.get_registry(&qhandle, ());

let idle_notifier = IdleNotifier::init(config)?;

let idle_manager = Self {
event_queue: Some(event_queue),
idle_notifier: Some(idle_notifier),
};
debug!("Idle Manager: Initialized");

Ok(idle_manager)
}

pub(crate) fn get_idle_state(&self) -> Option<&IdleState> {
self.idle_notifier
.as_ref()
.and_then(IdleNotifier::get_idle_state)
}

pub(crate) fn blocking_dispatch(&mut self) -> anyhow::Result<()> {
if let Some(event_queue) = self.event_queue.as_mut() {
event_queue.blocking_dispatch(self.idle_notifier.as_mut().unwrap())?;
}

Ok(())
}
}
108 changes: 108 additions & 0 deletions crates/backend/src/idle_notifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use config::Config;
use log::debug;
use wayland_client::{
delegate_noop,
protocol::{
wl_registry,
wl_seat::{self, WlSeat},
},
Dispatch,
};
use wayland_protocols::ext::idle_notify::v1::client::{
ext_idle_notification_v1::{self, ExtIdleNotificationV1},
ext_idle_notifier_v1::ExtIdleNotifierV1,
};

pub struct IdleNotifier {
seat: Option<WlSeat>,
threshold: u32,
idle_state: Option<IdleState>,
}

pub enum IdleState {
Idled,
Resumed,
}

impl IdleNotifier {
pub(crate) fn init(config: &Config) -> anyhow::Result<Self> {
let threshold = config.general().idle_threshold.duration;
let idle_notifier = Self {
seat: None,
idle_state: None,
threshold,
};
debug!("Idle Notifier: Initialized");
Ok(idle_notifier)
}

pub(crate) fn get_idle_state(&self) -> Option<&IdleState> {
self.idle_state.as_ref()
}
}

impl Dispatch<wl_registry::WlRegistry, ()> for IdleNotifier {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
qhandle: &wayland_client::QueueHandle<Self>,
) {
if let wl_registry::Event::Global {
name,
interface,
version,
} = event
{
match interface.as_ref() {
"wl_seat" => {
state.seat =
Some(registry.bind::<wl_seat::WlSeat, _, _>(name, version, qhandle, ()));
debug!("Idle Notifier: Bound the wl_seat");
}
"ext_idle_notifier_v1" => {
if state.threshold == 0 {
return;
}

let idle_notifier =
registry.bind::<ExtIdleNotifierV1, _, _>(name, version, qhandle, ());
debug!("Idle Notifier: Bound the ext_idle_notifier_v1");

if let Some(seat) = state.seat.as_ref() {
idle_notifier.get_idle_notification(state.threshold, seat, qhandle, ());
}
}
_ => (),
}
}
}
}

impl Dispatch<ExtIdleNotificationV1, ()> for IdleNotifier {
fn event(
state: &mut Self,
_idle_notification: &ext_idle_notification_v1::ExtIdleNotificationV1,
event: <ext_idle_notification_v1::ExtIdleNotificationV1 as wayland_client::Proxy>::Event,
_data: &(),
_conn: &wayland_client::Connection,
_qhandle: &wayland_client::QueueHandle<Self>,
) {
match event {
ext_idle_notification_v1::Event::Idled => {
state.idle_state = Some(IdleState::Idled);
debug!("Idle Notifier: Idled");
}
ext_idle_notification_v1::Event::Resumed => {
state.idle_state = Some(IdleState::Resumed);
debug!("Idle Notifier: Resumed");
}
_ => (),
}
}
}

delegate_noop!(IdleNotifier: ignore WlSeat);
delegate_noop!(IdleNotifier: ignore ExtIdleNotifierV1);
3 changes: 3 additions & 0 deletions crates/backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use tokio::sync::mpsc::unbounded_channel;

mod banner;
mod cache;
mod dispatcher;
mod idle_manager;
mod idle_notifier;
mod internal_messages;
mod render;
mod scheduler;
Expand Down
24 changes: 24 additions & 0 deletions crates/backend/src/render.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::time::Duration;

use crate::dispatcher::Dispatcher;
use crate::idle_manager::IdleManager;
use crate::idle_notifier::IdleState;

use super::internal_messages::{RendererInternalChannel, ServerMessage};
use config::Config;
use log::{debug, info};
Expand All @@ -10,6 +14,7 @@ use super::window_manager::WindowManager;
pub(crate) struct Renderer {
config: Config,
window_manager: WindowManager,
idle_manager: IdleManager,
channel: RendererInternalChannel,
}

Expand All @@ -20,6 +25,7 @@ impl Renderer {
) -> anyhow::Result<Self> {
Ok(Self {
window_manager: WindowManager::init(&config)?,
idle_manager: IdleManager::init(&config)?,
channel: renderer_internal_channel,
config,
})
Expand All @@ -32,6 +38,8 @@ impl Renderer {

debug!("Renderer: Running");
loop {
self.handle_idle_state()?;

while let Ok(message) = self.channel.try_recv_from_server() {
match message {
ServerMessage::ShowNotification(notification) => {
Expand Down Expand Up @@ -67,6 +75,8 @@ impl Renderer {
self.window_manager.handle_actions(&self.config)?;
self.window_manager.dispatch()?;

self.idle_manager.dispatch()?;

{
match self.config.check_updates() {
FileState::Updated => {
Expand All @@ -92,6 +102,20 @@ impl Renderer {
}
}

fn handle_idle_state(&mut self) -> anyhow::Result<()> {
let mut state = self.idle_manager.get_idle_state();
while let Some(IdleState::Idled) = state {
self.idle_manager.blocking_dispatch()?;
state = self.idle_manager.get_idle_state();

if let Some(IdleState::Resumed) = state {
self.window_manager.reset_timeouts().ok();
}
}

Ok(())
}

fn update_config(&mut self) -> anyhow::Result<()> {
self.config.update();
self.window_manager.update_by_config(&self.config)?;
Expand Down
8 changes: 7 additions & 1 deletion crates/backend/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,20 @@ impl Window {

pub(super) fn handle_hover(&mut self, config: &Config) {
if let Some(index) = self.get_hovered_banner(config) {
self.banners[&index].update_timeout();
self.banners[&index].reset_timeout();

// INFO: because of every tracking pointer position, it emits very frequently and it's
// annoying. So moved to 'TRACE' level for specific situations.
trace!("Window: Updated timeout of hovered notification banner with id {index}");
}
}

pub(super) fn reset_timeouts(&mut self) {
self.banners
.values_mut()
.for_each(BannerRect::reset_timeout);
}

pub(super) fn handle_click(&mut self, config: &Config) -> Vec<RendererMessage> {
if let PrioritiedPressState::Unpressed = self.pointer_state.press_state {
return vec![];
Expand Down
41 changes: 15 additions & 26 deletions crates/backend/src/window_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use shared::cached_data::CachedData;
use wayland_client::{Connection, EventQueue, QueueHandle};

use crate::cache::CachedLayout;
use crate::dispatcher::Dispatcher;

use super::internal_messages::RendererMessage;
use config::Config;
Expand All @@ -27,6 +28,16 @@ pub(crate) struct WindowManager {
notification_queue: VecDeque<Notification>,
}

impl Dispatcher for WindowManager {
type State = Window;

fn get_event_queue_and_state(
&mut self,
) -> Option<(&mut EventQueue<Self::State>, &mut Self::State)> {
Some((self.event_queue.as_mut()?, self.window.as_mut()?))
}
}

impl WindowManager {
pub(crate) fn init(config: &Config) -> anyhow::Result<Self> {
let connection = Connection::connect_to_env()?;
Expand Down Expand Up @@ -211,34 +222,12 @@ impl WindowManager {
Ok(())
}

pub(crate) fn dispatch(&mut self) -> anyhow::Result<bool> {
if self.event_queue.is_none() {
return Ok(false);
}

let event_queue = unsafe { self.event_queue.as_mut().unwrap_unchecked() };
let window = unsafe { self.window.as_mut().unwrap_unchecked() };

let dispatched_count = event_queue.dispatch_pending(window)?;

if dispatched_count > 0 {
return Ok(true);
pub(crate) fn reset_timeouts(&mut self) -> anyhow::Result<()> {
if let Some(window) = self.window.as_mut() {
window.reset_timeouts();
}

event_queue.flush()?;
let Some(guard) = event_queue.prepare_read() else {
return Ok(false);
};
let Ok(count) = guard.read() else {
return Ok(false);
};

Ok(if count > 0 {
event_queue.dispatch_pending(window)?;
true
} else {
false
})
Ok(())
}

fn update_window(&mut self, config: &Config) -> anyhow::Result<()> {
Expand Down
Loading

0 comments on commit a450e86

Please sign in to comment.