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

@ -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:");
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
}
}

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));