Skip to content

Commit 2b76083

Browse files
authored
Merge pull request #351 from mahkoh/jorth/idle-grace-period
idle: add a grace period
2 parents 1ad3d11 + e8be15a commit 2b76083

File tree

29 files changed

+405
-79
lines changed

29 files changed

+405
-79
lines changed

jay-config/src/_private/client.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,10 @@ impl Client {
893893
self.send(&ClientMessage::SetIdle { timeout })
894894
}
895895

896+
pub fn set_idle_grace_period(&self, period: Duration) {
897+
self.send(&ClientMessage::SetIdleGracePeriod { period })
898+
}
899+
896900
pub fn set_explicit_sync_enabled(&self, enabled: bool) {
897901
self.send(&ClientMessage::SetExplicitSyncEnabled { enabled })
898902
}

jay-config/src/_private/ipc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ pub enum ClientMessage<'a> {
527527
SetXScalingMode {
528528
mode: XScalingMode,
529529
},
530+
SetIdleGracePeriod {
531+
period: Duration,
532+
},
530533
}
531534

532535
#[derive(Serialize, Deserialize, Debug)]

jay-config/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,24 @@ pub fn workspaces() -> Vec<Workspace> {
224224
/// Configures the idle timeout.
225225
///
226226
/// `None` disables the timeout.
227+
///
228+
/// The default is 10 minutes.
227229
pub fn set_idle(timeout: Option<Duration>) {
228230
get!().set_idle(timeout.unwrap_or_default())
229231
}
230232

233+
/// Configures the idle grace period.
234+
///
235+
/// The grace period starts after the idle timeout expires. During the grace period, the
236+
/// screen goes black but the displays are not yet disabled and the idle callback (set
237+
/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
238+
/// that the machine will soon go idle.
239+
///
240+
/// The default is 5 seconds.
241+
pub fn set_idle_grace_period(timeout: Duration) {
242+
get!().set_idle_grace_period(timeout)
243+
}
244+
231245
/// Enables or disables explicit sync.
232246
///
233247
/// Calling this after the compositor has started has no effect.

release-notes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
- Implement wl-fixes.
66
- Implement ei_touchscreen v2.
77
- Implement idle-notification v2.
8+
- Add an idle grace period. During the grace period, the screen goes black but is neither
9+
disabled nor locked. This is similar to how android handles going idle. The default is
10+
5 seconds.
811

912
# 1.7.0 (2024-10-25)
1013

src/backends/metal/present.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ impl MetalConnector {
504504
true,
505505
render_hw_cursor,
506506
node.has_fullscreen(),
507+
true,
507508
node.global.persistent.transform.get(),
508509
Some(&self.state.damage_visualizer),
509510
);

src/cli.rs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod xwayland;
1717
use {
1818
crate::{
1919
cli::{
20-
damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs,
20+
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs,
2121
xwayland::XwaylandArgs,
2222
},
2323
compositor::start_compositor,
@@ -101,38 +101,6 @@ pub struct RunPrivilegedArgs {
101101
pub program: Vec<String>,
102102
}
103103

104-
#[derive(Subcommand, Debug)]
105-
pub enum IdleCmd {
106-
/// Print the idle status.
107-
Status,
108-
/// Set the idle interval.
109-
Set(IdleSetArgs),
110-
}
111-
112-
impl Default for IdleCmd {
113-
fn default() -> Self {
114-
Self::Status
115-
}
116-
}
117-
118-
#[derive(Args, Debug)]
119-
pub struct IdleSetArgs {
120-
/// The interval of inactivity after which to disable the screens.
121-
///
122-
/// This can be either a number in minutes and seconds or the keyword `disabled` to
123-
/// disable the screensaver.
124-
///
125-
/// Minutes and seconds can be specified in any of the following formats:
126-
///
127-
/// * 1m
128-
/// * 1m5s
129-
/// * 1m 5s
130-
/// * 1min 5sec
131-
/// * 1 minute 5 seconds
132-
#[clap(verbatim_doc_comment, required = true)]
133-
pub interval: Vec<String>,
134-
}
135-
136104
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, PartialEq)]
137105
pub enum ScreenshotFormat {
138106
/// The PNG image format.

src/cli/idle.rs

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,60 @@
11
use {
22
crate::{
3-
cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
3+
cli::{duration::parse_duration, GlobalArgs, IdleArgs},
44
tools::tool_client::{with_tool_client, Handle, ToolClient},
5-
utils::stack::Stack,
5+
utils::{debug_fn::debug_fn, stack::Stack},
66
wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId},
77
},
8+
clap::{Args, Subcommand},
89
std::{cell::Cell, rc::Rc},
910
};
1011

12+
#[derive(Subcommand, Debug)]
13+
pub enum IdleCmd {
14+
/// Print the idle status.
15+
Status,
16+
/// Set the idle interval.
17+
Set(IdleSetArgs),
18+
/// Set the idle grace period.
19+
SetGracePeriod(IdleSetGracePeriodArgs),
20+
}
21+
22+
impl Default for IdleCmd {
23+
fn default() -> Self {
24+
Self::Status
25+
}
26+
}
27+
28+
#[derive(Args, Debug)]
29+
pub struct IdleSetArgs {
30+
/// The interval of inactivity after which to disable the screens.
31+
///
32+
/// This can be either a number in minutes and seconds or the keyword `disabled` to
33+
/// disable the screensaver.
34+
///
35+
/// Minutes and seconds can be specified in any of the following formats:
36+
///
37+
/// * 1m
38+
/// * 1m5s
39+
/// * 1m 5s
40+
/// * 1min 5sec
41+
/// * 1 minute 5 seconds
42+
#[clap(verbatim_doc_comment, required = true)]
43+
pub interval: Vec<String>,
44+
}
45+
46+
#[derive(Args, Debug)]
47+
pub struct IdleSetGracePeriodArgs {
48+
/// The grace period after the idle timeout expires.
49+
///
50+
/// During this period, after the idle timeout expires, the screen only goes black
51+
/// but is not yet disabled or locked.
52+
///
53+
/// This uses the same formatting options as the idle timeout itself.
54+
#[clap(verbatim_doc_comment, required = true)]
55+
pub period: Vec<String>,
56+
}
57+
1158
pub fn main(global: GlobalArgs, args: IdleArgs) {
1259
with_tool_client(global.log_level.into(), |tc| async move {
1360
let idle = Idle { tc: tc.clone() };
@@ -31,16 +78,21 @@ impl Idle {
3178
match args.command.unwrap_or_default() {
3279
IdleCmd::Status => self.status(idle).await,
3380
IdleCmd::Set(args) => self.set(idle, args).await,
81+
IdleCmd::SetGracePeriod(args) => self.set_grace_period(idle, args).await,
3482
}
3583
}
3684

3785
async fn status(self, idle: JayIdleId) {
3886
let tc = &self.tc;
3987
tc.send(jay_idle::GetStatus { self_id: idle });
40-
let interval = Rc::new(Cell::new(0u64));
41-
jay_idle::Interval::handle(tc, idle, interval.clone(), |iv, msg| {
88+
let timeout = Rc::new(Cell::new(0u64));
89+
jay_idle::Interval::handle(tc, idle, timeout.clone(), |iv, msg| {
4290
iv.set(msg.interval);
4391
});
92+
let grace = Rc::new(Cell::new(0u64));
93+
jay_idle::GracePeriod::handle(tc, idle, grace.clone(), |iv, msg| {
94+
iv.set(msg.period);
95+
});
4496
struct Inhibitor {
4597
surface: WlSurfaceId,
4698
_client_id: u64,
@@ -57,26 +109,31 @@ impl Idle {
57109
});
58110
});
59111
tc.round_trip().await;
60-
let minutes = interval.get() / 60;
61-
let seconds = interval.get() % 60;
62-
print!("Interval:");
63-
if minutes == 0 && seconds == 0 {
64-
print!(" disabled");
65-
} else {
66-
if minutes > 0 {
67-
print!(" {} minute", minutes);
68-
if minutes > 1 {
69-
print!("s");
112+
let interval = |iv: u64| {
113+
debug_fn(move |f| {
114+
let minutes = iv / 60;
115+
let seconds = iv % 60;
116+
if minutes == 0 && seconds == 0 {
117+
write!(f, " disabled")?;
118+
} else {
119+
if minutes > 0 {
120+
write!(f, " {} minute", minutes)?;
121+
if minutes > 1 {
122+
write!(f, "s")?;
123+
}
124+
}
125+
if seconds > 0 {
126+
write!(f, " {} second", seconds)?;
127+
if seconds > 1 {
128+
write!(f, "s")?;
129+
}
130+
}
70131
}
71-
}
72-
if seconds > 0 {
73-
print!(" {} second", seconds);
74-
if seconds > 1 {
75-
print!("s");
76-
}
77-
}
78-
}
79-
println!();
132+
Ok(())
133+
})
134+
};
135+
println!("Interval:{}", interval(timeout.get()));
136+
println!("Grace period:{}", interval(grace.get()));
80137
let mut inhibitors = inhibitors.take();
81138
inhibitors.sort_by_key(|i| i.pid);
82139
inhibitors.sort_by_key(|i| i.surface);
@@ -93,15 +150,27 @@ impl Idle {
93150

94151
async fn set(self, idle: JayIdleId, args: IdleSetArgs) {
95152
let tc = &self.tc;
96-
let interval = if args.interval.len() == 1 && args.interval[0] == "disabled" {
97-
0
98-
} else {
99-
parse_duration(&args.interval).as_secs() as u64
100-
};
101153
tc.send(jay_idle::SetInterval {
102154
self_id: idle,
103-
interval,
155+
interval: parse_idle_time(&args.interval),
156+
});
157+
tc.round_trip().await;
158+
}
159+
160+
async fn set_grace_period(self, idle: JayIdleId, args: IdleSetGracePeriodArgs) {
161+
let tc = &self.tc;
162+
tc.send(jay_idle::SetGracePeriod {
163+
self_id: idle,
164+
period: parse_idle_time(&args.period),
104165
});
105166
tc.round_trip().await;
106167
}
107168
}
169+
170+
fn parse_idle_time(time: &[String]) -> u64 {
171+
if time.len() == 1 && time[0] == "disabled" {
172+
0
173+
} else {
174+
parse_duration(time).as_secs() as u64
175+
}
176+
}

src/compositor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,13 @@ fn start_compositor2(
200200
input: Default::default(),
201201
change: Default::default(),
202202
timeout: Cell::new(Duration::from_secs(10 * 60)),
203+
grace_period: Cell::new(Duration::from_secs(5)),
203204
timeout_changed: Default::default(),
204205
inhibitors: Default::default(),
205206
inhibitors_changed: Default::default(),
206207
inhibited_idle_notifications: Default::default(),
207208
backend_idle: Cell::new(true),
209+
in_grace_period: Cell::new(false),
208210
},
209211
run_args,
210212
xwayland: XWaylandState {

src/config/handler.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,10 @@ impl ConfigProxyHandler {
919919
self.state.idle.set_timeout(timeout);
920920
}
921921

922+
fn handle_set_idle_grace_period(&self, period: Duration) {
923+
self.state.idle.set_grace_period(period);
924+
}
925+
922926
fn handle_set_explicit_sync_enabled(&self, enabled: bool) {
923927
self.state.explicit_sync_enabled.set(enabled);
924928
}
@@ -1980,6 +1984,9 @@ impl ConfigProxyHandler {
19801984
ClientMessage::SetXScalingMode { mode } => self
19811985
.handle_set_x_scaling_mode(mode)
19821986
.wrn("set_x_scaling_mode")?,
1987+
ClientMessage::SetIdleGracePeriod { period } => {
1988+
self.handle_set_idle_grace_period(period)
1989+
}
19831990
}
19841991
Ok(())
19851992
}

src/gfx_api.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ impl dyn GfxFramebuffer {
371371
render_cursor: bool,
372372
render_hardware_cursor: bool,
373373
black_background: bool,
374+
fill_black_in_grace_period: bool,
374375
transform: Transform,
375376
visualizer: Option<&DamageVisualizer>,
376377
) -> GfxRenderPass {
@@ -383,6 +384,7 @@ impl dyn GfxFramebuffer {
383384
render_cursor,
384385
render_hardware_cursor,
385386
black_background,
387+
fill_black_in_grace_period,
386388
transform,
387389
visualizer,
388390
)
@@ -406,6 +408,7 @@ impl dyn GfxFramebuffer {
406408
cursor_rect: Option<Rect>,
407409
scale: Scale,
408410
render_hardware_cursor: bool,
411+
fill_black_in_grace_period: bool,
409412
) -> Result<Option<SyncFile>, GfxError> {
410413
self.render_node(
411414
acquire_sync,
@@ -417,6 +420,7 @@ impl dyn GfxFramebuffer {
417420
true,
418421
render_hardware_cursor,
419422
node.has_fullscreen(),
423+
fill_black_in_grace_period,
420424
node.global.persistent.transform.get(),
421425
)
422426
}
@@ -432,6 +436,7 @@ impl dyn GfxFramebuffer {
432436
render_cursor: bool,
433437
render_hardware_cursor: bool,
434438
black_background: bool,
439+
fill_black_in_grace_period: bool,
435440
transform: Transform,
436441
) -> Result<Option<SyncFile>, GfxError> {
437442
let pass = self.create_render_pass(
@@ -442,6 +447,7 @@ impl dyn GfxFramebuffer {
442447
render_cursor,
443448
render_hardware_cursor,
444449
black_background,
450+
fill_black_in_grace_period,
445451
transform,
446452
None,
447453
);
@@ -722,9 +728,16 @@ pub fn create_render_pass(
722728
render_cursor: bool,
723729
render_hardware_cursor: bool,
724730
black_background: bool,
731+
fill_black_in_grace_period: bool,
725732
transform: Transform,
726733
visualizer: Option<&DamageVisualizer>,
727734
) -> GfxRenderPass {
735+
if fill_black_in_grace_period && state.idle.in_grace_period.get() {
736+
return GfxRenderPass {
737+
ops: vec![],
738+
clear: Some(Color::SOLID_BLACK),
739+
};
740+
}
728741
let mut ops = vec![];
729742
let mut renderer = Renderer {
730743
base: renderer_base(physical_size, &mut ops, scale, transform),

0 commit comments

Comments
 (0)