Skip to content

feat: turn off screens when idle#133

Open
fspy wants to merge 1 commit intorharish101:mainfrom
fspy:feature/idle
Open

feat: turn off screens when idle#133
fspy wants to merge 1 commit intorharish101:mainfrom
fspy:feature/idle

Conversation

@fspy
Copy link
Copy Markdown

@fspy fspy commented Oct 9, 2025

this PR introduces a feature that turns off screens after an idle period, configured by idle_timeout.timeout_duration.

turns off screens after an idle period, configured by
idle_timeout.timeout_duration.
Copy link
Copy Markdown
Contributor

@max-ishere max-ishere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Felipe, thanks for contributing. Full disclosure, I am just a fellow maintainer, but I noticed quite a few issues ranging from mild ones to quite important.

Please don't take the fact that I left comments on nearly every line personally. The code works great, the feature is awesome and I do want it in the project, and I haven't noticed any bugs. However, there are some things I think can be improved.

Really, I am just here to keep the code quality up, and make the project better. Some of these are my personal opinions (mostly the parts about the config structure), so feel free to disagree.

To be clear, I am not a maintainer, so my opinions aren't as important.

Comment on lines +52 to +54
[idle_timeout]
# Time in seconds before turning off screens
timeout_duration = 30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of bikeshedding - I think that the top-level key should just be called [idle]. But it's not that important.

The thing that bothers me more is the unspecific timeout_duration key.

I think it would be nice for the greeter to optionally suspend the system if it is idle. It doesn't need to be done in this PR, but I think such a feature will be added eventually. Thus, the key that turns the screens off should be more specific to avoid confusion in the future. For user's convenience, I don't see any issues with aliasing it in serde it to allow "screen", "monitor", and "display" timeouts.

Additionally, it would be nice to use humantime-serde because ffs if I have to convert from minutes, or hours to seconds once again, I will go insane.

[idle]
# Alias: monitor_timeout, display_timeout
screen_timeout = "20min"

}
}

pub fn run_idle_loop(timeout_ms: u32) -> Result<(), Box<dyn std::error::Error>> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a std::time::Duration makes more sense than a u32 named *_ms. The same goes for State::timeout, it should be a Duration too.

Comment on lines +129 to +137
if let Some(power_manager) = self.output_power_manager.as_ref() {
self.outputs.iter().for_each(|(_, output)| {
let power_control = power_manager.get_output_power(output, qh, ());
power_control.set_mode(mode);
power_control.destroy();
});
} else {
tracing::warn!("No output power manager available to set display power mode");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid unnecessary nesting, this could be written as:

let Some(power_manager) = self.output_power_manager.as_ref() else {
    tracing::warn!("No output power manager available to set display power mode");
    
    return;
};

self.outputs.iter().for_each(|(_, output)| {
    let power_control = power_manager.get_output_power(output, qh, ());
    power_control.set_mode(mode);
    power_control.destroy();
});

But more importantly, since output_power_manager is the core of this entire thing, wouldn't it make more sense to remove the Option<> and simply fail to start the idle loop if it is not found? I am not sure if it is possible to use 2 separate State types in the trait generics.

Comment on lines +141 to +146
if let (Some(idle_notifier), Some(seat)) = (&self.idle_notifier, &self.seat) {
self.idle_notification =
Some(idle_notifier.get_idle_notification(self.timeout_ms, seat, qh, ()));
} else {
tracing::warn!("Cannot create idle notification: missing idle notifier or seat");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto regarding let else, and the fact that it may not have to be an option.

Additionally, this function should probably return a Result<(), Err> or at least a bool so that the caller can exit on line 195:

if state.create_idle_notification(&qh).is_err() {
    tracing::warn!("Not starting idle monitor: Failed to create idle notification");
    
    return;
}

loop {
    event_queue.blocking_dispatch(&mut state)?;
}

The Err could be an enum like { NoNotifier, NoSeat }, in which case you could write it as:

#[derive(Error)]
enum CreateNotificationError {
    #[error("No idle notifier registered")]
    NoNotifier,
    
    ...
}

if let Err(err) = ... {
    warn!("Not starting idle monitor: {err}");
}

Comment on lines +155 to +157
fn check_protocols_ready(&mut self) {
self.protocols_ready = self.has_required_protocols();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.protocols_ready is never used.

state.is_idle = false;
state.set_display_power_mode(Mode::On, qh);
}
notification.destroy();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon checking this in Cosmic, it does not seem necessary to destroy the notification every time a Resumed event is sent - the screen continues to turn off.

Is there some documentation page that mandates this?

idle_notifier: Option<ext_idle_notifier_v1::ExtIdleNotifierV1>,
timeout_ms: u32,
idle_notification: Option<ext_idle_notification_v1::ExtIdleNotificationV1>,
is_idle: bool,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand the point of this bool. It does not seem like it would change anything in how the monitors are turned off. Is there some reason for this? If so, a doccomment would be helpful.

Comment on lines +343 to +347
std::thread::spawn(move || {
if let Err(e) = crate::idle::run_idle_loop(timeout_ms) {
tracing::error!("Idle monitoring failed: {}", e);
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use tokio::spawn_blocking instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants