idle: add a grace period
This commit is contained in:
parent
1ad3d11616
commit
e8be15a26c
29 changed files with 405 additions and 79 deletions
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -527,6 +527,9 @@ pub enum ClientMessage<'a> {
|
|||
SetXScalingMode {
|
||||
mode: XScalingMode,
|
||||
},
|
||||
SetIdleGracePeriod {
|
||||
period: Duration,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -504,6 +504,7 @@ impl MetalConnector {
|
|||
true,
|
||||
render_hw_cursor,
|
||||
node.has_fullscreen(),
|
||||
true,
|
||||
node.global.persistent.transform.get(),
|
||||
Some(&self.state.damage_visualizer),
|
||||
);
|
||||
|
|
|
|||
34
src/cli.rs
34
src/cli.rs
|
|
@ -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.
|
||||
|
|
|
|||
127
src/cli/idle.rs
127
src/cli/idle.rs
|
|
@ -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:");
|
||||
if minutes == 0 && seconds == 0 {
|
||||
print!(" disabled");
|
||||
} else {
|
||||
if minutes > 0 {
|
||||
print!(" {} minute", minutes);
|
||||
if minutes > 1 {
|
||||
print!("s");
|
||||
let interval = |iv: u64| {
|
||||
debug_fn(move |f| {
|
||||
let minutes = iv / 60;
|
||||
let seconds = iv % 60;
|
||||
if minutes == 0 && seconds == 0 {
|
||||
write!(f, " disabled")?;
|
||||
} else {
|
||||
if minutes > 0 {
|
||||
write!(f, " {} minute", minutes)?;
|
||||
if minutes > 1 {
|
||||
write!(f, "s")?;
|
||||
}
|
||||
}
|
||||
if seconds > 0 {
|
||||
write!(f, " {} second", seconds)?;
|
||||
if seconds > 1 {
|
||||
write!(f, "s")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if seconds > 0 {
|
||||
print!(" {} second", seconds);
|
||||
if seconds > 1 {
|
||||
print!("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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ impl ExtImageCopyCaptureFrameV1 {
|
|||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
jay_config::video::Transform::None,
|
||||
)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ impl JayScreencast {
|
|||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
Transform::None,
|
||||
);
|
||||
match res {
|
||||
|
|
|
|||
|
|
@ -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 _),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ pub fn take_screenshot(
|
|||
include_cursor,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
Transform::None,
|
||||
)?;
|
||||
let drm = match allocator.drm() {
|
||||
|
|
|
|||
13
src/state.rs
13
src/state.rs
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue