diff --git a/src/backend.rs b/src/backend.rs index e6dd2489..2dcfa462 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -14,7 +14,13 @@ linear_ids!(ConnectorIds, ConnectorId); linear_ids!(InputDeviceIds, InputDeviceId); pub trait Backend { - fn switch_to(&self, vtnr: u32); + fn switch_to(&self, vtnr: u32) { + let _ = vtnr; + } + + fn set_idle(&self, idle: bool) { + let _ = idle; + } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index 7fc3f715..aa3a19b0 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -8,11 +8,7 @@ use { pub struct DummyBackend {} -impl Backend for DummyBackend { - fn switch_to(&self, vtnr: u32) { - let _ = vtnr; - } -} +impl Backend for DummyBackend {} pub struct DummyOutput { pub id: ConnectorId, diff --git a/src/backends/metal.rs b/src/backends/metal.rs index 39348bca..ffeba71b 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -4,7 +4,7 @@ mod video; use { crate::{ - async_engine::{AsyncError, AsyncFd}, + async_engine::{AsyncError, AsyncFd, Phase}, backend::{ Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState, @@ -25,6 +25,7 @@ use { logind::{LogindError, Session}, render::RenderError, state::State, + tasks::idle, udev::{Udev, UdevError, UdevMonitor}, utils::{ clonecell::{CloneCell, UnsafeCellCloneSafe}, @@ -34,7 +35,10 @@ use { smallmap::SmallMap, syncqueue::SyncQueue, }, - video::{drm::DrmError, gbm::GbmError}, + video::{ + drm::{DrmError, DRM_MODE_ATOMIC_ALLOW_MODESET}, + gbm::GbmError, + }, }, std::{ cell::{Cell, RefCell}, @@ -128,6 +132,34 @@ impl Backend for MetalBackend { } }) } + + fn set_idle(&self, idle: bool) { + let devices = self.device_holder.drm_devices.lock(); + for device in devices.values() { + let mut change = device.dev.master.change(); + for connector in device.connectors.values() { + if let Some(crtc) = connector.crtc.get() { + if idle == crtc.active.value.get() { + crtc.active.value.set(!idle); + change.change_object(crtc.id, |c| { + c.change(crtc.active.id, (!idle) as _); + }); + } + } + } + if let Err(e) = change.commit(DRM_MODE_ATOMIC_ALLOW_MODESET, 0) { + log::error!("Could not set monitors idle/not idle: {}", ErrorFmt(e)); + return; + } + } + if !idle { + for device in devices.values() { + for connector in device.connectors.values() { + self.present(connector); + } + } + } + } } async fn run_(state: Rc) -> Result<(), MetalError> { @@ -193,6 +225,9 @@ async fn run_(state: Rc) -> Result<(), MetalError> { return Err(MetalError::Enumerate(Box::new(e))); } state.backend.set(Some(metal.clone())); + let _idle = state + .eng + .spawn2(Phase::PostLayout, idle(state.clone(), metal.clone())); pending().await } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 2e96feb3..db9361d2 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1002,7 +1002,14 @@ impl MetalBackend { self.present(connector); } - fn present(&self, connector: &Rc) { + pub fn present(&self, connector: &Rc) { + let crtc = match connector.crtc.get() { + Some(crtc) => crtc, + _ => return, + }; + if !crtc.active.value.get() { + return; + } let buffers = match connector.buffers.get() { None => return, Some(b) => b, diff --git a/src/backends/x.rs b/src/backends/x.rs index 3385cf86..e8a34196 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -113,11 +113,7 @@ pub struct XBackend { _grab: SpawnedFuture<()>, } -impl Backend for XBackend { - fn switch_to(&self, _vtnr: u32) { - log::error!("X backend cannot switch vts"); - } -} +impl Backend for XBackend {} struct XBackendData { state: Rc, diff --git a/src/cli.rs b/src/cli.rs index c8a5d7e5..e8666a54 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -47,7 +47,7 @@ pub enum Cmd { pub struct ScreenshotArgs { /// The filename of the saved screenshot /// - /// If no filename is given, the screenshot will be saved under jay-%Y-%m-%d-%H:%M:%S.qoi + /// If no filename is given, the screenshot will be saved under %Y-%m-%d-%H%M%S_jay.qoi /// in the current directory. /// /// The filename can contain the usual strftime parameters. diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index 22359692..e48b71ea 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -60,7 +60,7 @@ async fn run(screenshot: Rc) { .args .filename .as_deref() - .unwrap_or("jay-%Y-%m-%d-%H:%M:%S.qoi"); + .unwrap_or("%Y-%m-%d-%H%M%S_jay.qoi"); let filename = Local::now().format(filename).to_string(); if let Err(e) = std::fs::write(&filename, &data) { fatal!("Could not write `{}`: {}", filename, ErrorFmt(e)); diff --git a/src/compositor.rs b/src/compositor.rs index 7ead0405..46cf9a06 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -17,7 +17,7 @@ use { logger::Logger, render::{self, RenderError}, sighand::{self, SighandError}, - state::{ConnectorData, State}, + state::{ConnectorData, IdleState, State}, tasks, tree::{ container_layout, container_render_data, float_layout, float_titles, DisplayNode, @@ -32,12 +32,12 @@ use { xwayland, }, forker::ForkerProxy, - std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc}, + std::{cell::Cell, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::c, }; -pub const MAX_EXTENTS: i32 = (1 << 24) - 1; +pub const MAX_EXTENTS: i32 = (1 << 22) - 1; pub fn start_compositor(global: GlobalArgs, args: RunArgs) { let forker = match ForkerProxy::create() { @@ -123,6 +123,12 @@ fn main_(forker: Rc, logger: Arc, _args: &RunArgs) -> Resul connectors: Default::default(), outputs: Default::default(), status: Default::default(), + idle: IdleState { + input: Default::default(), + change: Default::default(), + timeout: Cell::new(Duration::from_secs(10)), + timeout_changed: Default::default(), + }, }); { let dummy_output = Rc::new(OutputNode { diff --git a/src/ifs/wl_surface/xwindow.rs b/src/ifs/wl_surface/xwindow.rs index 8577fe29..f7853a76 100644 --- a/src/ifs/wl_surface/xwindow.rs +++ b/src/ifs/wl_surface/xwindow.rs @@ -247,10 +247,12 @@ impl Xwindow { _ => return, }; let extents = self.surface.extents.get(); - // let extents = self.xdg.extents.get(); // parent.child_active_changed(self, self.active_surfaces.get() > 0); parent.node_child_size_changed(self, extents.width(), extents.height()); - // parent.child_title_changed(self, self.title.borrow_mut().deref()); + parent.node_child_title_changed( + self, + self.data.info.title.borrow_mut().as_deref().unwrap_or(""), + ); } pub fn is_mapped(&self) -> bool { @@ -414,7 +416,9 @@ impl SizedNode for Xwindow { fn change_extents(self: &Rc, rect: &Rect) { let old = self.data.info.extents.replace(*rect); if old != *rect { - self.events.push(XWaylandEvent::Configure(self.clone())); + if !self.data.info.override_redirect.get() { + self.events.push(XWaylandEvent::Configure(self.clone())); + } if old.position() != rect.position() { self.surface.set_absolute_position(rect.x1(), rect.y1()); } diff --git a/src/state.rs b/src/state.rs index 0dd002b9..3a31d96a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -25,8 +25,8 @@ use { OutputNode, SizedNode, WorkspaceNode, }, utils::{ - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, fdcloser::FdCloser, - linkedlist::LinkedList, queue::AsyncQueue, + asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, fdcloser::FdCloser, linkedlist::LinkedList, queue::AsyncQueue, }, wheel::Wheel, xkbcommon::{XkbContext, XkbKeymap}, @@ -37,6 +37,7 @@ use { cell::{Cell, RefCell}, rc::Rc, sync::Arc, + time::Duration, }, }; @@ -77,6 +78,14 @@ pub struct State { pub connectors: CopyHashMap>, pub outputs: CopyHashMap>, pub status: CloneCell>, + pub idle: IdleState, +} + +pub struct IdleState { + pub input: Cell, + pub change: AsyncEvent, + pub timeout: Cell, + pub timeout_changed: Cell, } pub struct InputDeviceData { @@ -290,4 +299,10 @@ impl State { output.set_status(&status); } } + + pub fn input_occurred(&self) { + if !self.idle.input.replace(true) { + self.idle.change.trigger(); + } + } } diff --git a/src/tasks.rs b/src/tasks.rs index 8a75926b..b467faf7 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,10 +1,10 @@ mod backend; mod connector; +mod idle; mod input_device; mod slow_clients; mod start_backend; -pub use start_backend::start_backend; use { crate::{ state::State, @@ -12,6 +12,7 @@ use { }, std::rc::Rc, }; +pub use {idle::idle, start_backend::start_backend}; pub async fn handle_backend_events(state: Rc) { let mut beh = BackendEventHandler { state }; diff --git a/src/tasks/idle.rs b/src/tasks/idle.rs new file mode 100644 index 00000000..8c6f0f77 --- /dev/null +++ b/src/tasks/idle.rs @@ -0,0 +1,101 @@ +use { + crate::{ + async_engine::{AsyncError, Timer}, + backend::Backend, + state::State, + utils::errorfmt::ErrorFmt, + }, + futures_util::{select, FutureExt}, + std::{rc::Rc, time::Duration}, + uapi::c, +}; + +pub async fn idle(state: Rc, backend: Rc) { + let timer = match state.eng.timer(c::CLOCK_MONOTONIC) { + Ok(t) => t, + Err(e) => { + log::error!("Could not create idle timer: {}", ErrorFmt(e)); + return; + } + }; + state.idle.change.trigger(); + state.idle.timeout_changed.set(true); + let mut idle = Idle { + state, + backend, + timer, + idle: false, + dead: false, + last_input: now(), + }; + idle.run().await; +} + +struct Idle { + state: Rc, + backend: Rc, + timer: Timer, + idle: bool, + dead: bool, + last_input: c::timespec, +} + +impl Idle { + async fn run(&mut self) { + while !self.dead { + select! { + res = self.timer.expired().fuse() => self.handle_expired(res), + _ = self.state.idle.change.triggered().fuse() => self.handle_idle_changes(), + } + } + } + + fn handle_expired(&mut self, res: Result) { + if let Err(e) = res { + log::error!("Could not wait for idle timer to expire: {}", ErrorFmt(e)); + self.dead = true; + return; + } + let timeout = self.state.idle.timeout.get(); + let since = duration_since(self.last_input); + if since >= timeout { + self.backend.set_idle(true); + self.idle = true; + } else { + self.program_timer(timeout - since); + } + } + + fn handle_idle_changes(&mut self) { + if self.state.idle.timeout_changed.replace(false) { + self.program_timer(self.state.idle.timeout.get()); + } + if self.state.idle.input.replace(false) { + self.last_input = now(); + if self.idle { + self.backend.set_idle(false); + self.idle = false; + } + } + } + + fn program_timer(&mut self, timeout: Duration) { + if let Err(e) = self.timer.program(Some(timeout), None) { + log::error!("Could not program idle timer: {}", ErrorFmt(e)); + self.dead = true; + } + } +} + +fn now() -> c::timespec { + let mut now = uapi::pod_zeroed(); + let _ = uapi::clock_gettime(c::CLOCK_MONOTONIC, &mut now); + now +} + +fn duration_since(start: c::timespec) -> Duration { + let now = now(); + let nanos = + (now.tv_sec as i64 - start.tv_sec as i64) * 1_000_000_000 + (now.tv_nsec - start.tv_nsec); + Duration::from_nanos(nanos as u64) +} diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 1c066429..3c9e80e1 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -56,6 +56,7 @@ impl DeviceHandler { } if any_events { seat.mark_last_active(); + self.state.input_occurred(); } } else { while self.dev.event().is_some() { diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index e40bbd61..b0d7ce1c 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -7,7 +7,7 @@ use { }, rect::Rect, state::State, - tree::Node, + tree::{Node, SizedNode}, utils::{ bitflags::BitflagsExt, errorfmt::ErrorFmt, linkedlist::LinkedList, queue::AsyncQueue, }, @@ -1344,15 +1344,15 @@ impl Wm { }; self.update_override_redirect(data, event.override_redirect); if data.info.override_redirect.get() { - let extents = Rect::new_sized( - event.x as _, - event.y as _, - event.width as _, - event.height as _, - ) - .unwrap(); - let changed = data.info.extents.replace(extents) != extents; - if changed { + if let Some(window) = data.window.get() { + let extents = Rect::new_sized( + event.x as _, + event.y as _, + event.width as _, + event.height as _, + ) + .unwrap(); + window.change_extents(&extents); self.state.tree_changed(); } }