1
0
Fork 0
forked from wry/wry

idle: add a grace period

This commit is contained in:
Julian Orth 2025-01-26 12:29:20 +01:00
parent 1ad3d11616
commit e8be15a26c
29 changed files with 405 additions and 79 deletions

View file

@ -893,6 +893,10 @@ impl Client {
self.send(&ClientMessage::SetIdle { timeout })
}
pub fn set_idle_grace_period(&self, period: Duration) {
self.send(&ClientMessage::SetIdleGracePeriod { period })
}
pub fn set_explicit_sync_enabled(&self, enabled: bool) {
self.send(&ClientMessage::SetExplicitSyncEnabled { enabled })
}

View file

@ -527,6 +527,9 @@ pub enum ClientMessage<'a> {
SetXScalingMode {
mode: XScalingMode,
},
SetIdleGracePeriod {
period: Duration,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -224,10 +224,24 @@ pub fn workspaces() -> Vec<Workspace> {
/// Configures the idle timeout.
///
/// `None` disables the timeout.
///
/// The default is 10 minutes.
pub fn set_idle(timeout: Option<Duration>) {
get!().set_idle(timeout.unwrap_or_default())
}
/// Configures the idle grace period.
///
/// The grace period starts after the idle timeout expires. During the grace period, the
/// screen goes black but the displays are not yet disabled and the idle callback (set
/// with [`on_idle`]) is not yet called. This is a purely visual effect to inform the user
/// that the machine will soon go idle.
///
/// The default is 5 seconds.
pub fn set_idle_grace_period(timeout: Duration) {
get!().set_idle_grace_period(timeout)
}
/// Enables or disables explicit sync.
///
/// Calling this after the compositor has started has no effect.

View file

@ -5,6 +5,9 @@
- Implement wl-fixes.
- Implement ei_touchscreen v2.
- Implement idle-notification v2.
- Add an idle grace period. During the grace period, the screen goes black but is neither
disabled nor locked. This is similar to how android handles going idle. The default is
5 seconds.
# 1.7.0 (2024-10-25)

View file

@ -504,6 +504,7 @@ impl MetalConnector {
true,
render_hw_cursor,
node.has_fullscreen(),
true,
node.global.persistent.transform.get(),
Some(&self.state.damage_visualizer),
);

View file

@ -17,7 +17,7 @@ mod xwayland;
use {
crate::{
cli::{
damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs,
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs,
xwayland::XwaylandArgs,
},
compositor::start_compositor,
@ -101,38 +101,6 @@ pub struct RunPrivilegedArgs {
pub program: Vec<String>,
}
#[derive(Subcommand, Debug)]
pub enum IdleCmd {
/// Print the idle status.
Status,
/// Set the idle interval.
Set(IdleSetArgs),
}
impl Default for IdleCmd {
fn default() -> Self {
Self::Status
}
}
#[derive(Args, Debug)]
pub struct IdleSetArgs {
/// The interval of inactivity after which to disable the screens.
///
/// This can be either a number in minutes and seconds or the keyword `disabled` to
/// disable the screensaver.
///
/// Minutes and seconds can be specified in any of the following formats:
///
/// * 1m
/// * 1m5s
/// * 1m 5s
/// * 1min 5sec
/// * 1 minute 5 seconds
#[clap(verbatim_doc_comment, required = true)]
pub interval: Vec<String>,
}
#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, PartialEq)]
pub enum ScreenshotFormat {
/// The PNG image format.

View file

@ -1,13 +1,60 @@
use {
crate::{
cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
cli::{duration::parse_duration, GlobalArgs, IdleArgs},
tools::tool_client::{with_tool_client, Handle, ToolClient},
utils::stack::Stack,
utils::{debug_fn::debug_fn, stack::Stack},
wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId},
},
clap::{Args, Subcommand},
std::{cell::Cell, rc::Rc},
};
#[derive(Subcommand, Debug)]
pub enum IdleCmd {
/// Print the idle status.
Status,
/// Set the idle interval.
Set(IdleSetArgs),
/// Set the idle grace period.
SetGracePeriod(IdleSetGracePeriodArgs),
}
impl Default for IdleCmd {
fn default() -> Self {
Self::Status
}
}
#[derive(Args, Debug)]
pub struct IdleSetArgs {
/// The interval of inactivity after which to disable the screens.
///
/// This can be either a number in minutes and seconds or the keyword `disabled` to
/// disable the screensaver.
///
/// Minutes and seconds can be specified in any of the following formats:
///
/// * 1m
/// * 1m5s
/// * 1m 5s
/// * 1min 5sec
/// * 1 minute 5 seconds
#[clap(verbatim_doc_comment, required = true)]
pub interval: Vec<String>,
}
#[derive(Args, Debug)]
pub struct IdleSetGracePeriodArgs {
/// The grace period after the idle timeout expires.
///
/// During this period, after the idle timeout expires, the screen only goes black
/// but is not yet disabled or locked.
///
/// This uses the same formatting options as the idle timeout itself.
#[clap(verbatim_doc_comment, required = true)]
pub period: Vec<String>,
}
pub fn main(global: GlobalArgs, args: IdleArgs) {
with_tool_client(global.log_level.into(), |tc| async move {
let idle = Idle { tc: tc.clone() };
@ -31,16 +78,21 @@ impl Idle {
match args.command.unwrap_or_default() {
IdleCmd::Status => self.status(idle).await,
IdleCmd::Set(args) => self.set(idle, args).await,
IdleCmd::SetGracePeriod(args) => self.set_grace_period(idle, args).await,
}
}
async fn status(self, idle: JayIdleId) {
let tc = &self.tc;
tc.send(jay_idle::GetStatus { self_id: idle });
let interval = Rc::new(Cell::new(0u64));
jay_idle::Interval::handle(tc, idle, interval.clone(), |iv, msg| {
let timeout = Rc::new(Cell::new(0u64));
jay_idle::Interval::handle(tc, idle, timeout.clone(), |iv, msg| {
iv.set(msg.interval);
});
let grace = Rc::new(Cell::new(0u64));
jay_idle::GracePeriod::handle(tc, idle, grace.clone(), |iv, msg| {
iv.set(msg.period);
});
struct Inhibitor {
surface: WlSurfaceId,
_client_id: u64,
@ -57,26 +109,31 @@ impl Idle {
});
});
tc.round_trip().await;
let minutes = interval.get() / 60;
let seconds = interval.get() % 60;
print!("Interval:");
let interval = |iv: u64| {
debug_fn(move |f| {
let minutes = iv / 60;
let seconds = iv % 60;
if minutes == 0 && seconds == 0 {
print!(" disabled");
write!(f, " disabled")?;
} else {
if minutes > 0 {
print!(" {} minute", minutes);
write!(f, " {} minute", minutes)?;
if minutes > 1 {
print!("s");
write!(f, "s")?;
}
}
if seconds > 0 {
print!(" {} second", seconds);
write!(f, " {} second", seconds)?;
if seconds > 1 {
print!("s");
write!(f, "s")?;
}
}
}
println!();
Ok(())
})
};
println!("Interval:{}", interval(timeout.get()));
println!("Grace period:{}", interval(grace.get()));
let mut inhibitors = inhibitors.take();
inhibitors.sort_by_key(|i| i.pid);
inhibitors.sort_by_key(|i| i.surface);
@ -93,15 +150,27 @@ impl Idle {
async fn set(self, idle: JayIdleId, args: IdleSetArgs) {
let tc = &self.tc;
let interval = if args.interval.len() == 1 && args.interval[0] == "disabled" {
0
} else {
parse_duration(&args.interval).as_secs() as u64
};
tc.send(jay_idle::SetInterval {
self_id: idle,
interval,
interval: parse_idle_time(&args.interval),
});
tc.round_trip().await;
}
async fn set_grace_period(self, idle: JayIdleId, args: IdleSetGracePeriodArgs) {
let tc = &self.tc;
tc.send(jay_idle::SetGracePeriod {
self_id: idle,
period: parse_idle_time(&args.period),
});
tc.round_trip().await;
}
}
fn parse_idle_time(time: &[String]) -> u64 {
if time.len() == 1 && time[0] == "disabled" {
0
} else {
parse_duration(time).as_secs() as u64
}
}

View file

@ -200,11 +200,13 @@ fn start_compositor2(
input: Default::default(),
change: Default::default(),
timeout: Cell::new(Duration::from_secs(10 * 60)),
grace_period: Cell::new(Duration::from_secs(5)),
timeout_changed: Default::default(),
inhibitors: Default::default(),
inhibitors_changed: Default::default(),
inhibited_idle_notifications: Default::default(),
backend_idle: Cell::new(true),
in_grace_period: Cell::new(false),
},
run_args,
xwayland: XWaylandState {

View file

@ -919,6 +919,10 @@ impl ConfigProxyHandler {
self.state.idle.set_timeout(timeout);
}
fn handle_set_idle_grace_period(&self, period: Duration) {
self.state.idle.set_grace_period(period);
}
fn handle_set_explicit_sync_enabled(&self, enabled: bool) {
self.state.explicit_sync_enabled.set(enabled);
}
@ -1980,6 +1984,9 @@ impl ConfigProxyHandler {
ClientMessage::SetXScalingMode { mode } => self
.handle_set_x_scaling_mode(mode)
.wrn("set_x_scaling_mode")?,
ClientMessage::SetIdleGracePeriod { period } => {
self.handle_set_idle_grace_period(period)
}
}
Ok(())
}

View file

@ -371,6 +371,7 @@ impl dyn GfxFramebuffer {
render_cursor: bool,
render_hardware_cursor: bool,
black_background: bool,
fill_black_in_grace_period: bool,
transform: Transform,
visualizer: Option<&DamageVisualizer>,
) -> GfxRenderPass {
@ -383,6 +384,7 @@ impl dyn GfxFramebuffer {
render_cursor,
render_hardware_cursor,
black_background,
fill_black_in_grace_period,
transform,
visualizer,
)
@ -406,6 +408,7 @@ impl dyn GfxFramebuffer {
cursor_rect: Option<Rect>,
scale: Scale,
render_hardware_cursor: bool,
fill_black_in_grace_period: bool,
) -> Result<Option<SyncFile>, GfxError> {
self.render_node(
acquire_sync,
@ -417,6 +420,7 @@ impl dyn GfxFramebuffer {
true,
render_hardware_cursor,
node.has_fullscreen(),
fill_black_in_grace_period,
node.global.persistent.transform.get(),
)
}
@ -432,6 +436,7 @@ impl dyn GfxFramebuffer {
render_cursor: bool,
render_hardware_cursor: bool,
black_background: bool,
fill_black_in_grace_period: bool,
transform: Transform,
) -> Result<Option<SyncFile>, GfxError> {
let pass = self.create_render_pass(
@ -442,6 +447,7 @@ impl dyn GfxFramebuffer {
render_cursor,
render_hardware_cursor,
black_background,
fill_black_in_grace_period,
transform,
None,
);
@ -722,9 +728,16 @@ pub fn create_render_pass(
render_cursor: bool,
render_hardware_cursor: bool,
black_background: bool,
fill_black_in_grace_period: bool,
transform: Transform,
visualizer: Option<&DamageVisualizer>,
) -> GfxRenderPass {
if fill_black_in_grace_period && state.idle.in_grace_period.get() {
return GfxRenderPass {
ops: vec![],
clear: Some(Color::SOLID_BLACK),
};
}
let mut ops = vec![];
let mut renderer = Renderer {
base: renderer_base(physical_size, &mut ops, scale, transform),

View file

@ -243,6 +243,7 @@ impl ExtImageCopyCaptureFrameV1 {
true,
true,
true,
false,
jay_config::video::Transform::None,
)
});

View file

@ -72,7 +72,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
12
13
}
fn required_caps(&self) -> ClientCaps {
@ -213,6 +213,7 @@ impl JayCompositorRequestHandler for JayCompositor {
id: req.id,
client: self.client.clone(),
tracker: Default::default(),
version: self.version,
});
track!(self.client, idle);
self.client.add_client_obj(&idle)?;

View file

@ -14,8 +14,11 @@ pub struct JayIdle {
pub id: JayIdleId,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
}
const GRACE_PERIOD_SINCE: Version = Version(13);
impl JayIdle {
fn send_interval(&self) {
let to = self.client.state.idle.timeout.get();
@ -25,6 +28,14 @@ impl JayIdle {
});
}
fn send_grace_period(&self) {
let to = self.client.state.idle.grace_period.get();
self.client.event(GracePeriod {
self_id: self.id,
period: to.as_secs(),
});
}
fn send_inhibitor(&self, surface: &ZwpIdleInhibitorV1) {
let surface = &surface.surface;
self.client.event(Inhibitor {
@ -42,6 +53,9 @@ impl JayIdleRequestHandler for JayIdle {
fn get_status(&self, _req: GetStatus, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.send_interval();
if self.version >= GRACE_PERIOD_SINCE {
self.send_grace_period();
}
{
let inhibitors = self.client.state.idle.inhibitors.lock();
for inhibitor in inhibitors.values() {
@ -56,11 +70,17 @@ impl JayIdleRequestHandler for JayIdle {
self.client.state.idle.set_timeout(interval);
Ok(())
}
fn set_grace_period(&self, req: SetGracePeriod, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let period = Duration::from_secs(req.period);
self.client.state.idle.set_grace_period(period);
Ok(())
}
}
object_base! {
self = JayIdle;
version = Version(1);
version = self.version;
}
impl Object for JayIdle {}

View file

@ -200,6 +200,7 @@ impl JayScreencast {
true,
true,
false,
false,
Transform::None,
);
match res {

View file

@ -266,6 +266,10 @@ impl TestConfig {
self.send(ClientMessage::SetIdle { timeout })
}
pub fn set_idle_grace_period(&self, period: Duration) -> TestResult {
self.send(ClientMessage::SetIdleGracePeriod { period })
}
pub fn set_floating(&self, seat: SeatId, floating: bool) -> TestResult {
self.send(ClientMessage::SetFloating {
seat: Seat(seat.raw() as _),

View file

@ -12,6 +12,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let ds = run.create_default_setup().await?;
run.cfg.set_idle(Duration::from_micros(100))?;
run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
let idle = run.backend.idle.expect()?;
tassert!(idle.next().is_err());

View file

@ -86,6 +86,7 @@ pub fn take_screenshot(
include_cursor,
true,
false,
false,
Transform::None,
)?;
let drm = match allocator.drm() {

View file

@ -261,12 +261,14 @@ pub struct IdleState {
pub input: Cell<bool>,
pub change: AsyncEvent,
pub timeout: Cell<Duration>,
pub grace_period: Cell<Duration>,
pub timeout_changed: Cell<bool>,
pub inhibitors: CopyHashMap<IdleInhibitorId, Rc<ZwpIdleInhibitorV1>>,
pub inhibitors_changed: Cell<bool>,
pub backend_idle: Cell<bool>,
pub inhibited_idle_notifications:
CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc<ExtIdleNotificationV1>>,
pub in_grace_period: Cell<bool>,
}
impl IdleState {
@ -276,6 +278,12 @@ impl IdleState {
self.change.trigger();
}
pub fn set_grace_period(&self, grace_period: Duration) {
self.grace_period.set(grace_period);
self.timeout_changed.set(true);
self.change.trigger();
}
pub fn add_inhibitor(&self, inhibitor: &Rc<ZwpIdleInhibitorV1>) {
self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone());
self.inhibitors_changed.set(true);
@ -937,6 +945,10 @@ impl State {
output: &Rc<OutputNode>,
hc: &mut dyn HardwareCursorUpdate,
) {
if self.idle.in_grace_period.get() {
hc.set_enabled(false);
return;
}
let Some(g) = self.cursor_user_group_hardware_cursor.get() else {
hc.set_enabled(false);
return;
@ -968,6 +980,7 @@ impl State {
Some(output.global.pos.get()),
output.global.persistent.scale.get(),
render_hw_cursor,
true,
)?;
output.latched(false);
output.perform_screencopies(

View file

@ -61,9 +61,12 @@ impl Idle {
self.dead = true;
return;
}
let grace_period = self.state.idle.grace_period.get();
let timeout = self.state.idle.timeout.get();
let after_grace = timeout.saturating_add(grace_period);
let since = duration_since(self.last_input);
if since >= timeout {
if since >= after_grace {
self.set_in_grace_period(false);
if !timeout.is_zero() && !self.is_inhibited {
if let Some(config) = self.state.config.get() {
config.idle();
@ -71,17 +74,31 @@ impl Idle {
self.backend.set_idle(true);
self.idle = true;
}
} else if since >= timeout {
if !timeout.is_zero() && !self.is_inhibited {
self.set_in_grace_period(true);
}
self.program_timer2(after_grace - since);
} else {
self.program_timer2(timeout - since);
}
}
fn set_in_grace_period(&mut self, val: bool) {
if self.state.idle.in_grace_period.replace(val) == val {
return;
}
self.state.damage(self.state.root.extents.get());
self.state.damage_hardware_cursors(false);
}
fn handle_idle_changes(&mut self) {
if self.state.idle.inhibitors_changed.replace(false) {
let is_inhibited = self.state.idle.inhibitors.len() > 0;
if self.is_inhibited != is_inhibited {
self.is_inhibited = is_inhibited;
if !self.is_inhibited {
self.last_input = now();
self.program_timer();
}
}
@ -91,6 +108,7 @@ impl Idle {
}
if self.state.idle.input.replace(false) {
self.last_input = now();
self.set_in_grace_period(false);
if self.idle {
self.backend.set_idle(false);
self.idle = false;

View file

@ -332,7 +332,7 @@ impl ToolClient {
self_id: s.registry,
name: s.jay_compositor.0,
interface: JayCompositor.name(),
version: s.jay_compositor.1.min(11),
version: s.jay_compositor.1.min(13),
id: id.into(),
});
self.jay_compositor.set(Some(id));

View file

@ -67,7 +67,8 @@ pub enum Action {
dev: ConfigDrmDevice,
},
ConfigureIdle {
idle: Duration,
idle: Option<Duration>,
grace_period: Option<Duration>,
},
ConfigureInput {
input: Box<Input>,
@ -348,6 +349,7 @@ pub struct Config {
pub render_device: Option<DrmDeviceMatch>,
pub inputs: Vec<Input>,
pub idle: Option<Duration>,
pub grace_period: Option<Duration>,
pub explicit_sync_enabled: Option<bool>,
pub focus_follows_mouse: bool,
pub window_management_key: Option<ModifiedKeySym>,

View file

@ -177,7 +177,10 @@ impl ActionParser<'_> {
.extract(val("idle"))?
.parse_map(&mut IdleParser(self.0))
.map_spanned_err(ActionParserError::ConfigureIdle)?;
Ok(Action::ConfigureIdle { idle })
Ok(Action::ConfigureIdle {
idle: idle.timeout,
grace_period: idle.grace_period,
})
}
fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {

View file

@ -294,9 +294,13 @@ impl Parser for ConfigParser<'_> {
}
}
let mut idle = None;
let mut grace_period = None;
if let Some(value) = idle_val {
match value.parse(&mut IdleParser(self.0)) {
Ok(v) => idle = Some(v),
Ok(v) => {
idle = v.timeout;
grace_period = v.grace_period;
}
Err(e) => {
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
}
@ -384,6 +388,7 @@ impl Parser for ConfigParser<'_> {
render_device,
inputs,
idle,
grace_period,
focus_follows_mouse: focus_follows_mouse.despan().unwrap_or(true),
window_management_key,
vrr,

View file

@ -2,7 +2,7 @@ use {
crate::{
config::{
context::Context,
extractor::{n64, opt, Extractor, ExtractorError},
extractor::{n64, opt, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::{
@ -25,7 +25,45 @@ pub enum IdleParserError {
pub struct IdleParser<'a>(pub &'a Context<'a>);
pub struct Idle {
pub timeout: Option<Duration>,
pub grace_period: Option<Duration>,
}
impl Parser for IdleParser<'_> {
type Value = Idle;
type Error = IdleParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (minutes, seconds, grace_period_val) = ext.extract((
opt(n64("minutes")),
opt(n64("seconds")),
opt(val("grace-period")),
))?;
let mut timeout = None;
if minutes.is_some() || seconds.is_some() {
timeout = Some(parse_duration(&minutes, &seconds));
}
let mut grace_period = None;
if let Some(gp) = grace_period_val {
grace_period = Some(gp.parse(&mut GracePeriodParser(self.0))?);
}
Ok(Idle {
timeout,
grace_period,
})
}
}
struct GracePeriodParser<'a>(pub &'a Context<'a>);
impl Parser for GracePeriodParser<'_> {
type Value = Duration;
type Error = IdleParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
@ -37,9 +75,13 @@ impl Parser for IdleParser<'_> {
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?;
let idle = Duration::from_secs(
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
);
Ok(idle)
let grace_period = parse_duration(&minutes, &seconds);
Ok(grace_period)
}
}
fn parse_duration(minutes: &Option<Spanned<u64>>, seconds: &Option<Spanned<u64>>) -> Duration {
Duration::from_secs(
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
)
}

View file

@ -24,7 +24,8 @@ use {
keyboard::{Keymap, ModifiedKeySym},
logging::set_log_level,
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture,
set_explicit_sync_enabled, set_idle, set_ui_drag_enabled, set_ui_drag_threshold,
set_explicit_sync_enabled, set_idle, set_idle_grace_period, set_ui_drag_enabled,
set_ui_drag_threshold,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
switch_to_vt,
theme::{reset_colors, reset_font, reset_sizes, set_font},
@ -188,7 +189,14 @@ impl Action {
}
})
}
Action::ConfigureIdle { idle } => B::new(move || set_idle(Some(idle))),
Action::ConfigureIdle { idle, grace_period } => B::new(move || {
if let Some(idle) = idle {
set_idle(Some(idle))
}
if let Some(period) = grace_period {
set_idle_grace_period(period)
}
}),
Action::MoveToOutput { output, workspace } => {
let state = state.clone();
B::new(move || {
@ -967,6 +975,9 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
if let Some(idle) = config.idle {
set_idle(Some(idle));
}
if let Some(period) = config.grace_period {
set_idle_grace_period(period);
}
}
on_devices_enumerated({
let state = state.clone();

View file

@ -811,8 +811,25 @@
"Vulkan"
]
},
"GracePeriod": {
"description": "The definition of a grace period.\n\nOmitted values are set to 0. If all values are 0, the grace period is disabled.\n\n- Example:\n\n ```toml\n idle.grace-period.seconds = 3\n ```\n",
"type": "object",
"properties": {
"minutes": {
"type": "integer",
"description": "The number of minutes the grace period lasts.",
"minimum": 0.0
},
"seconds": {
"type": "integer",
"description": "The number of seconds the grace period lasts.",
"minimum": 0.0
}
},
"required": []
},
"Idle": {
"description": "The definition of an idle timeout.\n\nOmitted values are set to 0. If all values are 0, the idle timeout is disabled.\n\n- Example:\n\n ```toml\n idle.minutes = 10\n ```\n",
"description": "The definition of an idle timeout.\n\nOmitted values are set to 0. If any value is explicitly set and all values are 0, the\nidle timeout is disabled.\n\n- Example:\n\n ```toml\n idle.minutes = 10\n ```\n",
"type": "object",
"properties": {
"minutes": {
@ -824,6 +841,10 @@
"type": "integer",
"description": "The number of seconds before going idle.",
"minimum": 0.0
},
"grace-period": {
"description": "The grace period after the timeout expires.\n\nDuring the grace period, the screen goes black but the outputs are not yet\ndisabled and the `on-idle` action does not yet run. This is a visual indicator\nthat the system will soon get idle.\n\nThe default is 5 seconds.\n",
"$ref": "#/$defs/GracePeriod"
}
},
"required": []

View file

@ -1670,12 +1670,51 @@ The string should have one of the following values:
<a name="types-GracePeriod"></a>
### `GracePeriod`
The definition of a grace period.
Omitted values are set to 0. If all values are 0, the grace period is disabled.
- Example:
```toml
idle.grace-period.seconds = 3
```
Values of this type should be tables.
The table has the following fields:
- `minutes` (optional):
The number of minutes the grace period lasts.
The value of this field should be a number.
The numbers should be integers.
The numbers should be greater than or equal to 0.
- `seconds` (optional):
The number of seconds the grace period lasts.
The value of this field should be a number.
The numbers should be integers.
The numbers should be greater than or equal to 0.
<a name="types-Idle"></a>
### `Idle`
The definition of an idle timeout.
Omitted values are set to 0. If all values are 0, the idle timeout is disabled.
Omitted values are set to 0. If any value is explicitly set and all values are 0, the
idle timeout is disabled.
- Example:
@ -1707,6 +1746,18 @@ The table has the following fields:
The numbers should be greater than or equal to 0.
- `grace-period` (optional):
The grace period after the timeout expires.
During the grace period, the screen goes black but the outputs are not yet
disabled and the `on-idle` action does not yet run. This is a visual indicator
that the system will soon get idle.
The default is 5 seconds.
The value of this field should be a [GracePeriod](#types-GracePeriod).
<a name="types-Input"></a>
### `Input`

View file

@ -2293,7 +2293,8 @@ Idle:
description: |
The definition of an idle timeout.
Omitted values are set to 0. If all values are 0, the idle timeout is disabled.
Omitted values are set to 0. If any value is explicitly set and all values are 0, the
idle timeout is disabled.
- Example:
@ -2313,6 +2314,44 @@ Idle:
integer_only: true
minimum: 0
required: false
grace-period:
description: |
The grace period after the timeout expires.
During the grace period, the screen goes black but the outputs are not yet
disabled and the `on-idle` action does not yet run. This is a visual indicator
that the system will soon get idle.
The default is 5 seconds.
ref: GracePeriod
required: false
GracePeriod:
kind: table
description: |
The definition of a grace period.
Omitted values are set to 0. If all values are 0, the grace period is disabled.
- Example:
```toml
idle.grace-period.seconds = 3
```
fields:
minutes:
description: The number of minutes the grace period lasts.
kind: number
integer_only: true
minimum: 0
required: false
seconds:
description: The number of seconds the grace period lasts.
kind: number
integer_only: true
minimum: 0
required: false
RepeatRate:

View file

@ -7,6 +7,10 @@ request set_interval {
interval: pod(u64),
}
request set_grace_period (since = 13) {
period: pod(u64),
}
# events
event interval {
@ -19,3 +23,7 @@ event inhibitor {
pid: pod(u64),
comm: str,
}
event grace_period (since = 13) {
period: pod(u64),
}