// SPDX-License-Identifier: GPL-3.0-only use crate::{shell::grabs::SeatMoveGrabState, state::ClientState, utils::prelude::*}; use calloop::Interest; use smithay::{ backend::renderer::{ element::{surface::KindEvaluation, Kind}, utils::{on_commit_buffer_handler, with_renderer_surface_state}, }, delegate_compositor, desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType}, reexports::wayland_server::{protocol::wl_surface::WlSurface, Client, Resource}, utils::{Clock, Logical, Monotonic, Size, Time, SERIAL_COUNTER}, wayland::{ compositor::{ add_blocker, add_post_commit_hook, add_pre_commit_hook, with_states, with_surface_tree_downward, BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes, SurfaceData, TraversalAction, }, dmabuf::get_dmabuf, drm_syncobj::DrmSyncobjCachedState, seat::WaylandFocus, shell::{ wlr_layer::LayerSurfaceAttributes, xdg::{ ToplevelSurface, XdgPopupSurfaceRoleAttributes, XdgToplevelSurfaceRoleAttributes, }, }, }, xwayland::XWaylandClientData, }; use std::{collections::VecDeque, sync::Mutex, time::Duration}; fn toplevel_ensure_initial_configure( toplevel: &ToplevelSurface, size: Option>, ) -> bool { // send the initial configure if relevant let initial_configure_sent = with_states(toplevel.wl_surface(), |states| { states .data_map .get::>() .unwrap() .lock() .unwrap() .initial_configure_sent }); if !initial_configure_sent { toplevel.with_pending_state(|states| states.size = size); toplevel.send_configure(); } initial_configure_sent } fn xdg_popup_ensure_initial_configure(popup: &PopupKind) { if let PopupKind::Xdg(popup) = popup { let initial_configure_sent = with_states(popup.wl_surface(), |states| { states .data_map .get::>() .unwrap() .lock() .unwrap() .initial_configure_sent }); if !initial_configure_sent { // NOTE: This should never fail as the initial configure is always // allowed. popup.send_configure().expect("initial configure failed"); } } } fn layer_surface_check_inital_configure(surface: &LayerSurface) -> bool { // send the initial configure if relevant let initial_configure_sent = with_states(surface.wl_surface(), |states| { states .data_map .get::>() .unwrap() .lock() .unwrap() .initial_configure_sent }); initial_configure_sent } pub fn client_compositor_state(client: &Client) -> &CompositorClientState { if let Some(state) = client.get_data::() { return &state.compositor_state; } if let Some(state) = client.get_data::() { return &state.compositor_client_state; } panic!("Unknown client data type") } #[derive(Debug)] struct FrametimeData { last_commit: Option>, last_diffs: VecDeque, estimation: Duration, } impl Default for FrametimeData { fn default() -> Self { FrametimeData { last_commit: None, last_diffs: VecDeque::with_capacity(100), estimation: Duration::MAX, } } } pub fn frame_time_estimation(clock: &Clock, states: &SurfaceData) -> Option { let data = states .data_map .get::>()? .lock() .unwrap(); if let Some(ref last) = data.last_commit { // if the time since the last commit is already higher than our estimation, // there is no reason to not use that as a better "guess" let diff = Time::elapsed(last, clock.now()); Some(diff.max(data.estimation)) } else { Some(data.estimation) } } pub fn recursive_frame_time_estimation( clock: &Clock, surface: &WlSurface, ) -> Option { let mut overall_estimate = None; with_surface_tree_downward( surface, (), |_, _, _| TraversalAction::DoChildren(()), |_, data, _| { let surface_estimate = frame_time_estimation(clock, data); overall_estimate = match (overall_estimate, surface_estimate) { (x, None) => x, (None, Some(estimate)) => Some(estimate), (Some(a), Some(b)) => Some(a.min(b)), }; }, |_, _, _| true, ); overall_estimate } pub const FRAME_TIME_FILTER: KindEvaluation = KindEvaluation::Dynamic({ fn frame_time_filter_fn(states: &SurfaceData) -> Kind { let clock = Clock::::new(); const _20_FPS: Duration = Duration::from_nanos(1_000_000_000 / 20); if frame_time_estimation(&clock, states).is_some_and(|dur| dur <= _20_FPS) { Kind::ScanoutCandidate } else { Kind::Unspecified } } frame_time_filter_fn }); impl CompositorHandler for State { fn compositor_state(&mut self) -> &mut CompositorState { &mut self.common.compositor_state } fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { client_compositor_state(client) } fn new_surface(&mut self, surface: &WlSurface) { add_pre_commit_hook::(surface, move |state, _dh, surface| { let mut acquire_point = None; let maybe_dmabuf = with_states(surface, |surface_data| { acquire_point = surface_data .cached_state .get::() .pending() .acquire_point .clone(); surface_data .cached_state .get::() .pending() .buffer .as_ref() .and_then(|assignment| match assignment { BufferAssignment::NewBuffer(buffer) => get_dmabuf(buffer).ok().cloned(), _ => None, }) }); if let Some(dmabuf) = maybe_dmabuf { if let Some(acquire_point) = acquire_point { if let Ok((blocker, source)) = acquire_point.generate_blocker() { let client = surface.client().unwrap(); let res = state.common.event_loop_handle.insert_source( source, move |_, _, state| { let dh = state.common.display_handle.clone(); state .client_compositor_state(&client) .blocker_cleared(state, &dh); Ok(()) }, ); if res.is_ok() { add_blocker(surface, blocker); return; } } } if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) { let client = surface.client().unwrap(); let res = state .common .event_loop_handle .insert_source(source, move |_, _, state| { let dh = state.common.display_handle.clone(); state .client_compositor_state(&client) .blocker_cleared(state, &dh); Ok(()) }); if res.is_ok() { add_blocker(surface, blocker); } } } }); add_post_commit_hook::(surface, |state, _dh, surface| { let now = state.common.clock.now(); with_states(surface, |states| { let mut data = states .data_map .get_or_insert_threadsafe::, _>(Default::default) .lock() .unwrap(); if let Some(ref last) = data.last_commit { let diff = Time::elapsed(last, now); data.last_diffs.push_back(diff); if data.last_diffs.len() > 100 { data.last_diffs.pop_front(); } data.estimation = data .last_diffs .iter() .fold(Duration::ZERO, |acc, new| acc.saturating_add(*new)) / (data.last_diffs.len() as u32); } data.last_commit = Some(now); }); }); } fn commit(&mut self, surface: &WlSurface) { // first load the buffer for various smithay helper functions (which also initializes the RendererSurfaceState) on_commit_buffer_handler::(surface); // and refresh smithays internal state self.common.on_commit(surface); // handle initial configure events and map windows if necessary let mapped = self.send_initial_configure_and_map(surface); let mut shell = self.common.shell.write(); // schedule a new render if let Some(output) = shell.visible_output_for_surface(surface) { self.backend.schedule_render(output); } if mapped { return; } if let Some(popup) = self.common.popups.find_popup(surface) { xdg_popup_ensure_initial_configure(&popup); return; } if with_renderer_surface_state(surface, |state| state.buffer().is_none()).unwrap_or(false) { // handle null-commits causing weird conflicts: // session-lock disallows null commits // if it was a move-grab? let seat = shell.seats.last_active().clone(); let moved_window = seat .user_data() .get::() .unwrap() .lock() .unwrap() .as_ref() .and_then(|state| { state .element() .windows() .any(|(s, _)| { s.wl_surface() .as_deref() .map(|s| s == surface) .unwrap_or(false) }) .then(|| state.element()) }); if let Some(window) = moved_window { if window.is_stack() { let stack = window.stack_ref().unwrap(); if let Some(i) = stack.surfaces().position(|s| { s.wl_surface() .as_deref() .map(|s| s == surface) .unwrap_or(false) }) { stack.remove_idx(i); } } else { std::mem::drop(shell); seat.get_pointer() .unwrap() .unset_grab(self, SERIAL_COUNTER.next_serial(), 0); return; } } // if it was a layer-shell surface? // ignore, that will affect recompute normally // if it was a sticky / floating / tiled window // we could unmap, I guess? // if it was an x11 surface => do nothing, we will get a separate UnmapNotify anyway } else { // handle some special cases, like grabs and changing layer surfaces // If we would re-position the window inside the grab we would get a weird jittery animation. // We only want to resize once the client has acknoledged & commited the new size, // so we need to carefully track the state through different handlers. if let Some(element) = shell.element_for_surface(surface).cloned() { crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location( element.clone(), &mut shell, ); } } // re-arrange layer-surfaces (commits may change size and positioning) let layer_output = shell .outputs() .find(|o| { let map = layer_map_for_output(o); map.layer_for_surface(surface, WindowSurfaceType::ALL) .is_some() }) .cloned(); if let Some(output) = layer_output { let changed = layer_map_for_output(&output).arrange(); if changed { shell.workspaces.recalculate(); } } } } impl State { fn send_initial_configure_and_map(&mut self, surface: &WlSurface) -> bool { let mut shell = self.common.shell.write(); if let Some(pending) = shell .pending_windows .iter() .find(|pending| pending.surface.wl_surface().as_deref() == Some(surface)) { if let Some(toplevel) = pending.surface.0.toplevel() { let initial_size = if let Some(output) = pending.fullscreen.as_ref() { Some(output.geometry().size.as_logical()) } else if pending.maximized { let active_output = shell.seats.last_active().active_output(); let zone = layer_map_for_output(&active_output).non_exclusive_zone(); Some(zone.size) } else { None }; if toplevel_ensure_initial_configure(toplevel, initial_size) && with_renderer_surface_state(surface, |state| state.buffer().is_some()) .unwrap_or(false) { let window = pending.surface.clone(); window.on_commit(); let res = shell.map_window( &window, &mut self.common.toplevel_info_state, &mut self.common.workspace_state, &self.common.event_loop_handle, ); if let Some(target) = res { let seat = shell.seats.last_active().clone(); std::mem::drop(shell); Shell::set_focus(self, Some(&target), &seat, None, true); return true; } } } } if let Some(layer_surface) = shell .pending_layers .iter() .find(|pending| pending.surface.wl_surface() == surface) .map(|pending| pending.surface.clone()) { if !layer_surface_check_inital_configure(&layer_surface) { // compute initial dimensions by mapping if let Some(target) = shell.map_layer(&layer_surface) { let seat = shell.seats.last_active().clone(); std::mem::drop(shell); Shell::set_focus(self, Some(&target), &seat, None, false); } layer_surface.layer_surface().send_configure(); return true; } }; false } } delegate_compositor!(State);