use { crate::{ backend::{ BackendColorSpace, BackendConnectorState, BackendEotfs, BackendGammaLut, ButtonState, HardwareCursor, Mode, transaction::BackendConnectorTransactionError, }, client::ClientId, cmm::cmm_description::ColorDescription, control_center::CCI_OUTPUTS, cursor::KnownCursor, fixed::Fixed, gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, ifs::{ ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_output::JayOutput, jay_screencast::JayScreencast, wl_buffer::WlBufferStorage, wl_output::{BlendSpace, WlOutputGlobal}, wl_seat::{ BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2, tablet::{TabletTool, TabletToolChanges, TabletToolId}, wl_pointer::PendingScroll, }, wl_surface::{ SurfaceSendPreferredColorDescription, SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor, ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, tray::DynTrayItem, zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1}, }, workspace_manager::{ ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1, ext_workspace_manager_v1::WorkspaceManagerId, }, wp_content_type_v1::ContentType, wp_presentation_feedback::KIND_VSYNC, zwlr_gamma_control_v1::ZwlrGammaControlV1, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, }, output_schedule::OutputSchedule, rect::Rect, renderer::Renderer, scale::Scale, state::State, text::TextTexture, theme::BarPosition, tree::{ Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, PinnedNode, StackedNode, TddType, TileDragDestination, Transform, WorkspaceDisplayOrder, WorkspaceDragDestination, WorkspaceNode, WorkspaceNodeId, walker::NodeVisitor, }, utils::{ asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, event_listener::EventSource, hash_map_ext::HashMapExt, linkedlist::{LinkedList, NodeRef}, on_drop_event::OnDropEvent, scroller::Scroller, }, wire::{ ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id, }, }, ahash::AHashMap, jay_config::video::{TearingMode as ConfigTearingMode, VrrMode as ConfigVrrMode}, numeric_sort::cmp, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, ops::{BitOrAssign, Deref}, rc::Rc, }, }; tree_id!(OutputNodeId); pub struct OutputNode { pub id: OutputNodeId, pub global: Rc, pub jay_outputs: CopyHashMap<(ClientId, JayOutputId), Rc>, pub workspaces: LinkedList>, pub workspace: CloneCell>>, pub seat_state: NodeSeatState, pub layers: [LinkedList>; 4], pub exclusive_zones: Cell, pub workspace_rect: Cell, pub workspace_rect_rel: Cell, pub non_exclusive_rect: Cell, pub non_exclusive_rect_rel: Cell, pub bar_rect: Cell, pub bar_rect_rel: Cell, pub bar_rect_with_separator: Cell, pub bar_rect_with_separator_rel: Cell, pub bar_separator_rect: Cell, pub bar_separator_rect_rel: Cell, pub render_data: RefCell, pub state: Rc, pub is_dummy: bool, pub status: CloneCell>, pub scroll: Scroller, pub pointer_positions: CopyHashMap, pub pointer_down: CopyHashMap, pub lock_surface: CloneCell>>, pub hardware_cursor: CloneCell>>, pub hardware_cursor_needs_render: Cell, pub update_render_data_scheduled: Cell, pub screencasts: CopyHashMap<(ClientId, JayScreencastId), Rc>, pub screencopies: CopyHashMap<(ClientId, ZwlrScreencopyFrameV1Id), Rc>, pub title_visible: Cell, pub schedule: Rc, pub latch_event: EventSource, pub vblank_event: EventSource, pub presentation_event: EventSource, pub render_margin_ns: Cell, pub flip_margin_ns: Cell>, pub ext_copy_sessions: CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc>, pub before_latch_event: EventSource, pub tray_start_rel: Cell, pub tray_items: LinkedList>, pub ext_workspace_groups: CopyHashMap>, pub pinned: LinkedList>, pub tearing: Cell, pub active_zwlr_gamma_control: CloneCell>>, } #[derive(Copy, Clone, Debug, PartialEq)] pub enum BeforeLatchResult { None, Yield, } impl BitOrAssign for BeforeLatchResult { fn bitor_assign(&mut self, rhs: Self) { if rhs == BeforeLatchResult::Yield { *self = rhs; } } } pub trait BeforeLatchListener { fn before_latch(self: Rc, present: u64) -> BeforeLatchResult; } pub trait LatchListener { fn after_latch(self: Rc, on: &OutputNode, tearing: bool); } pub trait VblankListener { fn after_vblank(self: Rc); } pub trait PresentationListener { fn presented( self: Rc, output: &OutputNode, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32, vrr: bool, ); } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum PointerType { Seat(SeatId), TabletTool(TabletToolId), } pub async fn output_render_data(state: Rc) { loop { let output = state.pending_output_render_data.pop().await; if output.global.destroyed.get() { continue; } if output.update_render_data_scheduled.get() { output.update_render_data_scheduled.set(false); output.update_render_data_phase1().triggered().await; output.update_render_data_phase2(); } } } impl OutputNode { pub async fn before_latch(&self, present: u64) { let mut res = BeforeLatchResult::None; for listener in self.before_latch_event.iter() { res |= listener.before_latch(present); } if res == BeforeLatchResult::Yield { self.state.eng.yield_now().await; } } pub fn latched(&self, tearing: bool) { self.schedule.latched(); for listener in self.latch_event.iter() { listener.after_latch(self, tearing); } } pub fn vblank(&self) { for listener in self.vblank_event.iter() { listener.after_vblank(); } if self.global.connector.needs_vblank_emulation.get() { if self.vblank_event.has_listeners() { self.global.connector.damage(); } else { let connector = self.global.connector.clone(); self.vblank_event.on_attach(Box::new(move || { connector.damage(); })); } } } pub fn presented( &self, tv_sec: u64, tv_nsec: u32, refresh: u32, seq: u64, flags: u32, vrr: bool, locked: bool, ) { for listener in self.presentation_event.iter() { listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr); } if locked && let Some(lock) = self.state.lock.lock.get() { lock.check_locked() } let tearing = flags.not_contains(KIND_VSYNC); if self.tearing.replace(tearing) != tearing { self.global .connector .head_managers .handle_tearing_active_change(tearing); self.state.trigger_cci(CCI_OUTPUTS); } } pub fn update_exclusive_zones(self: &Rc) { let mut exclusive = ExclusiveSize::default(); for layer in &self.layers { for surface in layer.iter() { exclusive = exclusive.max(&surface.exclusive_size()); } } if self.exclusive_zones.replace(exclusive) != exclusive { self.update_rects(); for layer in &self.layers { for surface in layer.iter() { surface.exclusive_zones_changed(); } } if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect.get()); } if self.node_visible() { self.state.damage(self.global.pos.get()); } } } pub fn add_screencast(&self, sc: &Rc) { self.screencasts.set((sc.client.id, sc.id), sc.clone()); self.screencast_changed(); } pub fn remove_screencast(&self, sc: &JayScreencast) { self.screencasts.remove(&(sc.client.id, sc.id)); self.screencast_changed(); } pub fn screencast_changed(&self) { for ws in self.workspaces.iter() { ws.update_has_captures(); } } pub fn perform_screencopies( &self, tex: &Rc, cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, render_hardware_cursor: bool, x_off: i32, y_off: i32, size: Option<(i32, i32)>, ) { if let Some(workspace) = self.workspace.get() { if !workspace.may_capture.get() { return; } } self.perform_wlr_screencopies( tex, cd, resv, acquire_sync, release_sync, render_hardware_cursor, x_off, y_off, size, ); for sc in self.screencasts.lock().values() { sc.copy_texture( self, tex, cd, resv, acquire_sync, release_sync, render_hardware_cursor, x_off, y_off, size, ); } for sc in self.ext_copy_sessions.lock().values() { sc.copy_texture( self, tex, cd, resv, acquire_sync, release_sync, render_hardware_cursor, x_off, y_off, size, ); } } pub fn perform_wlr_screencopies( &self, tex: &Rc, cd: &Rc, resv: Option<&Rc>, acquire_sync: &AcquireSync, release_sync: ReleaseSync, render_hardware_cursors: bool, x_off: i32, y_off: i32, size: Option<(i32, i32)>, ) { if self.screencopies.is_empty() { return; } let now = self.state.now(); for capture in self.screencopies.lock().drain_values() { let wl_buffer = match capture.buffer.take() { Some(b) => b, _ => { log::warn!("Capture frame is pending but has no buffer attached"); capture.send_failed(); continue; } }; if wl_buffer.destroyed() { capture.send_failed(); continue; } let mut ready = true; if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { match storage { WlBufferStorage::Shm { mem, stride, .. } => { let res = self.state.perform_shm_screencopy( tex, cd, acquire_sync, self.global.pos.get(), x_off, y_off, size, &capture, mem, *stride, wl_buffer.format, self.global.persistent.transform.get(), self.global.persistent.scale.get(), ); match res { Ok(p) => { ready = p.is_none(); capture.pending.set(p); } Err(e) => { log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); capture.send_failed(); continue; } } } WlBufferStorage::Dmabuf { fb, .. } => { let fb = match fb { Some(fb) => fb, _ => { log::warn!("Capture buffer has no framebuffer"); capture.send_failed(); continue; } }; let res = self.state.perform_screencopy( tex, resv, acquire_sync, release_sync, cd, &fb, AcquireSync::Implicit, ReleaseSync::Implicit, self.global.persistent.transform.get(), self.state.color_manager.srgb_gamma22(), self.global.pos.get(), render_hardware_cursors, x_off - capture.rect.x1(), y_off - capture.rect.y1(), size, self.global.persistent.transform.get(), self.global.persistent.scale.get(), ); if let Err(e) = res { log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); capture.send_failed(); continue; } } } } if capture.with_damage.get() { capture.send_damage(); } if ready { capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); } } self.screencast_changed(); } pub fn clear(&self) { self.global.clear(); self.workspace.set(None); let workspaces: Vec<_> = self.workspaces.iter().collect(); for workspace in workspaces { workspace.clear(); } self.lock_surface.take(); self.jay_outputs.clear(); self.screencasts.clear(); self.screencopies.clear(); self.ext_copy_sessions.clear(); self.ext_workspace_groups.clear(); self.latch_event.clear(); self.vblank_event.clear(); self.presentation_event.clear(); self.render_data.borrow_mut().clear(); } pub fn on_spaces_changed(self: &Rc) { self.update_rects(); if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect.get()); } for item in self.tray_items.iter() { item.send_current_configure(); } } pub fn on_colors_changed(self: &Rc) { self.schedule_update_render_data(); } pub fn set_preferred_scale(self: &Rc, scale: Scale) { let old_scale = self.global.persistent.scale.replace(scale); if scale == old_scale { return; } let legacy_scale = scale.round_up(); if self.global.legacy_scale.replace(legacy_scale) != legacy_scale { self.global.send_mode(); } self.state.remove_output_scale(old_scale); self.state.add_output_scale(scale); let rect = self.calculate_extents(); self.change_extents_(&rect); self.visit_children(&mut SurfaceSendPreferredScaleVisitor); self.schedule_update_render_data(); self.global .connector .head_managers .handle_scale_change(scale); self.state.trigger_cci(CCI_OUTPUTS); for head in self.global.connector.wlr_output_heads.lock().values() { head.handle_new_scale(scale); } } pub fn schedule_update_render_data(self: &Rc) { if !self.update_render_data_scheduled.replace(true) { self.state.pending_output_render_data.push(self.clone()); } } fn update_render_data_phase1(self: &Rc) -> Rc { let on_completed = Rc::new(OnDropEvent::default()); if !self.state.show_bar.get() { return on_completed.event(); } let Some(ctx) = self.state.render_ctx.get() else { return on_completed.event(); }; let font = self.state.theme.bar_font(); let theme = &self.state.theme; let bh = theme.sizes.bar_height(); let scale = self.global.persistent.scale.get(); let scale = if scale != 1 { Some(scale.to_f64()) } else { None }; let mut texture_height = bh; if let Some(scale) = scale { texture_height = (bh as f64 * scale).round() as _; } let active_id = self.workspace.get().map(|w| w.id); for ws in self.workspaces.iter() { let tex = &mut *ws.title_texture.borrow_mut(); let tex = tex.get_or_insert_with(|| TextTexture::new(&self.state, &ctx)); let tc = match active_id == Some(ws.id) { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), }; tex.schedule_render_fitting( on_completed.clone(), Some(texture_height), &font, &ws.name, tc, false, scale, ); } let mut rd = self.render_data.borrow_mut(); let tex = rd.status.get_or_insert_with(|| OutputStatus { tex_x: 0, tex: TextTexture::new(&self.state, &ctx), }); let status = self.status.get(); let tc = self.state.theme.colors.bar_text.get(); tex.tex.schedule_render_fitting( on_completed.clone(), Some(texture_height), &font, &status, tc, true, scale, ); on_completed.event() } fn update_render_data_phase2(&self) { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); rd.inactive_workspaces.clear(); rd.attention_requested_workspaces.clear(); rd.captured_inactive_workspaces.clear(); rd.active_workspace = None; if !self.state.show_bar.get() { self.state.damage(rd.full_area); return; } let mut pos = 0; let bar_rect_rel = self.bar_rect_rel.get(); let non_exclusive_rect_rel = self.non_exclusive_rect_rel.get(); let y1 = bar_rect_rel.y1() - non_exclusive_rect_rel.y1(); let scale = self.global.persistent.scale.get(); let scale = if scale != 1 { Some(scale.to_f64()) } else { None }; let active_id = self.workspace.get().map(|w| w.id); rd.bar_separator = self .bar_separator_rect_rel .get() .move_(-non_exclusive_rect_rel.x1(), -non_exclusive_rect_rel.y1()); for ws in self.workspaces.iter() { let mut title_width = bar_rect_rel.height(); let title = &*ws.title_texture.borrow(); if let Some(title) = title { if let Err(e) = title.flip() { log::error!("Could not render title: {}", ErrorFmt(e)); } if let Some(texture) = title.texture() { let mut x = pos + 1; let (mut width, _) = texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } if width + 2 > title_width { title_width = width + 2; } else { x = pos + (title_width - width) / 2; } rd.titles.push(OutputTitle { x1: pos, x2: pos + title_width, tex_x: x, tex_y: y1, tex: texture, ws: ws.deref().clone(), }); } } let rect = Rect::new_sized_saturating(pos, y1, title_width, bar_rect_rel.height()); if Some(ws.id) == active_id { rd.active_workspace = Some(OutputWorkspaceRenderData { rect, captured: ws.has_capture.get(), }); } else { if ws.attention_requests.active() { rd.attention_requested_workspaces.push(rect); } if ws.has_capture.get() { rd.captured_inactive_workspaces.push(rect); } else { rd.inactive_workspaces.push(rect); } } pos += title_width; } if let Some(status) = &mut rd.status { if let Err(e) = status.tex.flip() { log::error!("Could not render status: {}", ErrorFmt(e)); } if let Some(texture) = status.tex.texture() { let (mut width, _) = texture.size(); if let Some(scale) = scale { width = (width as f64 / scale).round() as _; } let pos = self.tray_start_rel.get() - width - 1; status.tex_x = pos; } } let old_full_area = rd.full_area; rd.full_area = self.bar_rect_with_separator.get(); if self.title_visible.get() { self.state.damage(rd.full_area.union(old_full_area)); } } pub fn ensure_workspace(self: &Rc) -> Rc { if let Some(ws) = self.workspace.get() { if !ws.is_dummy { return ws; } } self.generate_workspace() } pub fn generate_workspace(self: &Rc) -> Rc { let name = 'name: { for i in 1.. { let name = i.to_string(); if !self.state.workspaces.contains(&name) { break 'name name; } } unreachable!(); }; self.create_workspace(&name) } pub fn show_workspace(&self, ws: &Rc) -> bool { let mut seats = SmallVec::new(); if let Some(old) = self.workspace.set(Some(ws.clone())) { if old.id == ws.id { return false; } collect_kb_foci2(old.clone(), &mut seats); for pinned in self.pinned.iter() { pinned.deref().clone().set_workspace(ws, false); } if old.is_empty() { for jw in old.jay_workspaces.lock().values() { jw.send_destroyed(); jw.workspace.set(None); } for wh in old.ext_workspaces.lock().values() { wh.handle_destroyed(); } old.clear(); self.state.workspaces.remove(&old.name); } else { old.set_visible(false); old.flush_jay_workspaces(); } } self.update_visible(); self.update_presentation_type(); if let Some(fs) = ws.fullscreen.get() { fs.tl_change_extents(&self.global.pos.get()); } ws.change_extents(&self.workspace_rect.get()); for seat in seats { ws.clone().node_do_focus(&seat, Direction::Unspecified); } if self.node_visible() { self.state.damage(self.global.pos.get()); } true } pub fn find_workspace_insertion_point(&self, name: &str) -> Option>> { if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { for existing_ws in self.workspaces.iter() { if cmp(name, &existing_ws.name) == std::cmp::Ordering::Less { return Some(existing_ws); } } } None } pub fn create_workspace(self: &Rc, name: &str) -> Rc { let ws = WorkspaceNode::new(self, name, false); ws.opt.set(Some(ws.clone())); ws.update_has_captures(); let link = if let Some(before) = self.find_workspace_insertion_point(name) { before.prepend(ws.clone()) } else { self.workspaces.add_last(ws.clone()) }; *ws.output_link.borrow_mut() = Some(link); self.state.workspaces.set(name.to_string(), ws.clone()); if self.workspace.is_none() { self.show_workspace(&ws); } let mut clients_to_kill = AHashMap::new(); for watcher in self.state.workspace_watchers.lock().values() { if let Err(e) = watcher.send_workspace(&ws) { clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); } } for (client, e) in clients_to_kill.values() { client.error(e); } self.state.workspace_managers.announce_workspace(self, &ws); self.schedule_update_render_data(); ws } pub fn update_rects(self: &Rc) { let rect = self.global.pos.get(); let bh = self.state.theme.sizes.bar_height(); let bsw = self.state.theme.sizes.bar_separator_width(); let exclusive = self.exclusive_zones.get(); let y1 = rect.y1() + exclusive.top; let x2 = rect.x2() - exclusive.right; let y2 = rect.y2() - exclusive.bottom; let x1 = rect.x1() + exclusive.left; let width = (x2 - x1).max(0); let height = (y2 - y1).max(0); let non_exclusive_rect = Rect::new_sized_saturating(x1, y1, width, height); let non_exclusive_rect_rel = Rect::new_sized_saturating(exclusive.left, exclusive.top, width, height); let mut bar_rect = Rect::default(); let mut bar_rect_rel = Rect::default(); let mut bar_rect_with_separator = Rect::default(); let mut bar_rect_with_separator_rel = Rect::default(); let mut bar_separator_rect = Rect::default(); let mut bar_separator_rect_rel = Rect::default(); let mut workspace_rect = non_exclusive_rect; let mut workspace_rect_rel = non_exclusive_rect_rel; if self.state.show_bar.get() { match self.state.theme.bar_position.get() { BarPosition::Bottom => { workspace_rect = Rect::new_sized_saturating(x1, y1, width, height - bh - bsw); bar_rect_with_separator = Rect::new_sized_saturating(x1, y1 + height - bh - bsw, width, bh + bsw); bar_separator_rect = Rect::new_sized_saturating(x1, y1 + height - bh - bsw, width, bsw); bar_rect = Rect::new_sized_saturating(x1, y1 + height - bh, width, bh); } BarPosition::Top => { bar_rect = Rect::new_sized_saturating(x1, y1, width, bh); bar_separator_rect = Rect::new_sized_saturating(x1, y1 + bh, width, bsw); bar_rect_with_separator = Rect::new_sized_saturating(x1, y1, width, bh + bsw); workspace_rect = Rect::new_sized_saturating(x1, y1 + bh + bsw, width, height - bh - bsw); } } let to_rel = |r: Rect| r.move_(-rect.x1(), -rect.y1()); bar_rect_rel = to_rel(bar_rect); bar_rect_with_separator_rel = to_rel(bar_rect_with_separator); bar_separator_rect_rel = to_rel(bar_separator_rect); workspace_rect_rel = to_rel(workspace_rect); } self.non_exclusive_rect.set(non_exclusive_rect); self.non_exclusive_rect_rel.set(non_exclusive_rect_rel); self.bar_rect.set(bar_rect); self.bar_rect_rel.set(bar_rect_rel); self.bar_rect_with_separator.set(bar_rect_with_separator); self.bar_rect_with_separator_rel .set(bar_rect_with_separator_rel); self.bar_separator_rect.set(bar_separator_rect); self.bar_separator_rect_rel.set(bar_separator_rect_rel); self.workspace_rect.set(workspace_rect); self.workspace_rect_rel.set(workspace_rect_rel); self.update_tray_positions(); self.schedule_update_render_data(); } pub fn set_position(self: &Rc, x: i32, y: i32) { let pos = self.global.pos.get(); if (pos.x1(), pos.y1()) == (x, y) { return; } let rect = pos.at_point(x, y); self.change_extents_(&rect); for head in self.global.connector.wlr_output_heads.lock().values() { head.handle_position_change(x, y); } } pub fn update_mode(self: &Rc, mode: Mode) { self.update_mode_and_transform(mode, self.global.persistent.transform.get()); } pub fn update_transform(self: &Rc, transform: Transform) { self.update_mode_and_transform(self.global.mode.get(), transform); } pub fn update_mode_and_transform(self: &Rc, mode: Mode, transform: Transform) { let old_mode = self.global.mode.get(); let old_transform = self.global.persistent.transform.get(); if (old_mode, old_transform) == (mode, transform) { return; } let (old_width, old_height) = self.global.pixel_size(); self.global.mode.set(mode); self.global.refresh_nsec.set(mode.refresh_nsec()); self.global.persistent.transform.set(transform); let (new_width, new_height) = self.global.pixel_size(); self.change_extents_(&self.calculate_extents()); if (old_width, old_height) != (new_width, new_height) { for sc in self.screencasts.lock().values() { sc.schedule_realloc_or_reconfigure(); } for sc in self.ext_copy_sessions.lock().values() { sc.buffer_size_changed(); } } if transform != old_transform { self.state.refresh_hardware_cursors(); self.node_visit_children(&mut SurfaceSendPreferredTransformVisitor); self.global .connector .head_managers .handle_transform_change(transform); self.state.trigger_cci(CCI_OUTPUTS); for head in self.global.connector.wlr_output_heads.lock().values() { head.hande_transform_change(transform); } } } fn calculate_extents(&self) -> Rect { Self::calculate_extents_( self.global.mode.get(), self.global.persistent.transform.get(), self.global.persistent.scale.get(), self.global.pos.get().position(), ) } pub fn calculate_extents_( mode: Mode, transform: Transform, scale: Scale, pos: (i32, i32), ) -> Rect { let (width, height) = calculate_logical_size((mode.width, mode.height), transform, scale); Rect::new_sized_saturating(pos.0, pos.1, width, height) } fn change_extents_(self: &Rc, rect: &Rect) { let visible = self.node_visible(); if visible { let old_pos = self.global.pos.get(); self.state.damage(old_pos); } self.global.persistent.pos.set((rect.x1(), rect.y1())); self.global.pos.set(*rect); self.global.update_damage_matrix(); if visible { self.state.damage(*rect); } self.state.output_extents_changed(); self.update_rects(); if let Some(ls) = self.lock_surface.get() { ls.change_extents(*rect); } if let Some(c) = self.workspace.get() { if let Some(fs) = c.fullscreen.get() { fs.tl_change_extents(rect); } c.change_extents(&self.workspace_rect.get()); } for layer in &self.layers { for surface in layer.iter() { surface.output_resized(); } } self.global.send_mode(); for seat in self.state.globals.seats.lock().values() { seat.cursor_group().output_pos_changed(self) } self.state.tree_changed(); self.global .connector .head_managers .handle_position_size_change(self); self.state.trigger_cci(CCI_OUTPUTS); } pub fn update_state(self: &Rc, old: BackendConnectorState, state: BackendConnectorState) { self.update_btf_and_bcs(state.eotf, state.color_space); if old.vrr != state.vrr { self.schedule.set_vrr_enabled(state.vrr); } if old.mode != state.mode { self.update_mode(state.mode); } self.global.format.set(state.format); } fn update_btf_and_bcs(&self, btf: BackendEotfs, bcs: BackendColorSpace) { let old_btf = self.global.btf.replace(btf); let old_bcs = self.global.bcs.replace(bcs); if (old_btf, old_bcs) == (btf, bcs) { return; } self.update_color_description(); } fn update_color_description(&self) { if self.global.update_color_description() { self.state.damage(self.global.position()); if let Some(hc) = self.hardware_cursor.get() { self.hardware_cursor_needs_render.set(true); hc.damage(); } for fb in self.global.color_description_listeners.lock().values() { fb.send_image_description_changed(); } self.visit_children(&mut SurfaceSendPreferredColorDescription); } } fn visit_children(&self, visitor: &mut dyn NodeVisitor) { self.node_visit_children(visitor); for stacked in self.state.root.stacked.iter() { if stacked.node_output_id() != Some(self.id) { continue; } stacked.deref().clone().node_visit(visitor); } } pub fn set_brightness(&self, brightness: Option) { let old = self.global.persistent.brightness.replace(brightness); if old != brightness { self.update_color_description(); self.global .connector .head_managers .handle_brightness_change(brightness); self.state.trigger_cci(CCI_OUTPUTS); } } pub fn set_use_native_gamut(&self, use_native_gamut: bool) { let old = self .global .persistent .use_native_gamut .replace(use_native_gamut); if old != use_native_gamut { self.update_color_description(); self.global .connector .head_managers .handle_use_native_gamut_change(use_native_gamut); self.state.trigger_cci(CCI_OUTPUTS); } } pub fn set_blend_space(&self, blend_space: BlendSpace) { let old = self.global.persistent.blend_space.replace(blend_space); if old != blend_space { self.state.damage(self.global.position()); self.global .connector .head_managers .handle_blend_space_change(blend_space); self.state.trigger_cci(CCI_OUTPUTS); } } fn find_stacked_at( &self, stack: &LinkedList>, x: i32, y: i32, tree: &mut Vec, usecase: FindTreeUsecase, ) -> FindTreeResult { if stack.is_empty() { return FindTreeResult::Other; } let (x_abs, y_abs) = self.global.pos.get().translate_inv(x, y); for stacked in stack.rev_iter() { let ext = stacked.node_absolute_position(); if !stacked.node_visible() { continue; } if stacked.stacked_absolute_position_constrains_input() && !ext.contains(x_abs, y_abs) { // TODO: make constrain always true continue; } let (x, y) = ext.translate(x_abs, y_abs); let idx = tree.len(); tree.push(FoundNode { node: stacked.deref().clone(), x, y, }); match stacked.node_find_tree_at(x, y, tree, usecase) { FindTreeResult::AcceptsInput => { return FindTreeResult::AcceptsInput; } FindTreeResult::Other => { tree.truncate(idx); } } } FindTreeResult::Other } pub fn find_layer_surface_at( &self, x: i32, y: i32, layers: &[u32], tree: &mut Vec, usecase: FindTreeUsecase, ) -> FindTreeResult { match usecase { FindTreeUsecase::None => {} FindTreeUsecase::SelectToplevel => return FindTreeResult::Other, FindTreeUsecase::SelectToplevelOrPopup => return FindTreeResult::Other, FindTreeUsecase::SelectWorkspace => return FindTreeResult::Other, } let len = tree.len(); for layer in layers.iter().copied() { for surface in self.layers[layer as usize].rev_iter() { let pos = surface.output_extents(); if pos.contains(x, y) { let (x, y) = pos.translate(x, y); if surface.node_find_tree_at(x, y, tree, usecase) == FindTreeResult::AcceptsInput { return FindTreeResult::AcceptsInput; } tree.truncate(len); } } } FindTreeResult::Other } pub fn set_status(self: &Rc, status: &Rc) { self.status.set(status.clone()); self.schedule_update_render_data(); } fn pointer_move(self: &Rc, id: PointerType, x: Fixed, y: Fixed) { self.pointer_positions .set(id, (x.round_down(), y.round_down())); } pub fn has_fullscreen(&self) -> bool { self.workspace .get() .map(|w| w.fullscreen.is_some()) .unwrap_or(false) } pub fn set_lock_surface( &self, surface: Option>, ) -> Option> { let prev = self.lock_surface.set(surface); self.update_visible(); prev } pub fn fullscreen_changed(&self) { self.update_visible(); if self.node_visible() { self.state.damage(self.global.pos.get()); } } pub fn handle_workspace_display_order_update(self: &Rc) { if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { let mut workspaces: Vec<_> = self.workspaces.iter().collect(); workspaces.sort_by(|a, b| cmp(&a.name, &b.name)); for ws_ref in workspaces { ws_ref.detach(); self.workspaces.add_last_existing(&ws_ref); } } self.schedule_update_render_data(); } pub fn update_visible(&self) { let mut visible = self.state.root_visible(); if self.state.lock.locked.get() { if let Some(surface) = self.lock_surface.get() { surface.set_visible(visible); } visible = false; } macro_rules! set_layer_visible { ($layer:expr, $visible:expr) => { for ls in $layer.iter() { ls.set_visible($visible); } }; } let mut have_fullscreen = false; if let Some(ws) = self.workspace.get() { have_fullscreen = ws.fullscreen.is_some(); } let lower_visible = visible && !have_fullscreen; self.title_visible.set(lower_visible); set_layer_visible!(self.layers[0], lower_visible); set_layer_visible!(self.layers[1], lower_visible); set_layer_visible!(self.layers[2], lower_visible); for item in self.tray_items.iter() { item.set_visible(lower_visible); } if let Some(ws) = self.workspace.get() { ws.set_visible(visible); } set_layer_visible!(self.layers[3], visible); } fn bar_button(self: &Rc, seat: &Rc, x: i32, y: i32) -> bool { if !self.state.show_bar.get() { return false; } let bar_rect_rel = self.bar_rect_rel.get(); if bar_rect_rel.not_contains(x, y) { return false; } let (x, _) = bar_rect_rel.translate(x, y); let ws = 'ws: { let rd = self.render_data.borrow_mut(); for title in &rd.titles { if x >= title.x1 && x < title.x2 { break 'ws title.ws.clone(); } } return true; }; self.state.show_workspace2(Some(seat), self, &ws); true } fn button(self: Rc, seat: &Rc, id: PointerType) { let (x, y) = match self.pointer_positions.get(&id) { Some(p) => p, _ => return, }; if let PointerType::Seat(s) = id { self.pointer_down.set(s, (x, y)); } if self.bar_button(seat, x, y) { return; } let bar_rect_with_separator_rel = self.bar_rect_with_separator_rel.get(); if bar_rect_with_separator_rel.contains(x, y) { return; } let ws = self.ensure_workspace(); seat.focus_node(ws); } pub fn update_presentation_type(&self) { self.update_vrr_state(); self.update_tearing(); } fn update_vrr_state(&self) { let enabled = match self.global.persistent.vrr_mode.get() { VrrMode::Never => false, VrrMode::Always => true, VrrMode::Fullscreen { surface } => 'get: { let Some(ws) = self.workspace.get() else { break 'get false; }; let Some(tl) = ws.fullscreen.get() else { break 'get false; }; if let Some(req) = surface { let Some(surface) = tl.tl_scanout_surface() else { break 'get false; }; if let Some(req) = req.content_type { let Some(content_type) = surface.content_type.get() else { break 'get false; }; match content_type { ContentType::Photo if !req.photo => break 'get false, ContentType::Video if !req.video => break 'get false, ContentType::Game if !req.game => break 'get false, _ => {} } } } true } }; let res = self .global .connector .modify_state(&self.state, |s| s.vrr = enabled); if let Err(e) = res { log::error!("Could not set vrr mode: {}", e); } } fn update_tearing(&self) { let enabled = match self.global.persistent.tearing_mode.get() { TearingMode::Never => false, TearingMode::Always => true, TearingMode::Fullscreen { surface } => 'get: { let Some(ws) = self.workspace.get() else { break 'get false; }; let Some(tl) = ws.fullscreen.get() else { break 'get false; }; if let Some(req) = surface { let Some(surface) = tl.tl_scanout_surface() else { break 'get false; }; if req.tearing_requested { if !surface.tearing.get() { break 'get false; } } } true } }; let res = self .global .connector .modify_state(&self.state, |s| s.tearing = enabled); if let Err(e) = res { log::error!("Could not set tearing mode: {}", e); } } pub fn tile_drag_destination( self: &Rc, source: NodeId, x_abs: i32, y_abs: i32, ) -> Option { if self.state.lock.locked.get() { return None; } for stacked in self.state.root.stacked.rev_iter() { let Some(float) = stacked.deref().clone().node_into_float() else { continue; }; if !float.node_visible() { continue; } let pos = float.node_absolute_position(); if !pos.contains(x_abs, y_abs) { continue; } return float.tile_drag_destination(source, x_abs, y_abs); } let rect = self.non_exclusive_rect.get(); if !rect.contains(x_abs, y_abs) { return None; } let Some(ws) = self.workspace.get() else { return Some(TileDragDestination { highlight: rect, ty: TddType::NewWorkspace { output: self.clone(), }, }); }; if ws.fullscreen.is_some() { return None; } let bar_rect_with_separator = self.bar_rect_with_separator.get(); if bar_rect_with_separator.contains(x_abs, y_abs) { let rd = &*self.render_data.borrow(); let bar_rect = self.bar_rect.get(); let (x, _) = bar_rect.translate(x_abs, y_abs); let mut last_x2 = 0; for t in &rd.titles { if x < t.x2 { return Some(TileDragDestination { highlight: Rect::new_sized( bar_rect.x1() + t.x1, bar_rect.y1(), t.x2 - t.x1, bar_rect.height(), )?, ty: TddType::MoveToWorkspace { workspace: t.ws.clone(), }, }); } last_x2 = t.x2; } return Some(TileDragDestination { highlight: Rect::new_sized( bar_rect.x1() + last_x2, bar_rect.y1(), bar_rect.width() - last_x2, bar_rect.height(), )?, ty: TddType::MoveToNewWorkspace { output: self.clone(), }, }); } let rect = self.workspace_rect.get(); if !rect.contains(x_abs, y_abs) { return None; } let Some(c) = ws.container.get() else { return Some(TileDragDestination { highlight: rect, ty: TddType::NewContainer { workspace: ws }, }); }; c.tile_drag_destination(source, rect, x_abs, y_abs) } pub fn workspace_drag_destination( self: &Rc, source: WorkspaceNodeId, x_abs: i32, y_abs: i32, ) -> Option { if !self.state.show_bar.get() { return None; } let bar_rect_with_separator = self.bar_rect_with_separator.get(); if bar_rect_with_separator.not_contains(x_abs, y_abs) { return None; } let bar_rect = self.bar_rect.get(); if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { if self.workspaces.iter().any(|ws| ws.id == source) { return None; } return Some(WorkspaceDragDestination { highlight: bar_rect, output: self.clone(), before: None, }); } let rd = &*self.render_data.borrow(); let (x, _) = bar_rect.translate(x_abs, y_abs); let mut prev_is_source = false; let mut prev_center = 0; for t in &rd.titles { if t.ws.id == source { prev_is_source = true; continue; } let center = (t.x1 + t.x2) / 2; if x < center { return if prev_is_source { None } else { Some(WorkspaceDragDestination { highlight: Rect::new_sized( bar_rect.x1() + prev_center, bar_rect.y1(), center - prev_center, bar_rect.height(), )?, output: self.clone(), before: Some(t.ws.clone()), }) }; } prev_center = center; prev_is_source = false; } if prev_is_source { return None; } return Some(WorkspaceDragDestination { highlight: Rect::new_sized( bar_rect.x1() + prev_center, bar_rect.y1(), bar_rect.width() - prev_center, bar_rect.height(), )?, output: self.clone(), before: None, }); } pub fn update_tray_positions(self: &Rc) { let bar_rect = self.bar_rect.get(); let mut right = bar_rect.width(); let mut have_any = false; let icon_size = self.state.tray_icon_size(); for item in self.tray_items.rev_iter() { if item.data().surface.buffer.is_none() { continue; } have_any = true; right -= bar_rect.height(); let rel_pos = Rect::new_sized_saturating(right, 1, icon_size, icon_size); let abs_pos = rel_pos.move_(bar_rect.x1(), bar_rect.y1()); item.set_position(abs_pos, rel_pos); } if have_any { right -= 2; } let prev_right = self.tray_start_rel.replace(right); if prev_right != right { { let min = prev_right.min(right); let rect = Rect::new_saturating( bar_rect.x1() + min, bar_rect.y1(), bar_rect.x2(), bar_rect.y2(), ); self.state.damage(rect); } self.schedule_update_render_data(); } self.state.tree_changed(); } pub fn set_vrr_mode(&self, mode: &VrrMode) { let old = self.global.persistent.vrr_mode.replace(*mode); if old != *mode { self.update_presentation_type(); self.global .connector .head_managers .handle_vrr_mode_change(mode); self.state.trigger_cci(CCI_OUTPUTS); for head in self.global.connector.wlr_output_heads.lock().values() { head.handle_vrr_mode_change(mode); } } } pub fn set_tearing_mode(&self, mode: &TearingMode) { let old = self.global.persistent.tearing_mode.replace(*mode); if old != *mode { self.update_presentation_type(); self.global .connector .head_managers .handle_tearing_mode_change(mode); self.state.trigger_cci(CCI_OUTPUTS); } } pub fn set_hardware_cursor(&self, hc: Option>) { let is_none = hc.is_none(); let old = self.hardware_cursor.set(hc); let was_none = old.is_none(); if was_none != is_none { if is_none { self.state.outputs_without_hc.fetch_add(1); } else { self.state.outputs_without_hc.fetch_sub(1); } } } pub fn take_keyboard_navigation_focus(&self, seat: &Rc, direction: Direction) { let Some(ws) = self.workspace.get() else { return; }; if let Some(fs) = ws.fullscreen.get() { if fs.node_visible() { fs.node_do_focus(seat, direction); } } else if let Some(c) = ws.container.get() { if c.node_visible() { c.node_do_focus(seat, direction); } } else { if ws.node_visible() { seat.focus_node(ws); } } } pub fn set_gamma_lut( &self, gamma_lut: Option>, ) -> Result<(), BackendConnectorTransactionError> { self.global .connector .modify_state(&self.state, |s| s.gamma_lut = gamma_lut) .inspect_err(|e| { log::error!("Could not set gamma_lut: {}", ErrorFmt(e)); }) } pub fn set_flip_margin(&self, margin_ns: u64) { self.flip_margin_ns.set(Some(margin_ns)); self.state.trigger_cci(CCI_OUTPUTS); } } pub struct OutputTitle { pub x1: i32, pub x2: i32, pub tex_x: i32, pub tex_y: i32, pub tex: Rc, pub ws: Rc, } pub struct OutputStatus { pub tex_x: i32, pub tex: TextTexture, } #[derive(Copy, Clone)] pub struct OutputWorkspaceRenderData { pub rect: Rect, pub captured: bool, } #[derive(Default)] pub struct OutputRenderData { pub full_area: Rect, pub active_workspace: Option, pub bar_separator: Rect, pub inactive_workspaces: Vec, pub attention_requested_workspaces: Vec, pub captured_inactive_workspaces: Vec, pub titles: Vec, pub status: Option, } impl OutputRenderData { fn clear(&mut self) { self.titles.clear(); self.status.take(); } } impl Debug for OutputNode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("OutputNode").finish_non_exhaustive() } } impl Node for OutputNode { fn node_id(&self) -> NodeId { self.id.into() } fn node_seat_state(&self) -> &NodeSeatState { &self.seat_state } fn node_visit(self: Rc, visitor: &mut dyn NodeVisitor) { visitor.visit_output(&self); } fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { if let Some(ls) = self.lock_surface.get() { visitor.visit_lock_surface(&ls); } for ws in self.workspaces.iter() { visitor.visit_workspace(ws.deref()); } for layers in &self.layers { for surface in layers.iter() { visitor.visit_layer_surface(surface.deref()); } } for item in self.tray_items.iter() { item.deref().clone().node_visit(visitor); } } fn node_visible(&self) -> bool { self.state.root_visible() } fn node_absolute_position(&self) -> Rect { self.global.pos.get() } fn node_output(&self) -> Option> { self.global.opt.node() } fn node_location(&self) -> Option { Some(NodeLocation::Output(self.id)) } fn node_layer(&self) -> NodeLayerLink { NodeLayerLink::Output } fn node_do_focus(self: Rc, seat: &Rc, direction: Direction) { if self.state.lock.locked.get() { if let Some(lock) = self.lock_surface.get() { seat.focus_node(lock.surface.clone()); } return; } if let Some(ws) = self.workspace.get() { ws.node_do_focus(seat, direction); } } fn node_find_tree_at( &self, x: i32, y: i32, tree: &mut Vec, usecase: FindTreeUsecase, ) -> FindTreeResult { if self.state.lock.locked.get() { let allow_surface = match usecase { FindTreeUsecase::None => true, FindTreeUsecase::SelectToplevel => false, FindTreeUsecase::SelectToplevelOrPopup => false, FindTreeUsecase::SelectWorkspace => false, }; if allow_surface && let Some(ls) = self.lock_surface.get() { tree.push(FoundNode { node: ls.clone(), x, y, }); return ls.node_find_tree_at(x, y, tree, usecase); } return FindTreeResult::AcceptsInput; } let ws_rect_rel = self.workspace_rect_rel.get(); let select_workspace = match usecase { FindTreeUsecase::None => false, FindTreeUsecase::SelectToplevel => false, FindTreeUsecase::SelectToplevelOrPopup => false, FindTreeUsecase::SelectWorkspace => true, }; if select_workspace && ws_rect_rel.contains(x, y) { let (x, y) = ws_rect_rel.translate(x, y); if let Some(ws) = self.workspace.get() { tree.push(FoundNode { node: ws.clone(), x, y, }); return FindTreeResult::AcceptsInput; } } { let res = self.find_stacked_at(&self.state.root.stacked_above_layers, x, y, tree, usecase); if res.accepts_input() { return res; } } let mut fullscreen = None; if let Some(ws) = self.workspace.get() { fullscreen = ws.fullscreen.get(); } { let mut layers = &[OVERLAY, TOP][..]; if fullscreen.is_some() { layers = &[OVERLAY]; } let res = self.find_layer_surface_at(x, y, layers, tree, usecase); if res.accepts_input() { return res; } } { let res = self.find_stacked_at(&self.state.root.stacked, x, y, tree, usecase); if res.accepts_input() { return res; } } if let Some(fs) = fullscreen { tree.push(FoundNode { node: fs.clone(), x, y, }); fs.node_find_tree_at(x, y, tree, usecase) } else { let mut search_layers = true; let bar_rect_rel = self.bar_rect_rel.get(); if bar_rect_rel.contains(x, y) { let (x, y) = bar_rect_rel.translate(x, y); search_layers = false; for item in self.tray_items.iter() { let data = item.data(); let pos = data.rel_pos.get(); if pos.contains(x, y) { let (x, y) = pos.translate(x, y); tree.push(FoundNode { node: item.deref().clone(), x, y, }); return data.find_tree_at(x, y, tree); } } } else if ws_rect_rel.contains(x, y) && let Some(ws) = self.workspace.get() { let (x, y) = ws_rect_rel.translate(x, y); let len = tree.len(); tree.push(FoundNode { node: ws.clone(), x, y, }); match ws.node_find_tree_at(x, y, tree, usecase) { FindTreeResult::AcceptsInput => search_layers = false, FindTreeResult::Other => { tree.truncate(len); } } } if search_layers { self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree, usecase); } FindTreeResult::AcceptsInput } } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) { renderer.render_output(self, x, y); } fn node_on_button( self: Rc, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState, _serial: u64, ) { if button != BTN_LEFT { return; } if state != ButtonState::Pressed { self.pointer_down.remove(&seat.id()); return; } self.button(seat, PointerType::Seat(seat.id())); } fn node_on_axis_event(self: Rc, seat: &Rc, event: &PendingScroll) { let steps = match self.scroll.handle(event) { Some(e) => e, _ => return, }; if steps == 0 { return; } let ws = match self.workspace.get() { Some(ws) => ws, _ => return, }; let mut ws = 'ws: { for r in self.workspaces.iter() { if r.id == ws.id { break 'ws r; } } return; }; for _ in 0..steps.abs() { let new = if steps < 0 { ws.prev() } else { ws.next() }; ws = match new { Some(n) => n, None => break, }; } self.state.show_workspace2(Some(seat), &self, &ws); } fn node_on_leave(&self, seat: &WlSeatGlobal) { self.pointer_down.remove(&seat.id()); } fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(PointerType::Seat(seat.id()), x, y); } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("output focus"); seat.pointer_cursor().set_known(KnownCursor::Default); } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(PointerType::Seat(seat.id()), x, y); if let Some((down_x, down_y)) = self.pointer_down.get(&seat.id()) && self .state .ui_drag_threshold_reached((x.round_down(), y.round_down()), (down_x, down_y)) { let bar_rect_rel = self.bar_rect_rel.get(); if bar_rect_rel.contains(down_x, down_y) { let (down_x, _) = bar_rect_rel.translate(down_x, down_y); let rd = self.render_data.borrow_mut(); for title in &rd.titles { if down_x >= title.x1 && down_x < title.x2 { let ws = title.ws.clone(); drop(rd); seat.start_workspace_drag(&ws); break; } } } } } fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { self.pointer_positions .remove(&PointerType::TabletTool(tool.id)); } fn node_on_tablet_tool_enter( self: Rc, tool: &Rc, _time_usec: u64, x: Fixed, y: Fixed, ) { tool.cursor().set_known(KnownCursor::Default); self.pointer_move(PointerType::TabletTool(tool.id), x, y); } fn node_on_tablet_tool_apply_changes( self: Rc, tool: &Rc, _time_usec: u64, changes: Option<&TabletToolChanges>, x: Fixed, y: Fixed, ) { let id = PointerType::TabletTool(tool.id); self.pointer_move(id, x, y); if let Some(changes) = changes { if changes.down == Some(true) { self.button(tool.seat(), id); } } } } pub fn calculate_logical_size( mode: (i32, i32), transform: Transform, scale: crate::scale::Scale, ) -> (i32, i32) { let (mut width, mut height) = transform.maybe_swap(mode); if scale != 1 { let scale = scale.to_f64(); width = (width as f64 / scale).round() as _; height = (height as f64 / scale).round() as _; } (width, height) } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] pub enum VrrMode { #[default] Never, Always, Fullscreen { surface: Option, }, } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct VrrSurfaceRequirements { pub content_type: Option, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct VrrContentTypeRequirements { pub photo: bool, pub video: bool, pub game: bool, } impl Default for VrrContentTypeRequirements { fn default() -> Self { Self { photo: true, video: true, game: true, } } } impl VrrMode { pub const NEVER: &'static Self = &Self::Never; pub const ALWAYS: &'static Self = &Self::Always; pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; pub const VARIANT_2: &'static Self = &Self::Fullscreen { surface: Some(VrrSurfaceRequirements { content_type: None }), }; pub const VARIANT_3: &'static Self = &Self::Fullscreen { surface: Some(VrrSurfaceRequirements { content_type: Some(VrrContentTypeRequirements { photo: false, video: true, game: true, }), }), }; pub fn from_config(mode: ConfigVrrMode) -> Option<&'static Self> { let res = match mode { ConfigVrrMode::NEVER => Self::NEVER, ConfigVrrMode::ALWAYS => Self::ALWAYS, ConfigVrrMode::VARIANT_1 => Self::VARIANT_1, ConfigVrrMode::VARIANT_2 => Self::VARIANT_2, ConfigVrrMode::VARIANT_3 => Self::VARIANT_3, _ => return None, }; Some(res) } pub fn to_config(&self) -> ConfigVrrMode { match self { Self::NEVER => ConfigVrrMode::NEVER, Self::ALWAYS => ConfigVrrMode::ALWAYS, Self::VARIANT_1 => ConfigVrrMode::VARIANT_1, Self::VARIANT_2 => ConfigVrrMode::VARIANT_2, Self::VARIANT_3 => ConfigVrrMode::VARIANT_3, _ => { log::error!("VRR mode {self:?} has no config representation"); ConfigVrrMode::NEVER } } } } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] pub enum TearingMode { #[default] Never, Always, Fullscreen { surface: Option, }, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct TearingSurfaceRequirements { pub tearing_requested: bool, } impl Default for TearingSurfaceRequirements { fn default() -> Self { Self { tearing_requested: true, } } } impl TearingMode { pub const NEVER: &'static Self = &Self::Never; pub const ALWAYS: &'static Self = &Self::Always; pub const VARIANT_1: &'static Self = &Self::Fullscreen { surface: None }; pub const VARIANT_2: &'static Self = &Self::Fullscreen { surface: Some(TearingSurfaceRequirements { tearing_requested: false, }), }; pub const VARIANT_3: &'static Self = &Self::Fullscreen { surface: Some(TearingSurfaceRequirements { tearing_requested: true, }), }; pub fn from_config(mode: ConfigTearingMode) -> Option<&'static Self> { let res = match mode { ConfigTearingMode::NEVER => Self::NEVER, ConfigTearingMode::ALWAYS => Self::ALWAYS, ConfigTearingMode::VARIANT_1 => Self::VARIANT_1, ConfigTearingMode::VARIANT_2 => Self::VARIANT_2, ConfigTearingMode::VARIANT_3 => Self::VARIANT_3, _ => return None, }; Some(res) } pub fn to_config(&self) -> ConfigTearingMode { match self { Self::NEVER => ConfigTearingMode::NEVER, Self::ALWAYS => ConfigTearingMode::ALWAYS, Self::VARIANT_1 => ConfigTearingMode::VARIANT_1, Self::VARIANT_2 => ConfigTearingMode::VARIANT_2, Self::VARIANT_3 => ConfigTearingMode::VARIANT_3, } } }