Skip to content

feat: turn off screens when idle - #133

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

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

Conversation

@fspy

@fspy fspy commented Oct 9, 2025

Copy link
Copy Markdown

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.

@max-ishere max-ishere left a comment

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.

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 thread regreet.sample.toml
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"

Comment thread src/idle.rs
}
}

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 thread src/idle.rs
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 thread src/idle.rs
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 thread src/idle.rs
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.

Comment thread src/idle.rs
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?

Comment thread src/idle.rs
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 thread src/gui/component.rs
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.

@rharish101

rharish101 commented May 7, 2026

Copy link
Copy Markdown
Owner

On one hand, this would be very convenient to do with ReGreet itself. However, adding more options other than a simple idle timeout would start piling code.

On the other, you can probably do this in a similar way you do it with your main Wayland compositor: use the idle session manager. For example, if you use ReGreet to boot into Hyprland, you've probably installed something like hypridle or stasis. You could set that up in the Wayland compositor that runs ReGreet. That way users can configure more than a simple timeout for the idle behaviour, and no changes are needed for ReGreet.

Since most people would just log in with ReGreet and not bother to keep it running and let it idle, I'm leaning more towards the latter. Would that work for you, or are there downsides beyond the extra setup?

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.

3 participants