idle: add a grace period
This commit is contained in:
parent
1ad3d11616
commit
e8be15a26c
29 changed files with 405 additions and 79 deletions
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue