Inline some clones to make code more readable, add config, implement dimming -> locking -> sleeping into idle daemon, fix waiting on network thread, implement run menu with application launching and math evaluation,

This commit is contained in:
jmb05
2026-11-01 16:21:46 +01:00
parent 80b5cafb8c
commit a8fe99b345
14 changed files with 975 additions and 484 deletions

View File

@@ -1,12 +1,13 @@
use crate::backend::{BackendThread, Registrator};
use crate::config::Config;
use crate::{APP_NAME, LOGGER};
use crossbeam_channel::{Receiver, Sender};
use jmb_logger_rs::*;
use nmrs::raw::zbus::interface;
use std::collections::HashMap;
use std::error::Error;
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::sleep;
use wayland_client::globals::registry_queue_init;
@@ -17,12 +18,11 @@ use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::{
Event, ExtIdleNotificationV1,
};
use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1;
use zbus::conn::Builder;
use zbus::interface;
use zbus::message::Header;
const SERVICE: &str = "org.freedesktop.ScreenSaver";
const OBJECT_PATH: &str = "/org/freedesktop/ScreenSaver";
const LEGACY_OBJECT_PATH: &str = "/ScreenSaver";
struct WlState {
idle: Option<bool>,
@@ -53,14 +53,8 @@ impl Dispatch<ExtIdleNotificationV1, ()> for WlState {
_: &QueueHandle<Self>,
) {
match event {
Event::Idled => {
println!("Idled: {:?}", Instant::now());
state.idle = Some(true);
}
Event::Resumed => {
println!("Resumed: {:?}", Instant::now());
state.idle = Some(false);
}
Event::Idled => state.idle = Some(true),
Event::Resumed => state.idle = Some(false),
_ => {}
}
if let Some(idle) = state.idle {
@@ -85,15 +79,15 @@ impl Dispatch<ExtIdleNotifierV1, (), WlState> for WlState {
struct Inhibitor {
owner: String,
application: String,
_reason: String,
reason: String,
}
#[derive(Debug, Clone)]
pub enum IdleState {
Init(Sender<IdleMessage>),
Dimmed,
Inhibited,
Caffeine,
Inhibited(bool),
Caffeine(bool),
Active,
}
@@ -128,73 +122,92 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
}
fn init(registration_rx: Receiver<Sender<IdleState>>) {
let config = Config::load();
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
let tx_clone = tx.clone();
let instance = Arc::new(Mutex::new(Self {
subscribers: Vec::new(),
}));
let instance_clone = instance.clone();
std::thread::spawn(move || {
while let Ok(sender) = registration_rx.recv() {
if sender.send(IdleState::Init(tx_clone.clone())).is_ok() {
trace!(&LOGGER, "Added IdleDaemon Subscriber");
instance_clone
.lock()
.expect("Couldn't get lock")
.subscribers
.push(sender);
std::thread::spawn({
let instance = instance.clone();
let tx = tx.clone();
move || {
while let Ok(sender) = registration_rx.recv() {
if sender.send(IdleState::Init(tx.clone())).is_ok() {
trace!(&LOGGER, "Added IdleDaemon Subscriber");
instance
.lock()
.expect("Couldn't get lock")
.subscribers
.push(sender);
}
}
}
});
let (wl_tx, wl_rx) = watch::channel(false);
let (dbus_tx, dbus_rx) = watch::channel((false, false));
std::thread::spawn(move || {
if let Err(err) = Self::start_idle_notifier(wl_tx) {
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
let (dbus_tx, dbus_rx) = watch::channel(false);
std::thread::spawn({
let config = config.clone();
move || {
if let Err(err) = Self::start_idle_notifier(wl_tx, &config) {
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
}
}
});
std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let mut idle_inhibitor = None;
while let Ok(msg) = rx.recv() {
match msg {
IdleMessage::Inhibit => {
if idle_inhibitor.is_none()
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
{
idle_inhibitor = Some(ii);
std::thread::spawn({
let instance = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let mut idle_inhibitor = None;
while let Ok(msg) = rx.recv() {
match msg {
IdleMessage::Inhibit => {
if idle_inhibitor.is_none()
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
{
instance.lock().unwrap().publish(IdleState::Caffeine(true));
idle_inhibitor = Some(ii);
}
}
}
IdleMessage::Release => {
if let Some(ii) = idle_inhibitor.take()
&& let Err(e) = ii.uninhibit_idle().await
{
warn!(&LOGGER, "Error uninhibiting {e}");
IdleMessage::Release => {
if let Some(ii) = idle_inhibitor.take() {
match ii.uninhibit_idle().await {
Ok(_) => {
instance
.lock()
.unwrap()
.publish(IdleState::Caffeine(false));
}
Err(e) => warn!(&LOGGER, "Error uninhibiting {e}"),
}
}
}
}
}
}
});
});
}
});
std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
std::thread::spawn({
let instance_clone = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let res = IdleDaemon::start_dbus(dbus_tx).await;
match res {
Ok((_, _)) => {
Self::run_idle_timer(instance, wl_rx, dbus_rx).await;
runtime.block_on(async move {
let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await;
match res {
Ok((_, _)) => {
Self::run_idle_timer(instance, wl_rx, dbus_rx, &config).await;
}
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
}
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
}
});
});
}
});
}
}
@@ -215,20 +228,21 @@ impl IdleStage {
}
}
fn timeout(self) -> Duration {
fn timeout(self, cfg: &Config) -> Duration {
match self {
Self::Dim => Duration::from_secs(20),
Self::TurnOff => Duration::from_secs(20),
Self::Sleep => Duration::from_secs(20),
Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()),
Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()),
Self::Sleep => Duration::from_secs(cfg.sleep_timeout_delta()),
}
}
}
impl IdleDaemon {
async fn run_idle_timer(
_instance: Arc<Mutex<Self>>,
instance: Arc<Mutex<Self>>,
mut wl_rx: watch::Receiver<bool>,
mut dbus_rx: watch::Receiver<(bool, bool)>,
mut dbus_rx: watch::Receiver<bool>,
config: &Config,
) {
loop {
if let Err(e) = wl_rx.changed().await {
@@ -237,22 +251,26 @@ impl IdleDaemon {
}
let wl_idle = *wl_rx.borrow_and_update();
let dbus_state = *dbus_rx.borrow_and_update();
println!("Wayland idle state changed {wl_idle} (DBus state: {dbus_state:?})");
if wl_idle && !dbus_state.0 {
println!("Going into idle cycle");
if wl_idle && !dbus_state {
let mut stage = IdleStage::Dim;
loop {
tokio::select! {
_ = sleep(stage.timeout()) => {
_ = sleep(stage.timeout(config)) => {
match stage {
IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed),
IdleStage::TurnOff => _ = Command::new("hyprlock").spawn(),
IdleStage::Sleep => _ = Command::new("systemctl").arg("sleep").spawn(),
}
stage = stage.next();
println!("Reached next stage {stage:?}");
}
res = wl_rx.changed() => {
if let Err(e) = res {
warn!(&LOGGER, "Error getting Wayland idle state {e}");
return;
}
println!("WL state change");
if !*wl_rx.borrow_and_update() {
instance.lock().unwrap().publish(IdleState::Active);
}
break;
}
res = dbus_rx.changed() => {
@@ -260,7 +278,6 @@ impl IdleDaemon {
warn!(&LOGGER, "Error getting D-Bus idle state {e}");
return;
}
println!("D-Bus state change");
break;
}
}
@@ -269,7 +286,7 @@ impl IdleDaemon {
}
}
fn start_idle_notifier(tx: watch::Sender<bool>) -> Result<(), Box<dyn Error>> {
fn start_idle_notifier(tx: watch::Sender<bool>, config: &Config) -> Result<(), Box<dyn Error>> {
let conn = Connection::connect_to_env()?;
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
let queue_handle = event_queue.handle();
@@ -280,7 +297,12 @@ impl IdleDaemon {
let mut state = WlState { idle: None, tx };
let _notification = notifier.get_idle_notification(10_000, &seat, &queue_handle, ());
let _notification = notifier.get_idle_notification(
(config.idle_timeout() * 1000) as u32,
&seat,
&queue_handle,
(),
);
loop {
event_queue.blocking_dispatch(&mut state)?;
@@ -288,16 +310,18 @@ impl IdleDaemon {
}
async fn start_dbus(
tx: watch::Sender<(bool, bool)>,
tx: watch::Sender<bool>,
daemon: Arc<Mutex<IdleDaemon>>,
) -> zbus::Result<(ScreenSaver, zbus::connection::Connection)> {
let service = ScreenSaver::new(tx);
let service = ScreenSaver::new(tx, daemon);
let connection = Builder::session()?
.name(SERVICE)?
.serve_at(OBJECT_PATH, service.clone())?
.serve_at(LEGACY_OBJECT_PATH, service.clone())?
.build()
let connection = zbus::Connection::session().await?;
connection
.object_server()
.at(OBJECT_PATH, service.clone())
.await?;
connection.request_name(SERVICE).await?;
info!(&LOGGER, "Providing {SERVICE}");
@@ -347,22 +371,24 @@ impl IdleInhibitor {
#[derive(Clone)]
struct ScreenSaver {
inner: Arc<Mutex<Inner>>,
daemon: Arc<Mutex<IdleDaemon>>,
}
struct Inner {
next_cookie: u32,
inhibitors: HashMap<u32, Inhibitor>,
tx: watch::Sender<(bool, bool)>,
tx: watch::Sender<bool>,
}
impl ScreenSaver {
fn new(tx: watch::Sender<(bool, bool)>) -> Self {
fn new(tx: watch::Sender<bool>, daemon: Arc<Mutex<IdleDaemon>>) -> Self {
Self {
inner: Arc::new(Mutex::new(Inner {
next_cookie: 1,
inhibitors: HashMap::new(),
tx,
})),
daemon,
}
}
}
@@ -399,21 +425,26 @@ impl ScreenSaver {
}
};
let caffeine_flag = application_name.eq(APP_NAME);
debug!(&LOGGER, "Inhibitor added: {application_name} {cookie}");
debug!(
&LOGGER,
"Inhibitor added - Name: '{application_name}' Reason: '{reason_for_inhibit}' Value: {cookie}"
);
inner.inhibitors.insert(
cookie,
Inhibitor {
owner,
application: application_name,
_reason: reason_for_inhibit,
reason: reason_for_inhibit,
},
);
self.daemon
.lock()
.unwrap()
.publish(IdleState::Inhibited(true));
inner.tx.send_modify(|state| {
state.0 = !inner.inhibitors.is_empty();
state.1 = caffeine_flag;
*state = !inner.inhibitors.is_empty();
});
Ok(cookie)
}
@@ -432,25 +463,25 @@ impl ScreenSaver {
if let Some(removed) = inner.inhibitors.remove(&cookie) {
debug!(
&LOGGER,
"Inhibitor removed: {} {cookie}", removed.application
"Inhibitor removed - Name: '{}' Reason: '{}' Value: {cookie}",
removed.application,
removed.reason
);
}
let mut caffeine_flag = false;
for inhibitor in inner.inhibitors.values() {
if inhibitor.application.eq(APP_NAME) {
caffeine_flag = true;
break;
}
}
self.daemon
.lock()
.unwrap()
.publish(IdleState::Inhibited(!inner.inhibitors.is_empty()));
inner.tx.send_modify(|state| {
state.0 = !inner.inhibitors.is_empty();
state.1 = caffeine_flag;
*state = !inner.inhibitors.is_empty();
});
Ok(())
}
Some(_) => Err(zbus::fdo::Error::AccessDenied(
"Cookie belongs to another D-Bus client".into(),
)),
Some(_) => Err(zbus::fdo::Error::AccessDenied(format!(
"Cookie {cookie} belongs to another D-Bus client"
))),
None => Err(zbus::fdo::Error::Failed("Unknown inhibition cookie".into())),
}
}

View File

@@ -161,6 +161,26 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
}
};
std::thread::spawn({
let nm = nm.clone();
let mut instance = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
while let Ok(tx) = registration_rx.recv() {
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
instance.subscribers.push(tx);
let _ = instance.send_all_networks(&nm).await;
let _ = instance.send_active_connection(&nm).await;
}
}
});
}});
let mut events = match nm.network_events().await {
Ok(events) => events,
Err(err) => {
@@ -169,7 +189,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
}
};
let mut debouncer = EventDebouncer {
duration: Duration::from_millis(200),
deadline: None,
@@ -177,13 +196,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
};
debouncer.sleep.as_mut().reset(Instant::now() + Duration::from_secs(86400));
loop {
while let Ok(tx) = registration_rx.try_recv() {
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
instance.subscribers.push(tx);
let _ = instance.send_all_networks(&nm).await;
let _ = instance.send_active_connection(&nm).await;
}
}
tokio::select! {
command = command_rx.recv() => {
let Some(command) = command else {

View File

@@ -1,10 +1,10 @@
use crate::LOGGER;
use crate::backend::{BackendThread, SimpleBackendThread};
use crossbeam_channel::{Receiver, Sender};
use hyprland::data::{Client, Monitor, Workspaces};
use hyprland::data::{Client, CursorPosition, Monitor, Monitors, Workspaces};
use hyprland::event_listener::EventListener;
use hyprland::prelude::*;
use hyprland::shared::{MonitorId, WorkspaceId, WorkspaceType};
use hyprland::shared::{MonitorId, WorkspaceId};
use jmb_logger_rs::*;
use std::io;
use std::process::{Command, Output};
@@ -24,7 +24,6 @@ pub struct HyprlandComms {
}
impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
fn subscribers(&self) -> &Vec<Sender<WindowManagerEvent>> {
&self.subscribers
}
@@ -38,9 +37,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_changed_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name
&& let Ok(id) = name.parse::<i32>()
{
if let Ok(id) = workspace.name.parse::<i32>() {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
@@ -50,26 +47,24 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_added_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name {
//unpacks the name of the workspace
if let Ok(wss) = Workspaces::get() {
//gets all workspaces
if let Some(ws) = wss
.to_vec()
.iter()
.find(|ws| ws.id().unwrap() == workspace.id)
{
//find the current workspace Object from its id
if let Ok(id) = name.parse::<i32>() {
// parse the id as an int
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(
id,
ws.monitor_id.unwrap(),
));
}
//unpacks the name of the workspace
if let Ok(wss) = Workspaces::get() {
//gets all workspaces
if let Some(ws) = wss
.to_vec()
.iter()
.find(|ws| ws.id().unwrap() == workspace.id().unwrap())
{
//find the current workspace Object from its id
if let Ok(id) = workspace.name.parse::<i32>() {
// parse the id as an int
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(
id,
ws.monitor_id.unwrap(),
));
}
}
}
@@ -77,9 +72,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_deleted_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name
&& let Ok(id) = name.parse::<i32>()
{
if let Ok(id) = workspace.name.parse::<i32>() {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
@@ -92,7 +85,9 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceChanged(evt.workspace_id));
.publish(WindowManagerEvent::WorkspaceChanged(
evt.workspace.id().unwrap(),
));
});
let inst_clone = instance.clone();
@@ -106,15 +101,22 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_moved_handler(move |event| {
if let Some(monitor_id) = Self::monitor_id_from_workspace_id(event.id) {
if let Some(monitor_id) =
Self::monitor_id_from_workspace_id(event.workspace.id().unwrap())
{
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceRemoved(event.id));
.publish(WindowManagerEvent::WorkspaceRemoved(
event.workspace.id().unwrap(),
));
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(event.id, monitor_id));
.publish(WindowManagerEvent::WorkspaceAdded(
event.workspace.id().unwrap(),
monitor_id,
));
}
});
@@ -160,7 +162,7 @@ impl HyprlandComms {
None
}
pub fn _current_monitor() -> MonitorId {
pub fn current_monitor() -> MonitorId {
if let Ok(monitor) = Monitor::get_active() {
return monitor.id;
}
@@ -197,4 +199,22 @@ impl HyprlandComms {
.arg(format!("hyprctl dispatch \"{dispatcher}\""))
.output()
}
pub fn cursor_monitor() -> Option<MonitorId> {
if let Ok(pos) = CursorPosition::get()
&& let Ok(monitors) = Monitors::get()
{
for monitor in monitors {
if pos.x > monitor.x as i64
&& pos.x < (monitor.x as i64 + monitor.width as i64)
&& pos.y > monitor.y as i64
&& pos.y < (monitor.y as i64 + monitor.height as i64)
{
return Some(monitor.id);
}
}
}
None
}
}