From 3c1475041f3bc8c723fdc3f95851585ce93ce5a7 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 13 Jul 2025 14:51:25 +0200 Subject: [PATCH 01/26] output-transactions: unify state application after transactions --- src/backend/transaction.rs | 48 +++++++++++++++++++++++++++++++------- src/state.rs | 15 ++++++++---- src/tasks/connector.rs | 7 ++---- src/tasks/idle.rs | 2 +- src/tree/output.rs | 7 +----- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/backend/transaction.rs b/src/backend/transaction.rs index e118af0c..dca5ac1a 100644 --- a/src/backend/transaction.rs +++ b/src/backend/transaction.rs @@ -2,9 +2,10 @@ use { crate::{ backend::{ BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector, - ConnectorKernelId, Mode, + ConnectorId, ConnectorKernelId, Mode, }, backends::metal::MetalError, + state::State, utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, video::drm::DrmError, }, @@ -149,50 +150,74 @@ pub trait BackendAppliedConnectorTransaction { fn rollback(self: Box) -> Result<(), BackendConnectorTransactionError>; } -#[derive(Default)] +struct Common { + state: Rc, + states: AHashMap, +} + pub struct ConnectorTransaction { + common: Common, parts: AHashMap, Box>, } -#[derive(Default)] pub struct PreparedConnectorTransaction { + common: Common, parts: Vec>, } -#[derive(Default)] pub struct AppliedConnectorTransaction { + common: Common, parts: Vec>, } impl ConnectorTransaction { + pub fn new(state: &Rc) -> Self { + Self { + common: Common { + state: state.clone(), + states: Default::default(), + }, + parts: Default::default(), + } + } + pub fn add( &mut self, connector: &Rc, - change: BackendConnectorState, + mut state: BackendConnectorState, ) -> Result<(), BackendConnectorTransactionError> { + state.serial = self.common.state.backend_connector_state_serials.next(); let ty = connector.transaction_type(); let tran = match self.parts.entry(ty) { Entry::Occupied(v) => v.into_mut(), Entry::Vacant(v) => v.insert(connector.create_transaction()?), }; - tran.add(connector, change) + tran.add(connector, state)?; + self.common.states.insert(connector.id(), state); + Ok(()) } pub fn prepare( - &mut self, + mut self, ) -> Result { let mut new = vec![]; for tran in self.parts.drain_values() { new.push(tran.prepare()?); } - Ok(PreparedConnectorTransaction { parts: new }) + Ok(PreparedConnectorTransaction { + common: self.common, + parts: new, + }) } } impl PreparedConnectorTransaction { pub fn apply(self) -> Result { - let mut applied = AppliedConnectorTransaction::default(); + let mut applied = AppliedConnectorTransaction { + common: self.common, + parts: vec![], + }; for tran in self.parts { applied.parts.push(tran.apply()?); } @@ -205,6 +230,11 @@ impl AppliedConnectorTransaction { for tran in self.parts.drain(..) { tran.commit(); } + for (connector_id, state) in self.common.states.drain() { + if let Some(c) = self.common.state.connectors.get(&connector_id) { + c.set_state(&self.common.state, state); + } + } } } diff --git a/src/state.rs b/src/state.rs index b8e3dfad..a1277b09 100644 --- a/src/state.rs +++ b/src/state.rs @@ -422,14 +422,21 @@ impl ConnectorData { let mut tran = self.connector.create_transaction()?; tran.add(&self.connector, s)?; tran.prepare()?.apply()?.commit(); + self.set_state(state, s); + Ok(()) + } + + pub fn set_state(&self, state: &State, s: BackendConnectorState) { + let old = self.state.get(); + if old.serial >= s.serial { + return; + } + self.state.set(s); if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { - node.update_state(s); - } else { - self.state.set(s); + node.update_state(old, s); } - Ok(()) } } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 9b9b752b..ca5eea28 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -105,10 +105,7 @@ impl ConnectorHandler { async fn handle_connected(&self, info: MonitorInfo) { log::info!("Connector {} connected", self.data.connector.kernel_id()); self.data.connected.set(true); - let old_state = self.data.state.get(); - if old_state.serial < info.state.serial { - self.data.state.set(info.state); - } + self.data.set_state(&self.state, info.state); let name = self.state.globals.name(); if info.non_desktop { self.handle_non_desktop_connected(info).await; @@ -290,7 +287,7 @@ impl ConnectorHandler { on.global.formats.set(formats); } ConnectorEvent::State(state) => { - on.update_state(state); + self.data.set_state(&self.state, state); } ev => unreachable!("received unexpected event {:?}", ev), } diff --git a/src/tasks/idle.rs b/src/tasks/idle.rs index b576b4bc..71db33e0 100644 --- a/src/tasks/idle.rs +++ b/src/tasks/idle.rs @@ -136,7 +136,7 @@ impl Idle { } fn try_set_idle(&self, idle: bool) -> Result<(), BackendConnectorTransactionError> { - let mut tran = ConnectorTransaction::default(); + let mut tran = ConnectorTransaction::new(&self.state); for connector in self.state.connectors.lock().values() { let mut state = connector.state.get(); state.active = !idle; diff --git a/src/tree/output.rs b/src/tree/output.rs index 924cd0f5..60c091ba 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -849,12 +849,7 @@ impl OutputNode { self.state.tree_changed(); } - pub fn update_state(self: &Rc, state: BackendConnectorState) { - let old = self.global.connector.state.get(); - if old.serial >= state.serial { - return; - } - self.global.connector.state.set(state); + pub fn update_state(self: &Rc, old: BackendConnectorState, state: BackendConnectorState) { self.update_btf_and_bcs(state.transfer_function, state.color_space); if old.vrr != state.vrr { self.schedule.set_vrr_enabled(state.vrr); From 078c59d730c7a30ce3ca535db7e40e4934e6313c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 08:47:28 +0200 Subject: [PATCH 02/26] backend: add connector capabilities --- src/backend.rs | 11 +++++++++++ src/backends/metal/video.rs | 11 ++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index c93dbe8e..08b14ad2 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -124,6 +124,13 @@ impl Display for ConnectorKernelId { } } +bitflags! { + ConnectorCaps: u32; + CONCAP_CONNECTOR = 1 << 0, + CONCAP_MODE_SETTING = 1 << 1, + CONCAP_PHYSICAL_DISPLAY = 1 << 2, +} + pub trait Connector: Any { fn id(&self) -> ConnectorId; fn kernel_id(&self) -> ConnectorKernelId; @@ -132,6 +139,10 @@ pub trait Connector: Any { fn damage(&self); fn drm_dev(&self) -> Option; fn effectively_locked(&self) -> bool; + #[expect(dead_code)] + fn caps(&self) -> ConnectorCaps { + ConnectorCaps::none() + } fn drm_feedback(&self) -> Option> { None } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 4b1aaaed..bd8e10f4 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -4,9 +4,10 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, - BackendDrmLessee, BackendEvent, BackendLuminance, BackendTransferFunction, Connector, - ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, - HardwareCursorUpdate, Mode, MonitorInfo, + BackendDrmLessee, BackendEvent, BackendLuminance, BackendTransferFunction, + CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, + ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, + HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -854,6 +855,10 @@ impl Connector for MetalConnector { fb.locked } + fn caps(&self) -> ConnectorCaps { + CONCAP_CONNECTOR | CONCAP_MODE_SETTING | CONCAP_PHYSICAL_DISPLAY + } + fn drm_feedback(&self) -> Option> { self.drm_feedback.get() } From 8356dd5d5c8d5ddc77d995112fe938a58e9e0993 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:13:14 +0200 Subject: [PATCH 03/26] head-management: add infrastructure --- Cargo.lock | 21 + Cargo.toml | 1 + src/client.rs | 1 + src/client/objects.rs | 17 +- src/compositor.rs | 28 +- src/config/handler.rs | 2 +- src/globals.rs | 2 + src/ifs.rs | 1 + src/ifs/head_management.rs | 191 +++++++ .../head_management/head_management_macros.rs | 392 ++++++++++++++ src/ifs/head_management/jay_head_error_v1.rs | 77 +++ src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext/jay_head_ext_core_info_v1.rs | 61 +++ .../jay_head_manager_session_v1.rs | 488 ++++++++++++++++++ .../head_management/jay_head_manager_v1.rs | 158 ++++++ .../jay_head_transaction_result_v1.rs | 82 +++ src/ifs/head_management/jay_head_v1.rs | 55 ++ src/state.rs | 21 +- src/tasks/connector.rs | 32 +- src/utils/copyhashmap.rs | 8 + src/utils/rc_eq.rs | 26 +- wire/jay_head_error_v1.txt | 23 + wire/jay_head_ext_core_info_v1.txt | 15 + wire/jay_head_manager_ext_core_info_v1.txt | 7 + wire/jay_head_manager_session_v1.txt | 59 +++ wire/jay_head_manager_v1.txt | 21 + wire/jay_head_transaction_result_v1.txt | 15 + wire/jay_head_v1.txt | 7 + 28 files changed, 1791 insertions(+), 21 deletions(-) create mode 100644 src/ifs/head_management.rs create mode 100644 src/ifs/head_management/head_management_macros.rs create mode 100644 src/ifs/head_management/jay_head_error_v1.rs create mode 100644 src/ifs/head_management/jay_head_ext.rs create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs create mode 100644 src/ifs/head_management/jay_head_manager_session_v1.rs create mode 100644 src/ifs/head_management/jay_head_manager_v1.rs create mode 100644 src/ifs/head_management/jay_head_transaction_result_v1.rs create mode 100644 src/ifs/head_management/jay_head_v1.rs create mode 100644 wire/jay_head_error_v1.txt create mode 100644 wire/jay_head_ext_core_info_v1.txt create mode 100644 wire/jay_head_manager_ext_core_info_v1.txt create mode 100644 wire/jay_head_manager_session_v1.txt create mode 100644 wire/jay_head_manager_v1.txt create mode 100644 wire/jay_head_transaction_result_v1.txt create mode 100644 wire/jay_head_v1.txt diff --git a/Cargo.lock b/Cargo.lock index dcbafa8c..060bcd72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,6 +627,7 @@ dependencies = [ "tiny-skia", "tracy-client-sys", "uapi", + "with_builtin_macros", ] [[package]] @@ -1740,6 +1741,26 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "with_builtin_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24deb3cd6e530e7617b12b1f0f1ce160a3a71d92feb351c4db5156d1d10e398a" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2259ae9b1285596f1ee52ce8f627013c65853d4d7f271cb10bfe2d048769804a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index ca184928..5924b75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] } regex = "1.11.1" cfg-if = "1.0.0" opera = "1.0.1" +with_builtin_macros = "0.1.0" [build-dependencies] repc = "0.1.1" diff --git a/src/client.rs b/src/client.rs index f7052bae..f7f46712 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,6 +64,7 @@ bitflags! { CAP_INPUT_METHOD = 1 << 10, CAP_WORKSPACE = 1 << 11, CAP_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, + CAP_HEAD_MANAGER = 1 << 13, } pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); diff --git a/src/client/objects.rs b/src/client/objects.rs index 88c440a5..ab625f91 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -6,6 +6,7 @@ use { ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1, ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + head_management::jay_head_error_v1::JayHeadErrorV1, ipc::{ data_control::{ ext_data_control_source_v1::ExtDataControlSourceV1, @@ -41,12 +42,13 @@ use { }, wire::{ ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id, - ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId, - JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, - WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, - WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, - XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, - ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, + ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayHeadErrorV1Id, + JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, + WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, + WlSurfaceId, WpDrmLeaseConnectorV1Id, WpImageDescriptionV1Id, + WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, + XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, + ZwpTabletToolV2Id, }, }, std::{cell::RefCell, rc::Rc}, @@ -87,6 +89,7 @@ pub struct Objects { pub ext_workspace_groups: CopyHashMap>, pub wp_image_description: CopyHashMap>, + pub jay_head_errors: CopyHashMap>, ids: RefCell>, } @@ -126,6 +129,7 @@ impl Objects { ext_data_sources: Default::default(), ext_workspace_groups: Default::default(), wp_image_description: Default::default(), + jay_head_errors: Default::default(), ids: RefCell::new(vec![]), } } @@ -168,6 +172,7 @@ impl Objects { self.ext_copy_sessions.clear(); self.ext_data_sources.clear(); self.ext_workspace_groups.clear(); + self.jay_head_errors.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/compositor.rs b/src/compositor.rs index edc3f916..ad857998 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -29,6 +29,9 @@ use { format::XRGB8888, globals::Globals, ifs::{ + head_management::{ + HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done, + }, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, wl_seat::handle_position_hint_requests, @@ -62,6 +65,7 @@ use { numcell::NumCell, oserror::OsError, queue::AsyncQueue, + rc_eq::RcEq, refcounted::RefCounted, run_toplevel::RunToplevel, tri::Try, @@ -339,6 +343,9 @@ fn start_compositor2( node_at_tree: Default::default(), position_hint_requests: Default::default(), backend_connector_state_serials: Default::default(), + head_names: Default::default(), + head_managers: Default::default(), + head_managers_async: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -523,6 +530,11 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { "position hint requests", handle_position_hint_requests(state.clone()), ), + eng.spawn2( + "jay head manager send done", + Phase::Layout, + handle_jay_head_manager_done(state.clone()), + ), ] } @@ -602,9 +614,6 @@ fn create_dummy_output(state: &Rc) { tearing_mode: Cell::new(&TearingMode::Never), brightness: Cell::new(None), }); - let connector = Rc::new(DummyOutput { - id: state.connector_ids.next(), - }) as Rc; let mode = backend::Mode { width: 0, height: 0, @@ -622,11 +631,21 @@ fn create_dummy_output(state: &Rc) { color_space: Default::default(), transfer_function: Default::default(), }; + let id = state.connector_ids.next(); + let connector = Rc::new(DummyOutput { id }) as Rc; + let name = Rc::new("Dummy".to_string()); + let head_name = state.head_names.next(); + let head_state = HeadState { + name: RcEq(name.clone()), + wl_output: None, + monitor_info: None, + }; let connector_data = Rc::new(ConnectorData { + id, connector, handler: Cell::new(None), connected: Cell::new(true), - name: "Dummy".to_string(), + name, drm_dev: None, async_event: Default::default(), damaged: Cell::new(false), @@ -634,6 +653,7 @@ fn create_dummy_output(state: &Rc) { needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), state: Cell::new(backend_state), + head_managers: HeadManagers::new(head_name, head_state), }); let schedule = Rc::new(OutputSchedule::new( &state.ring, diff --git a/src/config/handler.rs b/src/config/handler.rs index 514c68fb..62f3f43c 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1175,7 +1175,7 @@ impl ConfigProxyHandler { fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_connector(connector)?; self.respond(Response::GetConnectorName { - name: connector.name.clone(), + name: connector.name.deref().clone(), }); Ok(()) } diff --git a/src/globals.rs b/src/globals.rs index 6ce60fa6..2aeab076 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -10,6 +10,7 @@ use { ext_image_copy::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1Global, ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, + head_management::jay_head_manager_v1::JayHeadManagerV1Global, ipc::{ data_control::{ ext_data_control_manager_v1::ExtDataControlManagerV1Global, @@ -225,6 +226,7 @@ impl Globals { add_singleton!(ExtWorkspaceManagerV1Global); add_singleton!(WpColorManagerV1Global); add_singleton!(XdgToplevelTagManagerV1Global); + add_singleton!(JayHeadManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index d6af56ed..c3995f05 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -9,6 +9,7 @@ pub mod ext_image_copy; pub mod ext_output_image_capture_source_manager_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; +pub mod head_management; pub mod ipc; pub mod jay_client_query; pub mod jay_color_management; diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs new file mode 100644 index 00000000..b5a539ae --- /dev/null +++ b/src/ifs/head_management.rs @@ -0,0 +1,191 @@ +use { + crate::{ + backend::{ConnectorId, MonitorInfo, transaction::BackendConnectorTransactionError}, + client::ClientId, + globals::GlobalName, + ifs::head_management::{ + head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, + jay_head_v1::JayHeadV1, + }, + state::OutputData, + utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, + wire::JayHeadManagerSessionV1Id, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + thiserror::Error, +}; + +#[macro_use] +mod head_management_macros; +pub mod jay_head_error_v1; +mod jay_head_ext; +pub mod jay_head_manager_session_v1; +pub mod jay_head_manager_v1; +mod jay_head_transaction_result_v1; +mod jay_head_v1; + +linear_ids!(HeadNames, HeadName, u64); + +struct Head { + session: Rc, + common: Rc, + head: Rc, + ext: HeadExts, +} + +#[derive(Error, Debug)] +enum HeadTransactionError { + #[error("The connector {} has been removed", .0)] + HeadRemoved(ConnectorId), + #[error("The display connected to connector {} has changed", .0)] + MonitorChanged(ConnectorId), + #[error("The transaction has already failed")] + AlreadyFailed, + #[error(transparent)] + Backend(#[from] BackendConnectorTransactionError), +} + +struct HeadCommon { + mgr: Rc, + name: HeadName, + id: ConnectorId, + removed: Cell, + + shared: Rc>, + snapshot_state: RefCell, + transaction_state: RefCell, + pending: RefCell>, +} + +#[derive(Clone, Eq, PartialEq)] +pub struct HeadState { + pub name: RcEq, + pub wl_output: Option, + pub monitor_info: Option>, +} + +enum HeadOp {} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +enum HeadMgrState { + #[default] + Init, + Started, + StopScheduled, + Stopped, +} + +struct HeadMgrCommon { + state: Cell, + in_transaction: Cell, + transaction_failed: Cell, +} + +impl HeadCommon { + fn assert_removed(&self) -> Result<(), HeadCommonError> { + if self.removed.get() { + Ok(()) + } else { + Err(HeadCommonError::NotYetRemoved) + } + } + + fn assert_in_transaction(&self) -> Result<(), HeadCommonError> { + if self.mgr.in_transaction.get() { + Ok(()) + } else { + Err(HeadCommonError::NotInTransaction) + } + } + + #[expect(dead_code)] + fn push_op(&self, op: HeadOp) -> Result<(), HeadCommonError> { + self.assert_in_transaction()?; + self.pending.borrow_mut().push(op); + Ok(()) + } +} + +impl HeadMgrCommon { + fn assert_stopped(&self) -> Result<(), HeadCommonError> { + if self.state.get() == HeadMgrState::Stopped { + Ok(()) + } else { + Err(HeadCommonError::NotYetStopped) + } + } +} + +#[derive(Debug, Error)] +pub enum HeadCommonError { + #[error("Head has not yet been removed")] + NotYetRemoved, + #[error("Manager is not inside a transaction")] + NotInTransaction, + #[error("Manager has not yet been stopped")] + NotYetStopped, +} + +pub struct HeadManagers { + name: HeadName, + state: Rc>, + managers: CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc>, +} + +macro_rules! skip_in_transaction { + ($mgr:expr) => { + if $mgr.common.mgr.in_transaction.get() { + continue; + } + }; +} + +impl HeadManagers { + pub fn new(name: HeadName, state: HeadState) -> Self { + Self { + name, + state: Rc::new(RefCell::new(state)), + managers: Default::default(), + } + } + + pub fn handle_removed(&self) { + for head in self.managers.lock().drain_values() { + skip_in_transaction!(head); + head.session.heads.remove(&self.name); + head.head.send_removed(); + head.session.schedule_done(); + } + } + + pub fn handle_output_connected(&self, output: &OutputData) { + let state = &mut *self.state.borrow_mut(); + state.monitor_info = Some(RcEq(output.monitor_info.clone())); + if let Some(n) = &output.node { + state.wl_output = Some(n.global.name); + } + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.core_info_v1 { + ext.send_wl_output(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_output_disconnected(&self) { + let state = &mut *self.state.borrow_mut(); + state.wl_output = None; + state.monitor_info = None; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.core_info_v1 { + ext.send_wl_output(state); + head.session.schedule_done(); + } + } + } +} diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs new file mode 100644 index 00000000..a99a79d4 --- /dev/null +++ b/src/ifs/head_management/head_management_macros.rs @@ -0,0 +1,392 @@ +use { + crate::{ + client::ClientError, + ifs::head_management::{ + Head, HeadState, + jay_head_manager_session_v1::{JayHeadManagerSessionV1, JayHeadManagerSessionV1Error}, + jay_head_manager_v1::JayHeadManagerV1, + jay_head_v1::JayHeadV1, + }, + object::{ObjectId, Version}, + state::ConnectorData, + utils::clonecell::CloneCell, + }, + std::rc::Rc, +}; + +macro_rules! error_ { + ($error_name:ident, $($tt:tt)*) => { + #[derive(Debug, thiserror::Error)] + pub enum $error_name { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] super::super::HeadCommonError), + $($tt)* + } + efrom!($error_name, ClientError, crate::client::ClientError); + }; +} + +macro_rules! impl_head_ext { + ( + $snake:ident, + $camel:ident, + $macro_name:ident, + $mgr_module:ident, + $mgr_name:ident, + $mgr_id_name:ident, + $head_module:ident, + $head_name:ident, + $head_id_name:ident, + $version:expr, + $(filter = $filter:ident,)? + $(after_announce = $after_announce:ident,)? + $(after_transaction = $after_transaction:ident,)? + ) => { + pub(in super::super) struct $mgr_name { + pub(in super::super) id: crate::wire::$mgr_id_name, + pub(in super::super) client: std::rc::Rc, + pub(in super::super) tracker: crate::leaks::Tracker, + pub(in super::super) version: crate::object::Version, + pub(in super::super) common: std::rc::Rc, + } + + impl $mgr_name { + pub(in super::super) const VERSION: crate::object::Version = crate::object::Version($version); + pub(in super::super) const NAME: &str = concat!("jay_head_manager_ext_", stringify!($snake)); + } + + object_base! { + self = $mgr_name; + version = self.version; + } + + impl crate::object::Object for $mgr_name {} + + simple_add_obj!($mgr_name); + + pub(in super::super) struct $head_name { + pub(in super::super) id: crate::wire::$head_id_name, + pub(in super::super) client: std::rc::Rc, + pub(in super::super) tracker: crate::leaks::Tracker, + pub(in super::super) version: crate::object::Version, + pub(in super::super) common: std::rc::Rc, + } + + object_base! { + self = $head_name; + version = self.version; + } + + impl crate::object::Object for $head_name {} + + simple_add_obj!($head_name); + + impl $mgr_name { + pub(in super::super) fn announce( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + connector: &crate::state::ConnectorData, + common: &std::rc::Rc, + ) -> Result>, crate::client::ClientError> { + $( + if !self.$filter(connector, common) { + return Ok(None); + } + )? + let obj = std::rc::Rc::new($head_name { + id: self.client.new_id()?, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: common.clone(), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + self.send_head(&obj); + Ok(Some(obj)) + } + + fn send_head(&self, obj: &$head_name) { + self.client.event(crate::wire::$mgr_module::Head { + self_id: self.id, + head: obj.id, + }); + } + } + + impl $head_name { + pub(in super::super) fn after_announce_wrapper( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + shared: &super::super::HeadState, + ) { + $( + self.$after_announce(shared); + )? + } + + pub(in super::super) fn after_transaction_wrapper( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + shared: &super::super::HeadState, + #[allow(clippy::allow_attributes, unused_variables)] + tran: &super::super::HeadState, + ) { + $( + self.$after_transaction(shared, tran); + )? + } + } + } +} + +#[rustfmt::skip] +macro_rules! declare_extension_type_macro { + ( + ($dollar:tt), + $snake:ident, + $camel:ident, + $macro_name:ident, + $macro_name_int:ident, + $mgr_module:ident, + $mgr_name:ident, + $mgr_id_name:ident, + $mgr_request_handler:ident, + $head_module:ident, + $head_name:ident, + $head_id_name:ident, + $head_request_handler:ident, + $error_name:ident, + ) => { + macro_rules! $macro_name { + ( + version = $version:expr, + $dollar(filter = $filter:ident,)? + $dollar(after_announce = $after_announce:ident,)? + $dollar(after_transaction = $after_transaction:ident,)? + ) => { + $macro_name_int! { + ($), + version = $version, + $dollar(filter = $filter,)? + $dollar(after_announce = $after_announce,)? + $dollar(after_transaction = $after_transaction,)? + } + } + } + + macro_rules! $macro_name_int { + ( + ($dollar2:tt), + version = $version:expr, + $dollar(filter = $filter:ident,)? + $dollar(after_announce = $after_announce:ident,)? + $dollar(after_transaction = $after_transaction:ident,)? + ) => { + impl_head_ext!( + $snake, + $camel, + $macro_name, + $mgr_module, + $mgr_name, + $mgr_id_name, + $head_module, + $head_name, + $head_id_name, + $version, + $dollar(filter = $filter,)? + $dollar(after_announce = $after_announce,)? + $dollar(after_transaction = $after_transaction,)? + ); + + macro_rules! head_common_req { + () => { + head_common_req_!($snake); + } + } + + macro_rules! mgr_common_req { + () => { + mgr_common_req_!($snake); + } + } + + type ErrorName = $error_name; + type MgrName = $mgr_name; + type HeadName = $head_name; + + macro_rules! error { + ($dollar2($tt:tt)*) => { + error_!($error_name, $dollar2($tt)*); + } + } + } + } + }; +} + +macro_rules! mgr_common_req_ { + ($snake:ident) => { + with_builtin_macros::with_eager_expansions! { + fn destroy( + &self, + _req: crate::wire::#{concat_idents!(jay_head_manager_ext_, $snake)}::Destroy, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.common.assert_stopped()?; + self.client.remove_obj(self)?; + Ok(()) + } + } + }; +} + +macro_rules! head_common_req_ { + ($snake:ident) => { + with_builtin_macros::with_eager_expansions! { + fn destroy( + &self, + _req: crate::wire::#{concat_idents!(jay_head_ext_, $snake)}::Destroy, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.common.assert_removed()?; + self.client.remove_obj(self)?; + Ok(()) + } + } + }; +} + +macro_rules! declare_extensions { + ($($snake:ident: $camel:ident,)*) => { + #[derive(linearize::Linearize)] + pub(super) enum HeadExtension { + $($camel,)* + } + + pub(super) fn send_available_extensions(mgr: &JayHeadManagerV1) { + use linearize::Linearize; + with_builtin_macros::with_eager_expansions! { + $( + type $camel = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)}; + mgr.send_extension( + HeadExtension::$camel.linearize() as _, + $camel::NAME, + $camel::VERSION, + ); + )* + } + } + + pub(super) fn announce_head( + session: &Rc, + head: &Rc, + connector: &ConnectorData, + ) -> Result, ClientError> { + session.send_head_start(head, connector.head_managers.name); + let head = super::Head { + session: session.clone(), + common: head.common.clone(), + head: head.clone(), + ext: HeadExts { + $( + $snake: match session.ext.$snake.get() { + Some(f) => f.announce(connector, &head.common)?, + _ => None, + }, + )* + }, + }; + session.send_head_complete(); + let shared = &*connector.head_managers.state.borrow(); + $( + if let Some(ext) = &head.ext.$snake { + ext.after_announce_wrapper(shared); + } + )* + Ok(Rc::new(head)) + } + + pub(super) fn bind_extension(session: &JayHeadManagerSessionV1, ext: HeadExtension, name: u32, version: u32, id: ObjectId) -> Result<(), JayHeadManagerSessionV1Error> { + match ext { + $( + HeadExtension::$camel => { + if session.ext.$snake.is_some() { + return Err(JayHeadManagerSessionV1Error::AlreadyBound(name)); + } + let version = Version(version); + with_builtin_macros::with_eager_expansions! { + type T = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)}; + if version > T::VERSION { + return Err(JayHeadManagerSessionV1Error::UnsupportedVersion(name, version)); + } + let obj = Rc::new(T { + id: id.into(), + client: session.client.clone(), + tracker: Default::default(), + version, + common: session.common.clone(), + }); + } + track!(session.client, obj); + session.client.add_client_obj(&obj)?; + session.ext.$snake.set(Some(obj)); + } + )* + } + Ok(()) + } + + with_builtin_macros::with_eager_expansions! { + #[derive(Default)] + pub(super) struct MgrExts { + $( + pub(super) $snake: CloneCell>>, + )* + } + + pub(super) struct HeadExts { + $( + pub(super) $snake: Option>, + )* + } + } + + impl HeadExts { + pub(super) fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + $( + if let Some(ext) = &self.$snake { + ext.after_transaction_wrapper(shared, tran); + } + )* + } + } + + with_builtin_macros::with_eager_expansions! { + $( + declare_extension_type_macro!( + ($), + $snake, + $camel, + #{concat_idents!(impl_, $snake)}, + #{concat_idents!(impl_, $snake, _int)}, + #{concat_idents!(jay_head_manager_ext_, $snake)}, + #{concat_idents!(JayHeadManagerExt, $camel)}, + #{concat_idents!(JayHeadManagerExt, $camel, Id)}, + #{concat_idents!(JayHeadManagerExt, $camel, RequestHandler)}, + #{concat_idents!(jay_head_ext_, $snake)}, + #{concat_idents!(JayHeadExt, $camel)}, + #{concat_idents!(JayHeadExt, $camel, Id)}, + #{concat_idents!(JayHeadExt, $camel, RequestHandler)}, + #{concat_idents!(JayHeadExt, $camel, Error)}, + ); + )* + } + }; +} + +declare_extensions! { + core_info_v1: CoreInfoV1, +} diff --git a/src/ifs/head_management/jay_head_error_v1.rs b/src/ifs/head_management/jay_head_error_v1.rs new file mode 100644 index 00000000..4560a99a --- /dev/null +++ b/src/ifs/head_management/jay_head_error_v1.rs @@ -0,0 +1,77 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::head_management::HeadTransactionError, + leaks::Tracker, + object::{Object, Version}, + utils::errorfmt::ErrorFmt, + wire::{JayHeadErrorV1Id, jay_head_error_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct JayHeadErrorV1 { + pub(super) id: JayHeadErrorV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) error: Rc, +} + +impl JayHeadErrorV1 { + pub fn send(&self) { + let msg = ErrorFmt(&*self.error).to_string(); + self.send_message(&msg); + match &*self.error { + HeadTransactionError::HeadRemoved(_) | HeadTransactionError::MonitorChanged(_) => { + self.send_out_of_date(); + } + HeadTransactionError::AlreadyFailed => { + self.send_already_failed(); + } + HeadTransactionError::Backend(_) => {} + } + self.client.event(Done { self_id: self.id }); + } + + fn send_message(&self, message: &str) { + self.client.event(Message { + self_id: self.id, + msg: message, + }); + } + + fn send_out_of_date(&self) { + self.client.event(OutOfDate { self_id: self.id }); + } + + fn send_already_failed(&self) { + self.client.event(AlreadyFailed { self_id: self.id }); + } +} + +impl JayHeadErrorV1RequestHandler for JayHeadErrorV1 { + type Error = JayHeadErrorV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = JayHeadErrorV1; + version = self.version; +} + +impl Object for JayHeadErrorV1 {} + +dedicated_add_obj!(JayHeadErrorV1, JayHeadErrorV1Id, jay_head_errors); + +#[derive(Debug, Error)] +pub enum JayHeadErrorV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayHeadErrorV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs new file mode 100644 index 00000000..d6d1abf0 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext.rs @@ -0,0 +1 @@ +pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs new file mode 100644 index 00000000..9bb30759 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs @@ -0,0 +1,61 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{jay_head_ext_core_info_v1::*, jay_head_manager_ext_core_info_v1::*}, + }, + std::rc::Rc, +}; + +impl_core_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_name(shared); + self.send_wl_output(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.wl_output != tran.wl_output { + self.send_wl_output(shared); + } + } + + fn send_name(&self, state: &HeadState) { + self.client.event(Name { + self_id: self.id, + name: Some(&**state.name), + }); + } + + pub(in super::super) fn send_wl_output(&self, state: &HeadState) { + match state.wl_output { + None => { + self.client.event(NoWlOutput { self_id: self.id }); + } + Some(name) => { + self.client.event(WlOutput { + self_id: self.id, + global_name: name.raw(), + }); + } + } + } +} + +impl JayHeadManagerExtCoreInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCoreInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs new file mode 100644 index 00000000..022dbe55 --- /dev/null +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -0,0 +1,488 @@ +use { + super::HeadMgrCommon, + crate::{ + backend::transaction::{ConnectorTransaction, PreparedConnectorTransaction}, + client::{Client, ClientError}, + ifs::{ + head_management::{ + Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadTransactionError, + head_management_macros::{HeadExtension, MgrExts, announce_head, bind_extension}, + jay_head_manager_v1::JayHeadManagerV1, + jay_head_transaction_result_v1::JayHeadTransactionResultV1, + jay_head_v1::JayHeadV1, + }, + wl_output::PersistentOutputState, + }, + leaks::Tracker, + object::{Object, Version}, + state::{ConnectorData, State}, + tree::{TearingMode, VrrMode}, + utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + wire::{ + JayHeadManagerSessionV1Id, JayHeadTransactionResultV1Id, + jay_head_manager_session_v1::{ + ApplyChanges, BeginTransaction, BindExtension, CommitTransaction, Destroy, + HeadComplete, HeadStart, JayHeadManagerSessionV1RequestHandler, + RollbackTransaction, Start, Stop, Stopped, TestTransaction, TransactionEnded, + TransactionStarted, + }, + }, + }, + linearize::LinearizeExt, + std::{cell::Cell, mem, ops::Deref, rc::Rc}, + thiserror::Error, +}; + +pub struct HeadManagerEvent { + mgr: Rc, + ty: HeadManagerEventType, +} + +pub(super) enum HeadManagerEventType { + Done, + TransactionStarted, + TransactionEnded, + TransactionResult(Rc), + Stopped, +} + +pub async fn handle_jay_head_manager_done(state: Rc) { + loop { + let ev = state.head_managers_async.pop().await; + let session = ev.mgr; + if session.mgr.destroyed.get() { + continue; + } + if session.mgr.done_scheduled.take() { + session.mgr.send_done(); + } + if session.common.state.get() == HeadMgrState::Stopped { + continue; + } + match ev.ty { + HeadManagerEventType::Done => {} + HeadManagerEventType::TransactionStarted => { + session.send_transaction_started(); + } + HeadManagerEventType::TransactionEnded => { + session.send_transaction_ended(); + } + HeadManagerEventType::Stopped => { + session.common.state.set(HeadMgrState::Stopped); + session.send_stopped(); + } + HeadManagerEventType::TransactionResult(res) => { + if !res.destroyed.get() { + res.send(); + } + } + } + } +} + +pub struct JayHeadManagerSessionV1 { + pub(super) id: JayHeadManagerSessionV1Id, + pub(super) mgr: Rc, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) common: Rc, + pub(super) serial: NumCell, + pub(super) heads: CopyHashMap>, + pub(super) ext: MgrExts, +} + +impl JayHeadManagerSessionV1 { + pub fn announce(self: &Rc, connector: &ConnectorData) { + if self.common.in_transaction.get() { + return; + } + if let Err(e) = self.try_announce(connector) { + self.client.error(e); + } + } + + fn try_announce(self: &Rc, connector: &ConnectorData) -> Result<(), ClientError> { + let shared = connector.head_managers.state.clone(); + let common = Rc::new(HeadCommon { + mgr: self.common.clone(), + name: connector.head_managers.name, + id: connector.id, + removed: Cell::new(false), + shared: shared.clone(), + snapshot_state: shared.deref().clone(), + transaction_state: shared.deref().clone(), + pending: Default::default(), + }); + let obj = Rc::new(JayHeadV1 { + id: self.client.new_id()?, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: common.clone(), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + let head = announce_head(self, &obj, connector)?; + connector + .head_managers + .managers + .set((self.client.id, self.id), head.clone()); + self.heads.set(common.name, head); + Ok(()) + } + + fn schedule_event(self: &Rc, ty: HeadManagerEventType) { + self.client + .state + .head_managers_async + .push(HeadManagerEvent { + mgr: self.clone(), + ty, + }); + } + + pub(super) fn schedule_done(self: &Rc) { + if !self.mgr.done_scheduled.replace(true) { + self.serial.fetch_add(1); + self.schedule_event(HeadManagerEventType::Done); + } + } + + fn schedule_transaction_started(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::TransactionStarted); + } + + fn schedule_transaction_ended(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::TransactionEnded); + } + + fn schedule_stopped(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::Stopped); + } + + fn send_transaction_started(&self) { + self.client.event(TransactionStarted { self_id: self.id }); + } + + fn send_transaction_ended(&self) { + self.client.event(TransactionEnded { self_id: self.id }); + } + + fn send_stopped(&self) { + self.client.event(Stopped { self_id: self.id }); + } + + pub(super) fn send_head_start(&self, head: &JayHeadV1, name: HeadName) { + self.client.event(HeadStart { + self_id: self.id, + head: head.id, + name: name.0, + }); + } + + pub(super) fn send_head_complete(&self) { + self.client.event(HeadComplete { self_id: self.id }); + } + + fn detach(&self, send_removed: bool) { + let id = (self.client.id, self.id); + for data in self.client.state.connectors.lock().values() { + if let Some(head) = data.head_managers.managers.remove(&id) { + if send_removed { + head.head.send_removed(); + } + } + } + self.client.state.head_managers.remove(&id); + self.heads.clear(); + } + + fn schedule_transaction_result( + self: &Rc, + id: JayHeadTransactionResultV1Id, + error: Option, + ) -> Result<(), JayHeadManagerSessionV1Error> { + if error.is_some() { + self.common.transaction_failed.set(true); + } + self.mgr.done_scheduled.set(true); + let res = Rc::new(JayHeadTransactionResultV1 { + id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + error: error.map(Rc::new), + destroyed: Cell::new(false), + }); + track!(self.client, res); + self.client.add_client_obj(&res)?; + self.schedule_event(HeadManagerEventType::TransactionResult(res)); + Ok(()) + } + + fn after_transaction_ended(self: &Rc) { + let mut to_remove = vec![]; + for head in self.heads.lock().values() { + if self.client.state.connectors.not_contains(&head.common.id) { + head.head.send_removed(); + to_remove.push(head.common.name); + continue; + } + let _ = head.common.pending.take(); + let tran = &*head.common.transaction_state.borrow(); + let shared = &*head.common.shared.borrow(); + head.ext.after_transaction(shared, tran); + } + for name in to_remove { + self.heads.remove(&name); + } + for connector in self.client.state.connectors.lock().values() { + if !self.heads.contains(&connector.head_managers.name) { + self.announce(connector); + } + } + self.schedule_transaction_ended(); + } + + fn prepare_transaction(&self) -> Result { + let mut tran = ConnectorTransaction::new(&self.client.state); + for head in self.heads.lock().values() { + let current = &*head.common.shared.borrow(); + let snapshot = &*head.common.snapshot_state.borrow(); + let desired = &*head.common.transaction_state.borrow(); + if desired == current || desired == snapshot { + continue; + } + let Some(connector) = self.client.state.connectors.get(&head.common.id) else { + return Err(HeadTransactionError::HeadRemoved(head.common.id)); + }; + let old = connector.state.get(); + #[expect(unused_mut)] + let mut new = old; + if old == new { + continue; + } + if current.monitor_info != desired.monitor_info { + return Err(HeadTransactionError::MonitorChanged(head.common.id)); + } + tran.add(&connector.connector, new)?; + } + Ok(tran.prepare()?) + } + + fn commit_transaction(&self) -> Result<(), HeadTransactionError> { + self.prepare_transaction()?.apply()?.commit(); + Ok(()) + } +} + +impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { + type Error = JayHeadManagerSessionV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_stopped()?; + self.mgr.sessions.fetch_sub(1); + self.client.remove_obj(self)?; + Ok(()) + } + + fn bind_extension(&self, req: BindExtension<'_>, _slf: &Rc) -> Result<(), Self::Error> { + if self.common.state.get() != HeadMgrState::Init { + return Err(JayHeadManagerSessionV1Error::AlreadyStarted); + } + let Some(ext) = HeadExtension::from_linear(req.name as usize) else { + return Err(JayHeadManagerSessionV1Error::UnknownExtension(req.name)); + }; + bind_extension(self, ext, req.name, req.version, req.id)?; + Ok(()) + } + + fn stop(&self, _req: Stop, slf: &Rc) -> Result<(), Self::Error> { + if self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::InTransaction); + } + match self.common.state.get() { + HeadMgrState::Init | HeadMgrState::Started => {} + HeadMgrState::StopScheduled | HeadMgrState::Stopped => return Ok(()), + } + self.common.state.set(HeadMgrState::StopScheduled); + self.detach(true); + self.serial.fetch_add(1); + slf.schedule_stopped(); + Ok(()) + } + + fn start(&self, _req: Start, slf: &Rc) -> Result<(), Self::Error> { + if self.common.state.get() != HeadMgrState::Init { + return Err(JayHeadManagerSessionV1Error::AlreadyStarted); + } + self.common.state.set(HeadMgrState::Started); + self.client + .state + .head_managers + .set((self.client.id, self.id), slf.clone()); + for connector in self.client.state.connectors.lock().values() { + slf.announce(connector); + } + slf.schedule_done(); + Ok(()) + } + + fn begin_transaction(&self, _req: BeginTransaction, slf: &Rc) -> Result<(), Self::Error> { + if self.common.in_transaction.replace(true) { + return Err(JayHeadManagerSessionV1Error::AlreadyInTransaction); + } + for head in self.heads.lock().values() { + let snapshot = head.common.shared.borrow().clone(); + *head.common.transaction_state.borrow_mut() = snapshot.clone(); + *head.common.snapshot_state.borrow_mut() = snapshot; + head.common.pending.borrow_mut().clear(); + } + self.common.transaction_failed.set(false); + slf.schedule_transaction_started(); + Ok(()) + } + + fn rollback_transaction( + &self, + _req: RollbackTransaction, + slf: &Rc, + ) -> Result<(), Self::Error> { + if !self.common.in_transaction.replace(false) { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + slf.after_transaction_ended(); + Ok(()) + } + + fn apply_changes(&self, req: ApplyChanges, slf: &Rc) -> Result<(), Self::Error> { + if !self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + macro_rules! schedule_result { + ($res:expr) => { + slf.schedule_transaction_result(req.result, Some($res))?; + return Ok(()); + }; + } + if self.common.transaction_failed.get() { + schedule_result!(HeadTransactionError::AlreadyFailed); + } + bitflags! { + ToSend: u32; + CORE_INFO = 1 << 0, + } + for head in self.heads.lock().values() { + let pending = mem::take(&mut *head.common.pending.borrow_mut()); + #[expect(unused_variables)] + let snapshot = &*head.common.snapshot_state.borrow(); + let state = &mut *head.common.transaction_state.borrow_mut(); + #[expect(unused_mut)] + let mut to_send = ToSend::default(); + #[expect(clippy::never_loop)] + for op in pending { + match op {} + } + if to_send.contains(CORE_INFO) + && let Some(i) = &head.ext.core_info_v1 + { + i.send_wl_output(state); + } + } + slf.schedule_transaction_result(req.result, None)?; + Ok(()) + } + + fn test_transaction(&self, req: TestTransaction, slf: &Rc) -> Result<(), Self::Error> { + if !self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + let res = self.prepare_transaction().err(); + slf.schedule_transaction_result(req.result, res)?; + Ok(()) + } + + fn commit_transaction( + &self, + req: CommitTransaction, + slf: &Rc, + ) -> Result<(), Self::Error> { + if !self.common.in_transaction.replace(false) { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + slf.after_transaction_ended(); + if let Err(e) = self.commit_transaction() { + slf.schedule_transaction_result(req.result, Some(e))?; + return Ok(()); + } + for head in self.heads.lock().values() { + let desired = &*head.common.transaction_state.borrow(); + if let Some(output) = self.client.state.outputs.get(&head.common.id) + && let Some(node) = &output.node + { + let _ = node; + } else if let Some(mi) = &desired.monitor_info { + let pos = &self.client.state.persistent_output_states; + let pos = match pos.get(&mi.output_id) { + Some(ps) => ps, + _ => { + let ps = Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Default::default(), + vrr_mode: Cell::new(&VrrMode::Never), + vrr_cursor_hz: Default::default(), + tearing_mode: Cell::new(&TearingMode::Never), + brightness: Default::default(), + }); + pos.set(mi.output_id.clone(), ps.clone()); + ps + } + }; + let _ = pos; + } + } + slf.schedule_transaction_result(req.result, None)?; + Ok(()) + } +} + +object_base! { + self = JayHeadManagerSessionV1; + version = self.version; +} + +impl Object for JayHeadManagerSessionV1 { + fn break_loops(&self) { + self.detach(false); + } +} + +simple_add_obj!(JayHeadManagerSessionV1); + +#[derive(Debug, Error)] +pub enum JayHeadManagerSessionV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] HeadCommonError), + #[error("Manager was already started")] + AlreadyStarted, + #[error("There is no extension with name {}", .0)] + UnknownExtension(u32), + #[error("The extension with name {} is already bound", .0)] + AlreadyBound(u32), + #[error("The extension with name {} does not support version {}", .0, .1 .0)] + UnsupportedVersion(u32, Version), + #[error("There already is an active transaction")] + AlreadyInTransaction, + #[error("There is no active transaction")] + NotInTransaction, + #[error("There is an active transaction")] + InTransaction, +} +efrom!(JayHeadManagerSessionV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_manager_v1.rs b/src/ifs/head_management/jay_head_manager_v1.rs new file mode 100644 index 00000000..3ce36535 --- /dev/null +++ b/src/ifs/head_management/jay_head_manager_v1.rs @@ -0,0 +1,158 @@ +use { + crate::{ + client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError}, + globals::{Global, GlobalName}, + ifs::head_management::{ + HeadMgrCommon, head_management_macros::send_available_extensions, + jay_head_manager_session_v1::JayHeadManagerSessionV1, + }, + leaks::Tracker, + object::{Object, Version}, + utils::numcell::NumCell, + wire::{ + JayHeadManagerV1Id, + jay_head_manager_v1::{ + CreateSession, Destroy, Done, Extension, ExtensionsDone, + JayHeadManagerV1RequestHandler, + }, + }, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct JayHeadManagerV1Global { + pub name: GlobalName, +} + +impl JayHeadManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: JayHeadManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), JayHeadManagerV1Error> { + let mgr = Rc::new(JayHeadManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + done_scheduled: Cell::new(false), + sessions: Default::default(), + destroyed: Default::default(), + }); + track!(client, mgr); + client.add_client_obj(&mgr)?; + send_available_extensions(&mgr); + mgr.send_extensions_done(); + Ok(()) + } +} + +global_base!( + JayHeadManagerV1Global, + JayHeadManagerV1, + JayHeadManagerV1Error +); + +simple_add_global!(JayHeadManagerV1Global); + +impl Global for JayHeadManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_HEAD_MANAGER + } +} + +pub(super) struct JayHeadManagerV1 { + pub(super) id: JayHeadManagerV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) done_scheduled: Cell, + pub(super) sessions: NumCell, + pub(super) destroyed: Cell, +} + +impl JayHeadManagerV1 { + pub(super) fn send_extension(&self, name: u32, interface: &str, version: Version) { + self.client.event(Extension { + self_id: self.id, + name, + interface, + version: version.0, + }); + } + + fn send_extensions_done(&self) { + self.client.event(ExtensionsDone { self_id: self.id }); + } + + pub(super) fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } +} + +impl JayHeadManagerV1RequestHandler for JayHeadManagerV1 { + type Error = JayHeadManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + if self.sessions.get() > 0 { + return Err(JayHeadManagerV1Error::HasSessions); + } + self.destroyed.set(true); + self.client.remove_obj(self)?; + Ok(()) + } + + fn create_session(&self, req: CreateSession, slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(JayHeadManagerSessionV1 { + id: req.session, + mgr: slf.clone(), + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: Rc::new(HeadMgrCommon { + state: Default::default(), + in_transaction: Cell::new(false), + transaction_failed: Cell::new(false), + }), + serial: Default::default(), + heads: Default::default(), + ext: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + self.sessions.fetch_add(1); + Ok(()) + } +} + +object_base! { + self = JayHeadManagerV1; + version = self.version; +} + +impl Object for JayHeadManagerV1 {} + +simple_add_obj!(JayHeadManagerV1); + +#[derive(Debug, Error)] +pub enum JayHeadManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Manager still has sessions")] + HasSessions, +} +efrom!(JayHeadManagerV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_transaction_result_v1.rs b/src/ifs/head_management/jay_head_transaction_result_v1.rs new file mode 100644 index 00000000..53732db2 --- /dev/null +++ b/src/ifs/head_management/jay_head_transaction_result_v1.rs @@ -0,0 +1,82 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::head_management::{HeadTransactionError, jay_head_error_v1::JayHeadErrorV1}, + leaks::Tracker, + object::{Object, Version}, + wire::{JayHeadTransactionResultV1Id, jay_head_transaction_result_v1::*}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub(super) struct JayHeadTransactionResultV1 { + pub(super) id: JayHeadTransactionResultV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) error: Option>, + pub(super) destroyed: Cell, +} + +impl JayHeadTransactionResultV1 { + pub(super) fn send(&self) { + match self.error { + None => self.send_success(), + _ => self.send_failed(), + } + } + + fn send_success(&self) { + self.client.event(Success { self_id: self.id }); + } + + fn send_failed(&self) { + self.client.event(Failed { self_id: self.id }); + } +} + +impl JayHeadTransactionResultV1RequestHandler for JayHeadTransactionResultV1 { + type Error = JayHeadTransactionResultV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + self.destroyed.set(true); + Ok(()) + } + + fn get_error(&self, req: GetError, _slf: &Rc) -> Result<(), Self::Error> { + let Some(err) = &self.error else { + return Err(JayHeadTransactionResultV1Error::NoError); + }; + let err = Rc::new(JayHeadErrorV1 { + id: req.error, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + error: err.clone(), + }); + track!(self.client, err); + self.client.add_client_obj(&err)?; + err.send(); + Ok(()) + } +} + +object_base! { + self = JayHeadTransactionResultV1; + version = self.version; +} + +impl Object for JayHeadTransactionResultV1 {} + +simple_add_obj!(JayHeadTransactionResultV1); + +#[derive(Debug, Error)] +pub enum JayHeadTransactionResultV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The transaction result was success")] + NoError, +} +efrom!(JayHeadTransactionResultV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_v1.rs b/src/ifs/head_management/jay_head_v1.rs new file mode 100644 index 00000000..06721b07 --- /dev/null +++ b/src/ifs/head_management/jay_head_v1.rs @@ -0,0 +1,55 @@ +use { + super::HeadCommon, + crate::{ + client::{Client, ClientError}, + ifs::head_management::HeadCommonError, + leaks::Tracker, + object::{Object, Version}, + wire::{JayHeadV1Id, jay_head_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub(super) struct JayHeadV1 { + pub(super) id: JayHeadV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) common: Rc, +} + +impl JayHeadV1 { + pub(super) fn send_removed(&self) { + self.common.removed.set(true); + self.client.event(Removed { self_id: self.id }); + } +} + +impl JayHeadV1RequestHandler for JayHeadV1 { + type Error = JayHeadV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_removed()?; + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = JayHeadV1; + version = self.version; +} + +impl Object for JayHeadV1 {} + +simple_add_obj!(JayHeadV1); + +#[derive(Debug, Error)] +pub enum JayHeadV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] HeadCommonError), +} +efrom!(JayHeadV1Error, ClientError); diff --git a/src/state.rs b/src/state.rs index a1277b09..51f4951d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -40,6 +40,10 @@ use { ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_idle_notification_v1::ExtIdleNotificationV1, ext_session_lock_v1::ExtSessionLockV1, + head_management::{ + HeadManagers, HeadNames, + jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1}, + }, ipc::{ DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds, x_data_device::XIpcDeviceIds, @@ -107,8 +111,9 @@ use { }, wheel::Wheel, wire::{ - ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayRenderCtxId, JaySeatEventsId, - JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, + ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id, + JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, + ZwpLinuxDmabufFeedbackV1Id, }, xwayland::{self, XWaylandEvent}, }, @@ -257,6 +262,10 @@ pub struct State { pub node_at_tree: RefCell>, pub position_hint_requests: AsyncQueue, pub backend_connector_state_serials: BackendConnectorStateSerials, + pub head_names: HeadNames, + pub head_managers: + CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc>, + pub head_managers_async: AsyncQueue, } // impl Drop for State { @@ -368,10 +377,11 @@ pub struct DeviceHandlerData { } pub struct ConnectorData { + pub id: ConnectorId, pub connector: Rc, pub handler: Cell>>, pub connected: Cell, - pub name: String, + pub name: Rc, pub drm_dev: Option>, pub async_event: Rc, pub damaged: Cell, @@ -379,11 +389,12 @@ pub struct ConnectorData { pub needs_vblank_emulation: Cell, pub damage_intersect: Cell, pub state: Cell, + pub head_managers: HeadManagers, } pub struct OutputData { pub connector: Rc, - pub monitor_info: MonitorInfo, + pub monitor_info: Rc, pub node: Option>, pub lease_connectors: Rc>, } @@ -1017,6 +1028,8 @@ impl State { self.tl_matcher_manager.clear(); self.node_at_tree.borrow_mut().clear(); self.position_hint_requests.clear(); + self.head_managers.clear(); + self.head_managers_async.clear(); } pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index ca5eea28..555d2900 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -7,13 +7,16 @@ use { format::XRGB8888, globals::GlobalName, ifs::{ + head_management::{HeadManagers, HeadState}, jay_tray_v1::JayTrayV1Global, wl_output::{PersistentOutputState, WlOutputGlobal}, }, output_schedule::OutputSchedule, state::{ConnectorData, OutputData, State}, tree::{OutputNode, WsMoveConfig, move_ws_to_output}, - utils::{asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt}, + utils::{ + asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt, rc_eq::RcEq, + }, }, std::{cell::Cell, collections::VecDeque, rc::Rc}, }; @@ -39,11 +42,18 @@ pub fn handle(state: &Rc, connector: &Rc) { transfer_function: Default::default(), }; let id = connector.id(); + let name = Rc::new(connector.kernel_id().to_string()); + let head_state = HeadState { + name: RcEq(name.clone()), + wl_output: None, + monitor_info: None, + }; let data = Rc::new(ConnectorData { + id, connector: connector.clone(), handler: Default::default(), connected: Cell::new(false), - name: connector.kernel_id().to_string(), + name, drm_dev: drm_dev.clone(), async_event: Rc::new(AsyncEvent::default()), damaged: Cell::new(false), @@ -51,6 +61,7 @@ pub fn handle(state: &Rc, connector: &Rc) { needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), state: Cell::new(backend_state), + head_managers: HeadManagers::new(state.head_names.next(), head_state), }); if let Some(dev) = drm_dev { dev.connectors.set(id, data.clone()); @@ -62,6 +73,9 @@ pub fn handle(state: &Rc, connector: &Rc) { }; let future = state.eng.spawn("connector handler", oh.handle()); data.handler.set(Some(future)); + for mgr in state.head_managers.lock().values() { + mgr.announce(&data); + } if state.connectors.set(id, data).is_some() { panic!("Connector id has been reused"); } @@ -100,6 +114,7 @@ impl ConnectorHandler { } self.data.handler.set(None); self.state.connectors.remove(&self.id); + self.data.head_managers.handle_removed(); } async fn handle_connected(&self, info: MonitorInfo) { @@ -113,6 +128,7 @@ impl ConnectorHandler { self.handle_desktop_connected(info, name).await; } self.data.connected.set(false); + self.data.head_managers.handle_output_disconnected(); log::info!("Connector {} disconnected", self.data.connector.kernel_id()); } @@ -218,11 +234,11 @@ impl ConnectorHandler { .add_output_scale(on.global.persistent.scale.get()); let output_data = Rc::new(OutputData { connector: self.data.clone(), - monitor_info: info, + monitor_info: Rc::new(info), node: Some(on.clone()), lease_connectors: Default::default(), }); - self.state.outputs.set(self.id, output_data); + self.state.outputs.set(self.id, output_data.clone()); on.schedule_update_render_data(); self.state.root.outputs.set(self.id, on.clone()); self.state.output_extents_changed(); @@ -274,6 +290,9 @@ impl ConnectorHandler { self.state.tree_changed(); on.update_presentation_type(); self.state.workspace_managers.announce_output(&on); + self.data + .head_managers + .handle_output_connected(&output_data); 'outer: loop { while let Some(event) = self.data.connector.event() { match event { @@ -362,7 +381,7 @@ impl ConnectorHandler { async fn handle_non_desktop_connected(&self, monitor_info: MonitorInfo) { let output_data = Rc::new(OutputData { connector: self.data.clone(), - monitor_info, + monitor_info: Rc::new(monitor_info), node: None, lease_connectors: Default::default(), }); @@ -387,6 +406,9 @@ impl ConnectorHandler { if let Some(config) = self.state.config.get() { config.connector_connected(self.id); } + self.data + .head_managers + .handle_output_connected(&output_data); 'outer: loop { while let Some(event) = self.data.connector.event() { match event { diff --git a/src/utils/copyhashmap.rs b/src/utils/copyhashmap.rs index 48cc2f1a..d534e1db 100644 --- a/src/utils/copyhashmap.rs +++ b/src/utils/copyhashmap.rs @@ -66,6 +66,14 @@ impl CopyHashMap { unsafe { self.map.get().deref().contains_key(k) } } + pub fn not_contains(&self, k: &Q) -> bool + where + Q: Hash + Eq + ?Sized, + K: Borrow, + { + !self.contains(k) + } + pub fn lock(&self) -> Locked<'_, K, V> { Locked { source: self, diff --git a/src/utils/rc_eq.rs b/src/utils/rc_eq.rs index 725b3a3d..378fcc24 100644 --- a/src/utils/rc_eq.rs +++ b/src/utils/rc_eq.rs @@ -1,5 +1,29 @@ -use std::rc::Rc; +use std::{ops::Deref, rc::Rc}; pub fn rc_eq(a: &Rc, b: &Rc) -> bool { Rc::as_ptr(a) as *const u8 == Rc::as_ptr(b) as *const u8 } + +pub struct RcEq(pub Rc); + +impl Clone for RcEq { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for RcEq { + fn eq(&self, other: &Self) -> bool { + rc_eq(&self.0, &other.0) + } +} + +impl Eq for RcEq {} + +impl Deref for RcEq { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/wire/jay_head_error_v1.txt b/wire/jay_head_error_v1.txt new file mode 100644 index 00000000..48c2e1b5 --- /dev/null +++ b/wire/jay_head_error_v1.txt @@ -0,0 +1,23 @@ +request destroy (destructor) { + +} + +event message { + msg: str, +} + +event already_failed { + +} + +event out_of_date { + +} + +event extension { + name: u32, +} + +event done { + +} diff --git a/wire/jay_head_ext_core_info_v1.txt b/wire/jay_head_ext_core_info_v1.txt new file mode 100644 index 00000000..26098bfc --- /dev/null +++ b/wire/jay_head_ext_core_info_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event wl_output { + global_name: u32, +} + +event no_wl_output { + +} + +event name { + name: optstr, +} diff --git a/wire/jay_head_manager_ext_core_info_v1.txt b/wire/jay_head_manager_ext_core_info_v1.txt new file mode 100644 index 00000000..5a733921 --- /dev/null +++ b/wire/jay_head_manager_ext_core_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_core_info_v1) (new), +} diff --git a/wire/jay_head_manager_session_v1.txt b/wire/jay_head_manager_session_v1.txt new file mode 100644 index 00000000..c0c07b39 --- /dev/null +++ b/wire/jay_head_manager_session_v1.txt @@ -0,0 +1,59 @@ +request destroy (destructor) { + +} + +request bind_extension { + name: u32, + interface: str, + version: u32, + id: id(object) (new), +} + +request stop { + +} + +event stopped { + +} + +request start { + +} + +event head_start { + head: id(jay_head_v1) (new), + name: u64, +} + +event head_complete { + +} + +request begin_transaction { + +} + +request rollback_transaction { + +} + +request apply_changes { + result: id(jay_head_transaction_result_v1) (new), +} + +request test_transaction { + result: id(jay_head_transaction_result_v1) (new), +} + +request commit_transaction { + result: id(jay_head_transaction_result_v1) (new), +} + +event transaction_started { + +} + +event transaction_ended { + +} diff --git a/wire/jay_head_manager_v1.txt b/wire/jay_head_manager_v1.txt new file mode 100644 index 00000000..1c678dec --- /dev/null +++ b/wire/jay_head_manager_v1.txt @@ -0,0 +1,21 @@ +request destroy (destructor) { + +} + +event extension { + name: u32, + interface: str, + version: u32, +} + +event extensions_done { + +} + +request create_session { + session: id(jay_head_manager_session_v1) (new), +} + +event done { + +} diff --git a/wire/jay_head_transaction_result_v1.txt b/wire/jay_head_transaction_result_v1.txt new file mode 100644 index 00000000..9fd49a488 --- /dev/null +++ b/wire/jay_head_transaction_result_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event success { + +} + +event failed { + +} + +request get_error { + error: id(jay_head_error_v1) (new), +} diff --git a/wire/jay_head_v1.txt b/wire/jay_head_v1.txt new file mode 100644 index 00000000..facc42f6 --- /dev/null +++ b/wire/jay_head_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event removed { + +} From 67acc6d938f737206e8d66e4c40532d4b7a2d544 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:18:36 +0200 Subject: [PATCH 04/26] head-management: add compositor-space-info-v1 extension --- src/compositor.rs | 11 +- src/ifs/head_management.rs | 95 ++++++++++++++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_compositor_space_info_v1.rs | 113 ++++++++++++++++++ .../jay_head_manager_session_v1.rs | 29 ++++- src/state.rs | 3 + src/tasks/connector.rs | 7 ++ src/tree/output.rs | 12 ++ .../jay_head_ext_compositor_space_info_v1.txt | 37 ++++++ ...d_manager_ext_compositor_space_info_v1.txt | 7 ++ 11 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_info_v1.rs create mode 100644 wire/jay_head_ext_compositor_space_info_v1.txt create mode 100644 wire/jay_head_manager_ext_compositor_space_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index ad857998..3c307503 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -76,7 +76,10 @@ use { }, ahash::AHashSet, forker::ForkerProxy, - jay_config::{_private::DEFAULT_SEAT_NAME, video::GfxApi}, + jay_config::{ + _private::DEFAULT_SEAT_NAME, + video::{GfxApi, Transform}, + }, std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}, thiserror::Error, uapi::c, @@ -637,7 +640,13 @@ fn create_dummy_output(state: &Rc) { let head_name = state.head_names.next(); let head_state = HeadState { name: RcEq(name.clone()), + position: (0, 0), + size: (0, 0), + transform: Transform::None, + scale: Default::default(), wl_output: None, + connector_enabled: true, + in_compositor_space: false, monitor_info: None, }; let connector_data = Rc::new(ConnectorData { diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index b5a539ae..0bc38665 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -7,10 +7,13 @@ use { head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, jay_head_v1::JayHeadV1, }, + scale::Scale, state::OutputData, + tree::OutputNode, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, wire::JayHeadManagerSessionV1Id, }, + jay_config::video::Transform, std::{ cell::{Cell, RefCell}, rc::Rc, @@ -64,9 +67,33 @@ struct HeadCommon { pub struct HeadState { pub name: RcEq, pub wl_output: Option, + pub connector_enabled: bool, + pub in_compositor_space: bool, + pub position: (i32, i32), + pub size: (i32, i32), + pub transform: Transform, + pub scale: Scale, pub monitor_info: Option>, } +impl HeadState { + fn update_in_compositor_space(&mut self, wl_output: Option) { + self.in_compositor_space = false; + self.wl_output = None; + if !self.connector_enabled { + return; + } + let Some(mi) = &self.monitor_info else { + return; + }; + if mi.non_desktop { + return; + } + self.in_compositor_space = true; + self.wl_output = wl_output; + } +} + enum HeadOp {} #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -164,11 +191,18 @@ impl HeadManagers { pub fn handle_output_connected(&self, output: &OutputData) { let state = &mut *self.state.borrow_mut(); state.monitor_info = Some(RcEq(output.monitor_info.clone())); + state.update_in_compositor_space(output.node.as_ref().map(|n| n.global.name)); if let Some(n) = &output.node { - state.wl_output = Some(n.global.name); + state.position = n.global.pos.get().position(); + state.size = n.global.pos.get().size(); + state.transform = n.global.persistent.transform.get(); } for head in self.managers.lock().values() { skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_inside_outside(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.core_info_v1 { ext.send_wl_output(state); head.session.schedule_done(); @@ -178,14 +212,71 @@ impl HeadManagers { pub fn handle_output_disconnected(&self) { let state = &mut *self.state.borrow_mut(); - state.wl_output = None; state.monitor_info = None; + state.update_in_compositor_space(None); for head in self.managers.lock().values() { skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_inside_outside(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.core_info_v1 { ext.send_wl_output(state); head.session.schedule_done(); } } } + + pub fn handle_position_size_change(&self, node: &OutputNode) { + let state = &mut *self.state.borrow_mut(); + let pos = node.global.pos.get(); + state.position = pos.position(); + state.size = pos.size(); + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_position(state); + ext.send_size(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_transform_change(&self, transform: Transform) { + let state = &mut *self.state.borrow_mut(); + state.transform = transform; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_transform(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_scale_change(&self, scale: Scale) { + let state = &mut *self.state.borrow_mut(); + state.scale = scale; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_scale(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_enabled_change(&self, enabled: bool) { + let state = &mut *self.state.borrow_mut(); + state.connector_enabled = enabled; + state.update_in_compositor_space(state.wl_output); + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.compositor_space_info_v1 { + ext.send_enabled(state); + ext.send_inside_outside(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index a99a79d4..db97aef4 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -389,4 +389,5 @@ macro_rules! declare_extensions { declare_extensions! { core_info_v1: CoreInfoV1, + compositor_space_info_v1: CompositorSpaceInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index d6d1abf0..44bcd59f 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1 +1,2 @@ +pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_info_v1.rs new file mode 100644 index 00000000..acffb919 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_info_v1.rs @@ -0,0 +1,113 @@ +use { + crate::{ + ifs::head_management::HeadState, + utils::transform_ext::TransformExt, + wire::{ + jay_head_ext_compositor_space_info_v1::{ + Disabled, Enabled, Inside, JayHeadExtCompositorSpaceInfoV1RequestHandler, Outside, + Position, Scaling, Size, Transform, + }, + jay_head_manager_ext_compositor_space_info_v1::JayHeadManagerExtCompositorSpaceInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_compositor_space_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_enabled(shared); + self.send_inside_outside(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.connector_enabled != tran.connector_enabled { + self.send_enabled(shared); + } + if shared.in_compositor_space != tran.in_compositor_space { + self.send_inside_outside(shared); + } else if shared.in_compositor_space { + if shared.position != tran.position { + self.send_position(shared); + } + if shared.size != tran.size { + self.send_size(shared); + } + if shared.transform != tran.transform { + self.send_transform(shared); + } + if shared.scale != tran.scale { + self.send_scale(shared); + } + } + } + + pub(in super::super) fn send_inside_outside(&self, state: &HeadState) { + if state.in_compositor_space { + self.client.event(Inside { self_id: self.id }); + self.send_position(state); + self.send_size(state); + self.send_transform(state); + self.send_scale(state); + } else { + self.client.event(Outside { self_id: self.id }); + } + } + + pub(in super::super) fn send_enabled(&self, state: &HeadState) { + if state.connector_enabled { + self.client.event(Enabled { self_id: self.id }); + } else { + self.client.event(Disabled { self_id: self.id }); + } + } + + pub(in super::super) fn send_position(&self, state: &HeadState) { + self.client.event(Position { + self_id: self.id, + x: state.position.0, + y: state.position.1, + }); + } + + pub(in super::super) fn send_size(&self, state: &HeadState) { + self.client.event(Size { + self_id: self.id, + width: state.size.0, + height: state.size.1, + }); + } + + pub(in super::super) fn send_transform(&self, state: &HeadState) { + self.client.event(Transform { + self_id: self.id, + transform: state.transform.to_wl() as _, + }); + } + + pub(in super::super) fn send_scale(&self, state: &HeadState) { + self.client.event(Scaling { + self_id: self.id, + scaling: state.scale.to_wl(), + }); + } +} + +impl JayHeadManagerExtCompositorSpaceInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCompositorSpaceInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 022dbe55..a6ef1f89 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -374,7 +374,13 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } bitflags! { ToSend: u32; - CORE_INFO = 1 << 0, + CORE_INFO = 1 << 0, + COMPOSITOR_SPACE_INFO_FULL = 1 << 1, + COMPOSITOR_SPACE_INFO_POS = 1 << 2, + COMPOSITOR_SPACE_INFO_SIZE = 1 << 3, + COMPOSITOR_SPACE_INFO_TRANSFORM = 1 << 4, + COMPOSITOR_SPACE_INFO_SCALE = 1 << 5, + COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { let pending = mem::take(&mut *head.common.pending.borrow_mut()); @@ -392,6 +398,27 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_wl_output(state); } + if let Some(i) = &head.ext.compositor_space_info_v1 { + if to_send.contains(COMPOSITOR_SPACE_INFO_ENABLED) { + i.send_enabled(state); + } + if to_send.contains(COMPOSITOR_SPACE_INFO_FULL) { + i.send_inside_outside(state); + } else { + if to_send.contains(COMPOSITOR_SPACE_INFO_POS) { + i.send_position(state); + } + if to_send.contains(COMPOSITOR_SPACE_INFO_SIZE) { + i.send_size(state); + } + if to_send.contains(COMPOSITOR_SPACE_INFO_TRANSFORM) { + i.send_transform(state); + } + if to_send.contains(COMPOSITOR_SPACE_INFO_SCALE) { + i.send_scale(state); + } + } + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) diff --git a/src/state.rs b/src/state.rs index 51f4951d..f708ef92 100644 --- a/src/state.rs +++ b/src/state.rs @@ -443,6 +443,9 @@ impl ConnectorData { return; } self.state.set(s); + if old.enabled != s.enabled { + self.head_managers.handle_enabled_change(s.enabled); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 555d2900..203f3049 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -18,6 +18,7 @@ use { asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt, rc_eq::RcEq, }, }, + jay_config::video::Transform, std::{cell::Cell, collections::VecDeque, rc::Rc}, }; @@ -45,7 +46,13 @@ pub fn handle(state: &Rc, connector: &Rc) { let name = Rc::new(connector.kernel_id().to_string()); let head_state = HeadState { name: RcEq(name.clone()), + position: (0, 0), + size: (0, 0), + transform: Transform::None, + scale: Default::default(), wl_output: None, + connector_enabled: backend_state.enabled, + in_compositor_space: false, monitor_info: None, }; let data = Rc::new(ConnectorData { diff --git a/src/tree/output.rs b/src/tree/output.rs index 60c091ba..cb06b12e 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -470,6 +470,10 @@ impl OutputNode { } } self.schedule_update_render_data(); + self.global + .connector + .head_managers + .handle_scale_change(scale); } pub fn schedule_update_render_data(self: &Rc) { @@ -800,6 +804,10 @@ impl OutputNode { if transform != old_transform { self.state.refresh_hardware_cursors(); self.node_visit_children(&mut SurfaceSendPreferredTransformVisitor); + self.global + .connector + .head_managers + .handle_transform_change(transform); } } @@ -847,6 +855,10 @@ impl OutputNode { seat.cursor_group().output_pos_changed(self) } self.state.tree_changed(); + self.global + .connector + .head_managers + .handle_position_size_change(self); } pub fn update_state(self: &Rc, old: BackendConnectorState, state: BackendConnectorState) { diff --git a/wire/jay_head_ext_compositor_space_info_v1.txt b/wire/jay_head_ext_compositor_space_info_v1.txt new file mode 100644 index 00000000..562895b4 --- /dev/null +++ b/wire/jay_head_ext_compositor_space_info_v1.txt @@ -0,0 +1,37 @@ +request destroy (destructor) { + +} + +event enabled { + +} + +event disabled { + +} + +event inside { + +} + +event outside { + +} + +event position { + x: i32, + y: i32, +} + +event size { + width: i32, + height: i32, +} + +event transform { + transform: u32, +} + +event scaling { + scaling: u32, +} diff --git a/wire/jay_head_manager_ext_compositor_space_info_v1.txt b/wire/jay_head_manager_ext_compositor_space_info_v1.txt new file mode 100644 index 00000000..a86df828 --- /dev/null +++ b/wire/jay_head_manager_ext_compositor_space_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_compositor_space_info_v1) (new), +} From a12b2596485ca906d11da8eb4a64b8b963fab6be Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:21:38 +0200 Subject: [PATCH 05/26] head-management: add compositor-space-positioner-v1 extension --- src/ifs/head_management.rs | 5 +- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + ...head_ext_compositor_space_positioner_v1.rs | 60 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 16 +++-- ...ead_ext_compositor_space_positioner_v1.txt | 15 +++++ ...ger_ext_compositor_space_positioner_v1.txt | 7 +++ 7 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_positioner_v1.rs create mode 100644 wire/jay_head_ext_compositor_space_positioner_v1.txt create mode 100644 wire/jay_head_manager_ext_compositor_space_positioner_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 0bc38665..db0396c7 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -94,7 +94,9 @@ impl HeadState { } } -enum HeadOp {} +enum HeadOp { + SetPosition(i32, i32), +} #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] enum HeadMgrState { @@ -128,7 +130,6 @@ impl HeadCommon { } } - #[expect(dead_code)] fn push_op(&self, op: HeadOp) -> Result<(), HeadCommonError> { self.assert_in_transaction()?; self.pending.borrow_mut().push(op); diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index db97aef4..72dcd2f2 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -390,4 +390,5 @@ macro_rules! declare_extensions { declare_extensions! { core_info_v1: CoreInfoV1, compositor_space_info_v1: CompositorSpaceInfoV1, + compositor_space_positioner_v1: CompositorSpacePositionerV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 44bcd59f..544be3e6 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,2 +1,3 @@ pub(super) mod jay_head_ext_compositor_space_info_v1; +pub(super) mod jay_head_ext_compositor_space_positioner_v1; pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_positioner_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_positioner_v1.rs new file mode 100644 index 00000000..a171c14d --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_positioner_v1.rs @@ -0,0 +1,60 @@ +use { + crate::{ + compositor::MAX_EXTENTS, + ifs::head_management::{HeadOp, HeadState}, + wire::{ + jay_head_ext_compositor_space_positioner_v1::{ + JayHeadExtCompositorSpacePositionerV1RequestHandler, Range, SetPosition, + }, + jay_head_manager_ext_compositor_space_positioner_v1::JayHeadManagerExtCompositorSpacePositionerV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_compositor_space_positioner_v1! { + version = 1, + after_announce = after_announce, +} + +impl HeadName { + fn after_announce(&self, _shared: &HeadState) { + self.send_range(); + } + + fn send_range(&self) { + self.client.event(Range { + self_id: self.id, + x_min: 0, + y_min: 0, + x_max: MAX_EXTENTS, + y_max: MAX_EXTENTS, + }) + } +} + +impl JayHeadManagerExtCompositorSpacePositionerV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCompositorSpacePositionerV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_position(&self, req: SetPosition, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_in_transaction()?; + if req.x < 0 || req.x > MAX_EXTENTS || req.y < 0 || req.y > MAX_EXTENTS { + return Err(JayHeadExtCompositorSpacePositionerV1Error::PositionOutOfBounds); + } + self.common.push_op(HeadOp::SetPosition(req.x, req.y))?; + Ok(()) + } +} + +error! { + #[error("The position is out of bounds")] + PositionOutOfBounds, +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index a6ef1f89..df58a6a3 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -5,7 +5,8 @@ use { client::{Client, ClientError}, ifs::{ head_management::{ - Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadTransactionError, + Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadOp, + HeadTransactionError, head_management_macros::{HeadExtension, MgrExts, announce_head, bind_extension}, jay_head_manager_v1::JayHeadManagerV1, jay_head_transaction_result_v1::JayHeadTransactionResultV1, @@ -387,11 +388,14 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { #[expect(unused_variables)] let snapshot = &*head.common.snapshot_state.borrow(); let state = &mut *head.common.transaction_state.borrow_mut(); - #[expect(unused_mut)] let mut to_send = ToSend::default(); - #[expect(clippy::never_loop)] for op in pending { - match op {} + match op { + HeadOp::SetPosition(x, y) => { + state.position = (x, y); + to_send |= COMPOSITOR_SPACE_INFO_POS; + } + } } if to_send.contains(CORE_INFO) && let Some(i) = &head.ext.core_info_v1 @@ -451,7 +455,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { if let Some(output) = self.client.state.outputs.get(&head.common.id) && let Some(node) = &output.node { - let _ = node; + node.set_position(desired.position.0, desired.position.1); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -470,7 +474,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { ps } }; - let _ = pos; + pos.pos.set(desired.position); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/wire/jay_head_ext_compositor_space_positioner_v1.txt b/wire/jay_head_ext_compositor_space_positioner_v1.txt new file mode 100644 index 00000000..68370d81 --- /dev/null +++ b/wire/jay_head_ext_compositor_space_positioner_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +request set_position { + x: i32, + y: i32, +} + +event range { + x_min: i32, + y_min: i32, + x_max: i32, + y_max: i32, +} diff --git a/wire/jay_head_manager_ext_compositor_space_positioner_v1.txt b/wire/jay_head_manager_ext_compositor_space_positioner_v1.txt new file mode 100644 index 00000000..1916e31f --- /dev/null +++ b/wire/jay_head_manager_ext_compositor_space_positioner_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_compositor_space_positioner_v1) (new), +} From a8ed4785af20e8f9d1411a24c115c6375648923f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:24:45 +0200 Subject: [PATCH 06/26] head-management: add compositor-space-scaler-v1 extension --- src/compositor.rs | 1 + src/ifs/head_management.rs | 12 +++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + ...jay_head_ext_compositor_space_scaler_v1.rs | 62 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 8 +++ src/scale.rs | 4 +- src/tasks/connector.rs | 1 + src/tree/output.rs | 20 ++++-- ...ay_head_ext_compositor_space_scaler_v1.txt | 12 ++++ ...manager_ext_compositor_space_scaler_v1.txt | 7 +++ 11 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs create mode 100644 wire/jay_head_ext_compositor_space_scaler_v1.txt create mode 100644 wire/jay_head_manager_ext_compositor_space_scaler_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 3c307503..8ef2f6f9 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -647,6 +647,7 @@ fn create_dummy_output(state: &Rc) { wl_output: None, connector_enabled: true, in_compositor_space: false, + mode: Default::default(), monitor_info: None, }; let connector_data = Rc::new(ConnectorData { diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index db0396c7..920b3eb3 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::{ConnectorId, MonitorInfo, transaction::BackendConnectorTransactionError}, + backend::{ConnectorId, Mode, MonitorInfo, transaction::BackendConnectorTransactionError}, client::ClientId, globals::GlobalName, ifs::head_management::{ @@ -71,6 +71,7 @@ pub struct HeadState { pub in_compositor_space: bool, pub position: (i32, i32), pub size: (i32, i32), + pub mode: Mode, pub transform: Transform, pub scale: Scale, pub monitor_info: Option>, @@ -92,10 +93,17 @@ impl HeadState { self.in_compositor_space = true; self.wl_output = wl_output; } + + fn update_size(&mut self) { + self.size = + OutputNode::calculate_extents_(self.mode, self.transform, self.scale, self.position) + .size(); + } } enum HeadOp { SetPosition(i32, i32), + SetScale(Scale), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -196,6 +204,7 @@ impl HeadManagers { if let Some(n) = &output.node { state.position = n.global.pos.get().position(); state.size = n.global.pos.get().size(); + state.mode = n.global.mode.get(); state.transform = n.global.persistent.transform.get(); } for head in self.managers.lock().values() { @@ -233,6 +242,7 @@ impl HeadManagers { let pos = node.global.pos.get(); state.position = pos.position(); state.size = pos.size(); + state.mode = node.global.mode.get(); for head in self.managers.lock().values() { skip_in_transaction!(head); if let Some(ext) = &head.ext.compositor_space_info_v1 { diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 72dcd2f2..031c33a8 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -391,4 +391,5 @@ declare_extensions! { core_info_v1: CoreInfoV1, compositor_space_info_v1: CompositorSpaceInfoV1, compositor_space_positioner_v1: CompositorSpacePositionerV1, + compositor_space_scaler_v1: CompositorSpaceScalerV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 544be3e6..fbea35cc 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,3 +1,4 @@ pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; +pub(super) mod jay_head_ext_compositor_space_scaler_v1; pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs new file mode 100644 index 00000000..6ac99aa9 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs @@ -0,0 +1,62 @@ +use { + crate::{ + ifs::head_management::{HeadOp, HeadState}, + scale::Scale, + wire::{ + jay_head_ext_compositor_space_scaler_v1::{ + JayHeadExtCompositorSpaceScalerV1RequestHandler, Range, SetScale, + }, + jay_head_manager_ext_compositor_space_scaler_v1::JayHeadManagerExtCompositorSpaceScalerV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_compositor_space_scaler_v1! { + version = 1, + after_announce = after_announce, +} + +const MIN_SCALE: Scale = Scale::from_wl(60); +const MAX_SCALE: Scale = Scale::from_int(16); + +impl HeadName { + fn after_announce(&self, _shared: &HeadState) { + self.send_range(MIN_SCALE, MAX_SCALE); + } + + fn send_range(&self, min: Scale, max: Scale) { + self.client.event(Range { + self_id: self.id, + min: min.to_wl(), + max: max.to_wl(), + }); + } +} + +impl JayHeadManagerExtCompositorSpaceScalerV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCompositorSpaceScalerV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_scale(&self, req: SetScale, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_in_transaction()?; + let scale = Scale::from_wl(req.scale); + if scale < MIN_SCALE || scale > MAX_SCALE { + return Err(JayHeadExtCompositorSpaceScalerV1Error::ScaleOutOfBounds); + } + self.common.push_op(HeadOp::SetScale(scale))?; + Ok(()) + } +} + +error! { + #[error("The scale is out of bounds")] + ScaleOutOfBounds, +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index df58a6a3..107bb6c3 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -395,6 +395,12 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.position = (x, y); to_send |= COMPOSITOR_SPACE_INFO_POS; } + HeadOp::SetScale(s) => { + state.scale = s; + state.update_size(); + to_send |= COMPOSITOR_SPACE_INFO_SCALE; + to_send |= COMPOSITOR_SPACE_INFO_SIZE; + } } } if to_send.contains(CORE_INFO) @@ -456,6 +462,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { && let Some(node) = &output.node { node.set_position(desired.position.0, desired.position.1); + node.set_preferred_scale(desired.scale); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -475,6 +482,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } }; pos.pos.set(desired.position); + pos.scale.set(desired.scale); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/src/scale.rs b/src/scale.rs index b937faa4..63f06f81 100644 --- a/src/scale.rs +++ b/src/scale.rs @@ -15,7 +15,7 @@ impl Default for Scale { } impl Scale { - pub fn from_int(f: u32) -> Self { + pub const fn from_int(f: u32) -> Self { Self(f.saturating_mul(BASE)) } @@ -31,7 +31,7 @@ impl Scale { self.0.saturating_add(BASE - 1) / BASE } - pub fn from_wl(wl: u32) -> Self { + pub const fn from_wl(wl: u32) -> Self { Self(wl) } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 203f3049..49e07403 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -53,6 +53,7 @@ pub fn handle(state: &Rc, connector: &Rc) { wl_output: None, connector_enabled: backend_state.enabled, in_compositor_space: false, + mode: Default::default(), monitor_info: None, }; let data = Rc::new(ConnectorData { diff --git a/src/tree/output.rs b/src/tree/output.rs index cb06b12e..e20e6256 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -812,14 +812,22 @@ impl OutputNode { } fn calculate_extents(&self) -> Rect { - let mode = self.global.mode.get(); - let (width, height) = calculate_logical_size( - (mode.width, mode.height), + Self::calculate_extents_( + self.global.mode.get(), self.global.persistent.transform.get(), self.global.persistent.scale.get(), - ); - let pos = self.global.pos.get(); - pos.with_size(width, height).unwrap() + 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(pos.0, pos.1, width, height).unwrap() } fn change_extents_(self: &Rc, rect: &Rect) { diff --git a/wire/jay_head_ext_compositor_space_scaler_v1.txt b/wire/jay_head_ext_compositor_space_scaler_v1.txt new file mode 100644 index 00000000..249a721e --- /dev/null +++ b/wire/jay_head_ext_compositor_space_scaler_v1.txt @@ -0,0 +1,12 @@ +request destroy (destructor) { + +} + +request set_scale { + scale: u32, +} + +event range { + min: u32, + max: u32, +} diff --git a/wire/jay_head_manager_ext_compositor_space_scaler_v1.txt b/wire/jay_head_manager_ext_compositor_space_scaler_v1.txt new file mode 100644 index 00000000..7c3b5ab4 --- /dev/null +++ b/wire/jay_head_manager_ext_compositor_space_scaler_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_compositor_space_scaler_v1) (new), +} From 5636f6bda5d217c7976cbae5bcb359ddafff3b1a Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:27:08 +0200 Subject: [PATCH 07/26] head-management: add compositor-space-transformer-v1 extension --- src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + ...ead_ext_compositor_space_transformer_v1.rs | 66 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 8 +++ ...ad_ext_compositor_space_transformer_v1.txt | 15 +++++ ...er_ext_compositor_space_transformer_v1.txt | 7 ++ 7 files changed, 99 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_transformer_v1.rs create mode 100644 wire/jay_head_ext_compositor_space_transformer_v1.txt create mode 100644 wire/jay_head_manager_ext_compositor_space_transformer_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 920b3eb3..33b65fc9 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -103,6 +103,7 @@ impl HeadState { enum HeadOp { SetPosition(i32, i32), + SetTransform(Transform), SetScale(Scale), } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 031c33a8..dfc5a720 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -391,5 +391,6 @@ declare_extensions! { core_info_v1: CoreInfoV1, compositor_space_info_v1: CompositorSpaceInfoV1, compositor_space_positioner_v1: CompositorSpacePositionerV1, + compositor_space_transformer_v1: CompositorSpaceTransformerV1, compositor_space_scaler_v1: CompositorSpaceScalerV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index fbea35cc..9cdf85d8 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,4 +1,5 @@ pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; pub(super) mod jay_head_ext_compositor_space_scaler_v1; +pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_transformer_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_transformer_v1.rs new file mode 100644 index 00000000..4535c7af --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_transformer_v1.rs @@ -0,0 +1,66 @@ +use { + crate::{ + ifs::head_management::{HeadOp, HeadState}, + utils::transform_ext::TransformExt, + wire::{ + jay_head_ext_compositor_space_transformer_v1::{ + JayHeadExtCompositorSpaceTransformerV1RequestHandler, SetTransform, + SupportedTransform, + }, + jay_head_manager_ext_compositor_space_transformer_v1::JayHeadManagerExtCompositorSpaceTransformerV1RequestHandler, + }, + }, + jay_config::video::Transform, + std::rc::Rc, +}; + +impl_compositor_space_transformer_v1! { + version = 1, + after_announce = after_announce, +} + +impl HeadName { + fn after_announce(&self, _shared: &HeadState) { + self.send_supported_transform(Transform::None); + self.send_supported_transform(Transform::Rotate90); + self.send_supported_transform(Transform::Rotate180); + self.send_supported_transform(Transform::Rotate270); + self.send_supported_transform(Transform::Flip); + self.send_supported_transform(Transform::FlipRotate90); + self.send_supported_transform(Transform::FlipRotate180); + self.send_supported_transform(Transform::FlipRotate270); + } + + fn send_supported_transform(&self, transform: Transform) { + self.client.event(SupportedTransform { + self_id: self.id, + transform: transform.to_wl() as _, + }); + } +} + +impl JayHeadManagerExtCompositorSpaceTransformerV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCompositorSpaceTransformerV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_transform(&self, req: SetTransform, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_in_transaction()?; + let Some(transform) = Transform::from_wl(req.transform as _) else { + return Err(ErrorName::UnknownTransform(req.transform)); + }; + self.common.push_op(HeadOp::SetTransform(transform))?; + Ok(()) + } +} + +error! { + #[error("Unknown transform {0}")] + UnknownTransform(u32), +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 107bb6c3..e8dc9fb7 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -395,6 +395,12 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.position = (x, y); to_send |= COMPOSITOR_SPACE_INFO_POS; } + HeadOp::SetTransform(t) => { + state.transform = t; + state.update_size(); + to_send |= COMPOSITOR_SPACE_INFO_TRANSFORM; + to_send |= COMPOSITOR_SPACE_INFO_SIZE; + } HeadOp::SetScale(s) => { state.scale = s; state.update_size(); @@ -463,6 +469,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { node.set_position(desired.position.0, desired.position.1); node.set_preferred_scale(desired.scale); + node.update_transform(desired.transform); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -483,6 +490,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { }; pos.pos.set(desired.position); pos.scale.set(desired.scale); + pos.transform.set(desired.transform); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/wire/jay_head_ext_compositor_space_transformer_v1.txt b/wire/jay_head_ext_compositor_space_transformer_v1.txt new file mode 100644 index 00000000..160b005d --- /dev/null +++ b/wire/jay_head_ext_compositor_space_transformer_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +request set_transform { + transform: u32, +} + +event reset_supported_transforms { + +} + +event supported_transform { + transform: u32, +} diff --git a/wire/jay_head_manager_ext_compositor_space_transformer_v1.txt b/wire/jay_head_manager_ext_compositor_space_transformer_v1.txt new file mode 100644 index 00000000..ea4badd4 --- /dev/null +++ b/wire/jay_head_manager_ext_compositor_space_transformer_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_compositor_space_transformer_v1) (new), +} From 51170cbde5c24baf365f42af55e379c408f64d82 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:31:12 +0200 Subject: [PATCH 08/26] head-management: add compositor-space-enabler-v1 extension --- src/backend.rs | 1 - src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + ...ay_head_ext_compositor_space_enabler_v1.rs | 49 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 10 +++- ...y_head_ext_compositor_space_enabler_v1.txt | 11 +++++ ...anager_ext_compositor_space_enabler_v1.txt | 7 +++ 8 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_enabler_v1.rs create mode 100644 wire/jay_head_ext_compositor_space_enabler_v1.txt create mode 100644 wire/jay_head_manager_ext_compositor_space_enabler_v1.txt diff --git a/src/backend.rs b/src/backend.rs index 08b14ad2..7b5b6b73 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -139,7 +139,6 @@ pub trait Connector: Any { fn damage(&self); fn drm_dev(&self) -> Option; fn effectively_locked(&self) -> bool; - #[expect(dead_code)] fn caps(&self) -> ConnectorCaps { ConnectorCaps::none() } diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 33b65fc9..a1e470fa 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -103,6 +103,7 @@ impl HeadState { enum HeadOp { SetPosition(i32, i32), + SetConnectorEnabled(bool), SetTransform(Transform), SetScale(Scale), } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index dfc5a720..52d6c99a 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -393,4 +393,5 @@ declare_extensions! { compositor_space_positioner_v1: CompositorSpacePositionerV1, compositor_space_transformer_v1: CompositorSpaceTransformerV1, compositor_space_scaler_v1: CompositorSpaceScalerV1, + compositor_space_enabler_v1: CompositorSpaceEnablerV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 9cdf85d8..2d5dda28 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,3 +1,4 @@ +pub(super) mod jay_head_ext_compositor_space_enabler_v1; pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; pub(super) mod jay_head_ext_compositor_space_scaler_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_enabler_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_enabler_v1.rs new file mode 100644 index 00000000..75179c74 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_enabler_v1.rs @@ -0,0 +1,49 @@ +use { + crate::{ + backend::CONCAP_CONNECTOR, + ifs::head_management::{HeadCommon, HeadOp}, + state::ConnectorData, + wire::{ + jay_head_ext_compositor_space_enabler_v1::{ + Disable, Enable, JayHeadExtCompositorSpaceEnablerV1RequestHandler, + }, + jay_head_manager_ext_compositor_space_enabler_v1::JayHeadManagerExtCompositorSpaceEnablerV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_compositor_space_enabler_v1! { + version = 1, + filter = filter, +} + +impl MgrName { + fn filter(&self, connector: &ConnectorData, _common: &Rc) -> bool { + connector.connector.caps().contains(CONCAP_CONNECTOR) + } +} + +impl JayHeadManagerExtCompositorSpaceEnablerV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCompositorSpaceEnablerV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn enable(&self, _req: Enable, _slf: &Rc) -> Result<(), Self::Error> { + self.common.push_op(HeadOp::SetConnectorEnabled(true))?; + Ok(()) + } + + fn disable(&self, _req: Disable, _slf: &Rc) -> Result<(), Self::Error> { + self.common.push_op(HeadOp::SetConnectorEnabled(false))?; + Ok(()) + } +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index e8dc9fb7..cb6be4a7 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -262,8 +262,8 @@ impl JayHeadManagerSessionV1 { return Err(HeadTransactionError::HeadRemoved(head.common.id)); }; let old = connector.state.get(); - #[expect(unused_mut)] let mut new = old; + new.enabled = desired.connector_enabled; if old == new { continue; } @@ -385,7 +385,6 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } for head in self.heads.lock().values() { let pending = mem::take(&mut *head.common.pending.borrow_mut()); - #[expect(unused_variables)] let snapshot = &*head.common.snapshot_state.borrow(); let state = &mut *head.common.transaction_state.borrow_mut(); let mut to_send = ToSend::default(); @@ -395,6 +394,13 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.position = (x, y); to_send |= COMPOSITOR_SPACE_INFO_POS; } + HeadOp::SetConnectorEnabled(enabled) => { + state.connector_enabled = enabled; + state.update_in_compositor_space(snapshot.wl_output); + to_send |= COMPOSITOR_SPACE_INFO_FULL; + to_send |= COMPOSITOR_SPACE_INFO_ENABLED; + to_send |= CORE_INFO; + } HeadOp::SetTransform(t) => { state.transform = t; state.update_size(); diff --git a/wire/jay_head_ext_compositor_space_enabler_v1.txt b/wire/jay_head_ext_compositor_space_enabler_v1.txt new file mode 100644 index 00000000..619bd395 --- /dev/null +++ b/wire/jay_head_ext_compositor_space_enabler_v1.txt @@ -0,0 +1,11 @@ +request destroy (destructor) { + +} + +request enable { + +} + +request disable { + +} diff --git a/wire/jay_head_manager_ext_compositor_space_enabler_v1.txt b/wire/jay_head_manager_ext_compositor_space_enabler_v1.txt new file mode 100644 index 00000000..b44c55a4 --- /dev/null +++ b/wire/jay_head_manager_ext_compositor_space_enabler_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_compositor_space_enabler_v1) (new), +} From e24ea3373466e5a794908baff40889855ab5437d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:29:13 +0200 Subject: [PATCH 09/26] head-management: add connector-info-v1 extension --- src/compositor.rs | 2 + src/ifs/head_management.rs | 24 ++++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_connector_info_v1.rs | 73 +++++++++++++++++++ src/state.rs | 3 + src/tasks/connector.rs | 2 + wire/jay_head_ext_connector_info_v1.txt | 19 +++++ ...jay_head_manager_ext_connector_info_v1.txt | 7 ++ 9 files changed, 132 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_connector_info_v1.rs create mode 100644 wire/jay_head_ext_connector_info_v1.txt create mode 100644 wire/jay_head_manager_ext_connector_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 8ef2f6f9..7967ed46 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -642,6 +642,8 @@ fn create_dummy_output(state: &Rc) { name: RcEq(name.clone()), position: (0, 0), size: (0, 0), + active: false, + connected: false, transform: Transform::None, scale: Default::default(), wl_output: None, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index a1e470fa..162d8e42 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -68,6 +68,8 @@ pub struct HeadState { pub name: RcEq, pub wl_output: Option, pub connector_enabled: bool, + pub active: bool, + pub connected: bool, pub in_compositor_space: bool, pub position: (i32, i32), pub size: (i32, i32), @@ -201,6 +203,7 @@ impl HeadManagers { pub fn handle_output_connected(&self, output: &OutputData) { let state = &mut *self.state.borrow_mut(); + state.connected = true; state.monitor_info = Some(RcEq(output.monitor_info.clone())); state.update_in_compositor_space(output.node.as_ref().map(|n| n.global.name)); if let Some(n) = &output.node { @@ -211,6 +214,10 @@ impl HeadManagers { } for head in self.managers.lock().values() { skip_in_transaction!(head); + if let Some(ext) = &head.ext.connector_info_v1 { + ext.send_connected(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.compositor_space_info_v1 { ext.send_inside_outside(state); head.session.schedule_done(); @@ -224,6 +231,7 @@ impl HeadManagers { pub fn handle_output_disconnected(&self) { let state = &mut *self.state.borrow_mut(); + state.connected = false; state.monitor_info = None; state.update_in_compositor_space(None); for head in self.managers.lock().values() { @@ -232,6 +240,10 @@ impl HeadManagers { ext.send_inside_outside(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.connector_info_v1 { + ext.send_connected(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.core_info_v1 { ext.send_wl_output(state); head.session.schedule_done(); @@ -292,4 +304,16 @@ impl HeadManagers { } } } + + pub fn handle_active_change(&self, active: bool) { + let state = &mut *self.state.borrow_mut(); + state.active = active; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.connector_info_v1 { + ext.send_active(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 52d6c99a..db079acf 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -394,4 +394,5 @@ declare_extensions! { compositor_space_transformer_v1: CompositorSpaceTransformerV1, compositor_space_scaler_v1: CompositorSpaceScalerV1, compositor_space_enabler_v1: CompositorSpaceEnablerV1, + connector_info_v1: ConnectorInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 2d5dda28..29d69415 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -3,4 +3,5 @@ pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; pub(super) mod jay_head_ext_compositor_space_scaler_v1; pub(super) mod jay_head_ext_compositor_space_transformer_v1; +pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_connector_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_connector_info_v1.rs new file mode 100644 index 00000000..5cb8da3f --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_connector_info_v1.rs @@ -0,0 +1,73 @@ +use { + crate::{ + backend::CONCAP_CONNECTOR, + ifs::head_management::{HeadCommon, HeadState}, + state::ConnectorData, + wire::{ + jay_head_ext_connector_info_v1::{ + Active, Connected, Disconnected, Inactive, JayHeadExtConnectorInfoV1RequestHandler, + }, + jay_head_manager_ext_connector_info_v1::JayHeadManagerExtConnectorInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_connector_info_v1! { + version = 1, + filter = filter, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl MgrName { + fn filter(&self, connector: &ConnectorData, _common: &Rc) -> bool { + connector.connector.caps().contains(CONCAP_CONNECTOR) + } +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_connected(shared); + self.send_active(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.connected != tran.connected { + self.send_connected(shared); + } + if shared.active != tran.active { + self.send_active(shared); + } + } + + pub(in super::super) fn send_connected(&self, state: &HeadState) { + if state.connected { + self.client.event(Connected { self_id: self.id }); + } else { + self.client.event(Disconnected { self_id: self.id }); + } + } + + pub(in super::super) fn send_active(&self, state: &HeadState) { + if state.active { + self.client.event(Active { self_id: self.id }); + } else { + self.client.event(Inactive { self_id: self.id }); + } + } +} + +impl JayHeadManagerExtConnectorInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtConnectorInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/state.rs b/src/state.rs index f708ef92..1e79ca79 100644 --- a/src/state.rs +++ b/src/state.rs @@ -446,6 +446,9 @@ impl ConnectorData { if old.enabled != s.enabled { self.head_managers.handle_enabled_change(s.enabled); } + if old.active != s.active { + self.head_managers.handle_active_change(s.active); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 49e07403..b453da99 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -48,6 +48,8 @@ pub fn handle(state: &Rc, connector: &Rc) { name: RcEq(name.clone()), position: (0, 0), size: (0, 0), + active: backend_state.active, + connected: false, transform: Transform::None, scale: Default::default(), wl_output: None, diff --git a/wire/jay_head_ext_connector_info_v1.txt b/wire/jay_head_ext_connector_info_v1.txt new file mode 100644 index 00000000..d17f1af1 --- /dev/null +++ b/wire/jay_head_ext_connector_info_v1.txt @@ -0,0 +1,19 @@ +request destroy (destructor) { + +} + +event connected { + +} + +event disconnected { + +} + +event active { + +} + +event inactive { + +} diff --git a/wire/jay_head_manager_ext_connector_info_v1.txt b/wire/jay_head_manager_ext_connector_info_v1.txt new file mode 100644 index 00000000..ef85fc1a --- /dev/null +++ b/wire/jay_head_manager_ext_connector_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_connector_info_v1) (new), +} From c25ddc8b5b0825d9b536a16aea83f2cab61c54fd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:32:38 +0200 Subject: [PATCH 10/26] head-management: add mode-info-v1 extension --- src/ifs/head_management.rs | 8 +++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext/jay_head_ext_mode_info_v1.rs | 51 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 6 +++ wire/jay_head_ext_mode_info_v1.txt | 9 ++++ wire/jay_head_manager_ext_mode_info_v1.txt | 7 +++ 7 files changed, 83 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_mode_info_v1.rs create mode 100644 wire/jay_head_ext_mode_info_v1.txt create mode 100644 wire/jay_head_manager_ext_mode_info_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 162d8e42..93a224c6 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -218,6 +218,10 @@ impl HeadManagers { ext.send_connected(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.compositor_space_info_v1 { ext.send_inside_outside(state); head.session.schedule_done(); @@ -264,6 +268,10 @@ impl HeadManagers { ext.send_size(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index db079acf..54b10996 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -395,4 +395,5 @@ declare_extensions! { compositor_space_scaler_v1: CompositorSpaceScalerV1, compositor_space_enabler_v1: CompositorSpaceEnablerV1, connector_info_v1: ConnectorInfoV1, + mode_info_v1: ModeInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 29d69415..af8d366a 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -5,3 +5,4 @@ pub(super) mod jay_head_ext_compositor_space_scaler_v1; pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; +pub(super) mod jay_head_ext_mode_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_info_v1.rs new file mode 100644 index 00000000..75602030 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_info_v1.rs @@ -0,0 +1,51 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_mode_info_v1::{JayHeadExtModeInfoV1RequestHandler, Mode}, + jay_head_manager_ext_mode_info_v1::JayHeadManagerExtModeInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_mode_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_mode(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.mode != tran.mode { + self.send_mode(shared); + } + } + + pub(in super::super) fn send_mode(&self, state: &HeadState) { + self.client.event(Mode { + self_id: self.id, + width: state.mode.width, + height: state.mode.height, + refresh_mhz: state.mode.refresh_rate_millihz, + }) + } +} + +impl JayHeadManagerExtModeInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtModeInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index cb6be4a7..82c6ea38 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -381,6 +381,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { COMPOSITOR_SPACE_INFO_SIZE = 1 << 3, COMPOSITOR_SPACE_INFO_TRANSFORM = 1 << 4, COMPOSITOR_SPACE_INFO_SCALE = 1 << 5, + MODE_INFO = 1 << 6, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -441,6 +442,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { } } } + if to_send.contains(MODE_INFO) + && let Some(i) = &head.ext.mode_info_v1 + { + i.send_mode(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) diff --git a/wire/jay_head_ext_mode_info_v1.txt b/wire/jay_head_ext_mode_info_v1.txt new file mode 100644 index 00000000..600e5eff --- /dev/null +++ b/wire/jay_head_ext_mode_info_v1.txt @@ -0,0 +1,9 @@ +request destroy (destructor) { + +} + +event mode { + width: i32, + height: i32, + refresh_mhz: u32, +} diff --git a/wire/jay_head_manager_ext_mode_info_v1.txt b/wire/jay_head_manager_ext_mode_info_v1.txt new file mode 100644 index 00000000..b1b5ec92 --- /dev/null +++ b/wire/jay_head_manager_ext_mode_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_mode_info_v1) (new), +} From 7e69a80dbcf3381a19cb212440a84b263b946802 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:33:44 +0200 Subject: [PATCH 11/26] head-management: add mode-setter-v1 extension --- src/ifs/head_management.rs | 5 ++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_mode_setter_v1.rs | 90 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 7 ++ wire/jay_head_ext_mode_setter_v1.txt | 17 ++++ wire/jay_head_manager_ext_mode_setter_v1.txt | 7 ++ 7 files changed, 128 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_mode_setter_v1.rs create mode 100644 wire/jay_head_ext_mode_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_mode_setter_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 93a224c6..2333ff88 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -108,6 +108,7 @@ enum HeadOp { SetConnectorEnabled(bool), SetTransform(Transform), SetScale(Scale), + SetMode(usize), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -222,6 +223,10 @@ impl HeadManagers { ext.send_mode(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.mode_setter_v1 { + ext.send_modes(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.compositor_space_info_v1 { ext.send_inside_outside(state); head.session.schedule_done(); diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 54b10996..1149819a 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -396,4 +396,5 @@ declare_extensions! { compositor_space_enabler_v1: CompositorSpaceEnablerV1, connector_info_v1: ConnectorInfoV1, mode_info_v1: ModeInfoV1, + mode_setter_v1: ModeSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index af8d366a..427b5ea5 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -6,3 +6,4 @@ pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_mode_info_v1; +pub(super) mod jay_head_ext_mode_setter_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_setter_v1.rs new file mode 100644 index 00000000..7fb64c2f --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_mode_setter_v1.rs @@ -0,0 +1,90 @@ +use { + crate::{ + backend::CONCAP_MODE_SETTING, + ifs::head_management::{HeadCommon, HeadOp, HeadState}, + state::ConnectorData, + wire::{ + jay_head_ext_mode_setter_v1::{ + JayHeadExtModeSetterV1RequestHandler, Mode, Reset, SetMode, + }, + jay_head_manager_ext_mode_setter_v1::JayHeadManagerExtModeSetterV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_mode_setter_v1! { + version = 1, + filter = filter, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl MgrName { + fn filter(&self, connector: &ConnectorData, _common: &Rc) -> bool { + connector.connector.caps().contains(CONCAP_MODE_SETTING) + } +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_modes(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + match (&shared.monitor_info, &tran.monitor_info) { + (Some(s), Some(t)) if s != t => {} + _ => return, + } + self.send_modes(shared); + } + + pub(in super::super) fn send_modes(&self, state: &HeadState) { + self.client.event(Reset { self_id: self.id }); + if let Some(mi) = &state.monitor_info { + for mode in &mi.modes { + self.client.event(Mode { + self_id: self.id, + width: mode.width, + height: mode.height, + refresh_mhz: mode.refresh_rate_millihz, + }) + } + } + } +} + +impl JayHeadManagerExtModeSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtModeSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_mode(&self, req: SetMode, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_in_transaction()?; + let num_modes = self + .common + .snapshot_state + .borrow() + .monitor_info + .as_deref() + .map(|i| i.modes.len()) + .unwrap_or(0); + let idx = req.idx as usize; + if idx >= num_modes { + return Err(JayHeadExtModeSetterV1Error::ModeOutOfBounds); + } + self.common.push_op(HeadOp::SetMode(idx))?; + Ok(()) + } +} + +error! { + #[error("The mode is out of bounds")] + ModeOutOfBounds, +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 82c6ea38..ecd51f3b 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -264,6 +264,7 @@ impl JayHeadManagerSessionV1 { let old = connector.state.get(); let mut new = old; new.enabled = desired.connector_enabled; + new.mode = desired.mode; if old == new { continue; } @@ -414,6 +415,12 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { to_send |= COMPOSITOR_SPACE_INFO_SCALE; to_send |= COMPOSITOR_SPACE_INFO_SIZE; } + HeadOp::SetMode(i) => { + state.mode = snapshot.monitor_info.as_deref().unwrap().modes[i]; + state.update_size(); + to_send |= MODE_INFO; + to_send |= COMPOSITOR_SPACE_INFO_SIZE; + } } } if to_send.contains(CORE_INFO) diff --git a/wire/jay_head_ext_mode_setter_v1.txt b/wire/jay_head_ext_mode_setter_v1.txt new file mode 100644 index 00000000..b575a8ba --- /dev/null +++ b/wire/jay_head_ext_mode_setter_v1.txt @@ -0,0 +1,17 @@ +request destroy (destructor) { + +} + +request set_mode { + idx: u32, +} + +event reset { + +} + +event mode { + width: i32, + height: i32, + refresh_mhz: u32, +} diff --git a/wire/jay_head_manager_ext_mode_setter_v1.txt b/wire/jay_head_manager_ext_mode_setter_v1.txt new file mode 100644 index 00000000..676efbb3 --- /dev/null +++ b/wire/jay_head_manager_ext_mode_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_mode_setter_v1) (new), +} From 81a7c973d070be22a4896d2c5d595109d417ec2f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 12 Jul 2025 09:34:04 +0200 Subject: [PATCH 12/26] head-management: add physical-display-info-v1 extension --- src/ifs/head_management.rs | 8 ++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_physical_display_info_v1.rs | 125 ++++++++++++++++++ .../jay_head_ext_physical_display_info_v1.txt | 38 ++++++ ...d_manager_ext_physical_display_info_v1.txt | 7 + 6 files changed, 180 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs create mode 100644 wire/jay_head_ext_physical_display_info_v1.txt create mode 100644 wire/jay_head_manager_ext_physical_display_info_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 2333ff88..2ee688ce 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -219,6 +219,10 @@ impl HeadManagers { ext.send_connected(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.physical_display_info_v1 { + ext.send_info(state); + head.session.schedule_done(); + } if let Some(ext) = &head.ext.mode_info_v1 { ext.send_mode(state); head.session.schedule_done(); @@ -257,6 +261,10 @@ impl HeadManagers { ext.send_wl_output(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.physical_display_info_v1 { + ext.send_info(state); + head.session.schedule_done(); + } } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 1149819a..30a78cd5 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -397,4 +397,5 @@ declare_extensions! { connector_info_v1: ConnectorInfoV1, mode_info_v1: ModeInfoV1, mode_setter_v1: ModeSetterV1, + physical_display_info_v1: PhysicalDisplayInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 427b5ea5..ac41e1c7 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -7,3 +7,4 @@ pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; +pub(super) mod jay_head_ext_physical_display_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs new file mode 100644 index 00000000..f927259f --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs @@ -0,0 +1,125 @@ +use { + crate::{ + backend::{self, CONCAP_PHYSICAL_DISPLAY}, + ifs::head_management::{HeadCommon, HeadState}, + state::ConnectorData, + wire::{ + jay_head_ext_physical_display_info_v1::{ + JayHeadExtPhysicalDisplayInfoV1RequestHandler, Manufacturer, Mode, Model, + NonDesktop, PhysicalSize, Reset, SerialNumber, VrrCapable, + }, + jay_head_manager_ext_physical_display_info_v1::JayHeadManagerExtPhysicalDisplayInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_physical_display_info_v1! { + version = 1, + filter = filter, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl MgrName { + fn filter(&self, connector: &ConnectorData, _common: &Rc) -> bool { + connector.connector.caps().contains(CONCAP_PHYSICAL_DISPLAY) + } +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_info(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + match (&shared.monitor_info, &tran.monitor_info) { + (Some(s), Some(t)) if s != t => {} + _ => return, + } + self.send_info(shared); + } + + pub(in super::super) fn send_info(&self, state: &HeadState) { + self.send_reset(); + if let Some(mi) = &state.monitor_info { + for mode in &mi.modes { + self.send_mode(mode); + } + self.send_manufacturer(&mi.output_id.manufacturer); + self.send_model(&mi.output_id.model); + self.send_serial_number(&mi.output_id.serial_number); + self.send_physical_size(mi.width_mm, mi.height_mm); + if mi.non_desktop { + self.send_non_desktop(); + } + if mi.vrr_capable { + self.send_vrr_capable(); + } + } + } + + fn send_reset(&self) { + self.client.event(Reset { self_id: self.id }); + } + + fn send_mode(&self, mode: &backend::Mode) { + self.client.event(Mode { + self_id: self.id, + width: mode.width, + height: mode.height, + refresh_mhz: mode.refresh_rate_millihz, + }); + } + + fn send_physical_size(&self, width_mm: i32, height_mm: i32) { + self.client.event(PhysicalSize { + self_id: self.id, + width_mm, + height_mm, + }); + } + + fn send_manufacturer(&self, manufacturer: &str) { + self.client.event(Manufacturer { + self_id: self.id, + manufacturer, + }); + } + + fn send_model(&self, model: &str) { + self.client.event(Model { + self_id: self.id, + model, + }); + } + + fn send_serial_number(&self, serial_number: &str) { + self.client.event(SerialNumber { + self_id: self.id, + serial_number, + }); + } + + fn send_non_desktop(&self) { + self.client.event(NonDesktop { self_id: self.id }); + } + + fn send_vrr_capable(&self) { + self.client.event(VrrCapable { self_id: self.id }); + } +} + +impl JayHeadManagerExtPhysicalDisplayInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtPhysicalDisplayInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/wire/jay_head_ext_physical_display_info_v1.txt b/wire/jay_head_ext_physical_display_info_v1.txt new file mode 100644 index 00000000..af4e0a9f --- /dev/null +++ b/wire/jay_head_ext_physical_display_info_v1.txt @@ -0,0 +1,38 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event mode { + width: i32, + height: i32, + refresh_mhz: u32, +} + +event physical_size { + width_mm: i32, + height_mm: i32, +} + +event manufacturer { + manufacturer: str, +} + +event model { + model: str, +} + +event serial_number { + serial_number: str, +} + +event non_desktop { + +} + +event vrr_capable { + +} diff --git a/wire/jay_head_manager_ext_physical_display_info_v1.txt b/wire/jay_head_manager_ext_physical_display_info_v1.txt new file mode 100644 index 00000000..1c99a82a --- /dev/null +++ b/wire/jay_head_manager_ext_physical_display_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_physical_display_info_v1) (new), +} From b76aade2654afb4b3a07ac8ce15eb491c307d23d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 13 Jul 2025 11:14:37 +0200 Subject: [PATCH 13/26] head-management: add non-desktop-info-v1 extension --- src/backend.rs | 1 + src/backends/metal/video.rs | 5 +- src/backends/x.rs | 1 + src/compositor.rs | 2 + src/ifs/head_management.rs | 22 ++++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_non_desktop_info_v1.rs | 78 +++++++++++++++++++ .../jay_head_ext_physical_display_info_v1.rs | 2 +- .../jay_head_manager_session_v1.rs | 6 ++ src/ifs/wp_drm_lease_device_v1.rs | 2 +- src/it/test_backend.rs | 1 + src/it/tests/t0034_workspace_restoration.rs | 1 + src/state.rs | 4 + src/tasks/connector.rs | 4 +- wire/jay_head_ext_non_desktop_info_v1.txt | 31 ++++++++ ...y_head_manager_ext_non_desktop_info_v1.txt | 7 ++ 17 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_info_v1.rs create mode 100644 wire/jay_head_ext_non_desktop_info_v1.txt create mode 100644 wire/jay_head_manager_ext_non_desktop_info_v1.txt diff --git a/src/backend.rs b/src/backend.rs index 7b5b6b73..39fe5a58 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -104,6 +104,7 @@ pub struct MonitorInfo { pub width_mm: i32, pub height_mm: i32, pub non_desktop: bool, + pub non_desktop_effective: bool, pub vrr_capable: bool, pub transfer_functions: Vec, pub color_spaces: Vec, diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index bd8e10f4..ecf24277 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -727,7 +727,7 @@ impl MetalConnector { match &event { ConnectorEvent::Connected(ty) => match state { FrontState::Disconnected => { - let non_desktop = ty.non_desktop; + let non_desktop = ty.non_desktop_effective; self.on_change.send_event(event); set_state(FrontState::Connected { non_desktop }); } @@ -1915,7 +1915,8 @@ impl MetalBackend { output_id: dd.output_id.clone(), width_mm: dd.mm_width as _, height_mm: dd.mm_height as _, - non_desktop: dd.non_desktop_effective, + non_desktop: dd.non_desktop, + non_desktop_effective: dd.non_desktop_effective, vrr_capable: dd.vrr_capable, transfer_functions, color_spaces, diff --git a/src/backends/x.rs b/src/backends/x.rs index d35cacbb..29e4969d 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -597,6 +597,7 @@ impl XBackend { width_mm: output.width.get(), height_mm: output.height.get(), non_desktop: false, + non_desktop_effective: false, vrr_capable: false, transfer_functions: vec![], color_spaces: vec![], diff --git a/src/compositor.rs b/src/compositor.rs index 7967ed46..4c23006c 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -651,6 +651,8 @@ fn create_dummy_output(state: &Rc) { in_compositor_space: false, mode: Default::default(), monitor_info: None, + inherent_non_desktop: false, + override_non_desktop: None, }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 2ee688ce..330f028b 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -77,6 +77,8 @@ pub struct HeadState { pub transform: Transform, pub scale: Scale, pub monitor_info: Option>, + pub inherent_non_desktop: bool, + pub override_non_desktop: Option, } impl HeadState { @@ -92,6 +94,9 @@ impl HeadState { if mi.non_desktop { return; } + if self.override_non_desktop == Some(true) { + return; + } self.in_compositor_space = true; self.wl_output = wl_output; } @@ -206,6 +211,7 @@ impl HeadManagers { let state = &mut *self.state.borrow_mut(); state.connected = true; state.monitor_info = Some(RcEq(output.monitor_info.clone())); + state.inherent_non_desktop = output.monitor_info.non_desktop; state.update_in_compositor_space(output.node.as_ref().map(|n| n.global.name)); if let Some(n) = &output.node { state.position = n.global.pos.get().position(); @@ -239,6 +245,10 @@ impl HeadManagers { ext.send_wl_output(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.non_desktop_info_v1 { + ext.send_state(state); + head.session.schedule_done(); + } } } @@ -337,4 +347,16 @@ impl HeadManagers { } } } + + pub fn handle_non_desktop_override_changed(&self, overrd: Option) { + let state = &mut *self.state.borrow_mut(); + state.override_non_desktop = overrd; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.non_desktop_info_v1 { + ext.send_state(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 30a78cd5..6bafdee2 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -398,4 +398,5 @@ declare_extensions! { mode_info_v1: ModeInfoV1, mode_setter_v1: ModeSetterV1, physical_display_info_v1: PhysicalDisplayInfoV1, + non_desktop_info_v1: NonDesktopInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index ac41e1c7..2a43ac31 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -7,4 +7,5 @@ pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; +pub(super) mod jay_head_ext_non_desktop_info_v1; pub(super) mod jay_head_ext_physical_display_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_info_v1.rs new file mode 100644 index 00000000..7c4d5dbd --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_info_v1.rs @@ -0,0 +1,78 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_non_desktop_info_v1::{ + EffectiveDesktop, EffectiveNonDesktop, InherentDesktop, InherentNonDesktop, + JayHeadExtNonDesktopInfoV1RequestHandler, OverrideDesktop, OverrideNonDesktop, + Reset, + }, + jay_head_manager_ext_non_desktop_info_v1::JayHeadManagerExtNonDesktopInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_non_desktop_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_state(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.override_non_desktop == tran.override_non_desktop { + match (&shared.monitor_info, &tran.monitor_info) { + (Some(s), Some(t)) if s.non_desktop == t.non_desktop => return, + (None, None) => return, + _ => {} + } + } + self.send_state(shared); + } + + pub(in super::super) fn send_state(&self, state: &HeadState) { + self.client.event(Reset { self_id: self.id }); + let mut inherent_non_desktop = None; + if let Some(monitor_info) = &state.monitor_info { + inherent_non_desktop = Some(monitor_info.non_desktop); + if monitor_info.non_desktop { + self.client.event(InherentNonDesktop { self_id: self.id }); + } else { + self.client.event(InherentDesktop { self_id: self.id }); + } + } + if let Some(overrd) = state.override_non_desktop { + if overrd { + self.client.event(OverrideNonDesktop { self_id: self.id }); + } else { + self.client.event(OverrideDesktop { self_id: self.id }); + } + } + if let Some(nd) = state.override_non_desktop.or(inherent_non_desktop) { + if nd { + self.client.event(EffectiveNonDesktop { self_id: self.id }); + } else { + self.client.event(EffectiveDesktop { self_id: self.id }); + } + } + } +} + +impl JayHeadManagerExtNonDesktopInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtNonDesktopInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs index f927259f..0c1fff34 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_physical_display_info_v1.rs @@ -50,7 +50,7 @@ impl HeadName { self.send_model(&mi.output_id.model); self.send_serial_number(&mi.output_id.serial_number); self.send_physical_size(mi.width_mm, mi.height_mm); - if mi.non_desktop { + if mi.non_desktop_effective { self.send_non_desktop(); } if mi.vrr_capable { diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index ecd51f3b..b9f24195 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -383,6 +383,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { COMPOSITOR_SPACE_INFO_TRANSFORM = 1 << 4, COMPOSITOR_SPACE_INFO_SCALE = 1 << 5, MODE_INFO = 1 << 6, + NON_DESKTOP_INFO = 1 << 7, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -454,6 +455,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_mode(state); } + if to_send.contains(NON_DESKTOP_INFO) + && let Some(i) = &head.ext.non_desktop_info_v1 + { + i.send_state(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) diff --git a/src/ifs/wp_drm_lease_device_v1.rs b/src/ifs/wp_drm_lease_device_v1.rs index 315610d5..fba54909 100644 --- a/src/ifs/wp_drm_lease_device_v1.rs +++ b/src/ifs/wp_drm_lease_device_v1.rs @@ -56,7 +56,7 @@ impl WpDrmLeaseDeviceV1Global { } for c in dev.connectors.lock().keys() { if let Some(o) = client.state.outputs.get(c) - && o.monitor_info.non_desktop + && o.monitor_info.non_desktop_effective { obj.create_connector(&o); } diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index e755f151..161cc532 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -135,6 +135,7 @@ impl TestBackend { width_mm: 80, height_mm: 60, non_desktop: false, + non_desktop_effective: false, vrr_capable: false, transfer_functions: vec![], color_spaces: vec![], diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index 97e63050..9adf3192 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -47,6 +47,7 @@ async fn test(run: Rc) -> TestResult { width_mm: 0, height_mm: 0, non_desktop: false, + non_desktop_effective: false, vrr_capable: false, transfer_functions: vec![], color_spaces: vec![], diff --git a/src/state.rs b/src/state.rs index 1e79ca79..f54fe3e9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -449,6 +449,10 @@ impl ConnectorData { if old.active != s.active { self.head_managers.handle_active_change(s.active); } + if old.non_desktop_override != s.non_desktop_override { + self.head_managers + .handle_non_desktop_override_changed(s.non_desktop_override); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index b453da99..5891835f 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -57,6 +57,8 @@ pub fn handle(state: &Rc, connector: &Rc) { in_compositor_space: false, mode: Default::default(), monitor_info: None, + inherent_non_desktop: false, + override_non_desktop: backend_state.non_desktop_override, }; let data = Rc::new(ConnectorData { id, @@ -132,7 +134,7 @@ impl ConnectorHandler { self.data.connected.set(true); self.data.set_state(&self.state, info.state); let name = self.state.globals.name(); - if info.non_desktop { + if info.non_desktop_effective { self.handle_non_desktop_connected(info).await; } else { self.handle_desktop_connected(info, name).await; diff --git a/wire/jay_head_ext_non_desktop_info_v1.txt b/wire/jay_head_ext_non_desktop_info_v1.txt new file mode 100644 index 00000000..8cb9d98a --- /dev/null +++ b/wire/jay_head_ext_non_desktop_info_v1.txt @@ -0,0 +1,31 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event inherent_desktop { + +} + +event inherent_non_desktop { + +} + +event override_desktop { + +} + +event override_non_desktop { + +} + +event effective_desktop { + +} + +event effective_non_desktop { + +} diff --git a/wire/jay_head_manager_ext_non_desktop_info_v1.txt b/wire/jay_head_manager_ext_non_desktop_info_v1.txt new file mode 100644 index 00000000..f1f9d8d3 --- /dev/null +++ b/wire/jay_head_manager_ext_non_desktop_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_non_desktop_info_v1) (new), +} From aaef75f9f30068d6eb9233c5547849038c15c722 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 13 Jul 2025 13:20:42 +0200 Subject: [PATCH 14/26] head-management: add vrr-state-v1 extension --- src/compositor.rs | 1 + src/ifs/head_management.rs | 21 +++++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext/jay_head_ext_vrr_state_v1.rs | 58 +++++++++++++++++++ src/state.rs | 3 + src/tasks/connector.rs | 1 + wire/jay_head_ext_vrr_state_v1.txt | 15 +++++ wire/jay_head_manager_ext_vrr_state_v1.txt | 7 +++ 9 files changed, 108 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_vrr_state_v1.rs create mode 100644 wire/jay_head_ext_vrr_state_v1.txt create mode 100644 wire/jay_head_manager_ext_vrr_state_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 4c23006c..d5f72de0 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -653,6 +653,7 @@ fn create_dummy_output(state: &Rc) { monitor_info: None, inherent_non_desktop: false, override_non_desktop: None, + vrr: false, }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 330f028b..d964a6cd 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -79,6 +79,7 @@ pub struct HeadState { pub monitor_info: Option>, pub inherent_non_desktop: bool, pub override_non_desktop: Option, + pub vrr: bool, } impl HeadState { @@ -249,6 +250,10 @@ impl HeadManagers { ext.send_state(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.vrr_state_v1 { + ext.send_state(state); + head.session.schedule_done(); + } } } @@ -275,6 +280,10 @@ impl HeadManagers { ext.send_info(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.vrr_state_v1 { + ext.send_state(state); + head.session.schedule_done(); + } } } @@ -359,4 +368,16 @@ impl HeadManagers { } } } + + pub fn handle_vrr_change(&self, vrr: bool) { + let state = &mut *self.state.borrow_mut(); + state.vrr = vrr; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.vrr_state_v1 { + ext.send_state(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 6bafdee2..20160c2c 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -399,4 +399,5 @@ declare_extensions! { mode_setter_v1: ModeSetterV1, physical_display_info_v1: PhysicalDisplayInfoV1, non_desktop_info_v1: NonDesktopInfoV1, + vrr_state_v1: VrrStateV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 2a43ac31..96d21fc7 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -9,3 +9,4 @@ pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; pub(super) mod jay_head_ext_physical_display_info_v1; +pub(super) mod jay_head_ext_vrr_state_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_vrr_state_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_vrr_state_v1.rs new file mode 100644 index 00000000..778dea23 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_vrr_state_v1.rs @@ -0,0 +1,58 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_vrr_state_v1::{ + Capable, Enabled, JayHeadExtVrrStateV1RequestHandler, Reset, + }, + jay_head_manager_ext_vrr_state_v1::JayHeadManagerExtVrrStateV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_vrr_state_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_state(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + let shared_capable = shared.monitor_info.as_ref().map(|m| m.vrr_capable); + let tran_capable = tran.monitor_info.as_ref().map(|m| m.vrr_capable); + if (shared.vrr, shared_capable) != (tran.vrr, tran_capable) { + self.send_state(shared); + } + } + + pub(in super::super) fn send_state(&self, state: &HeadState) { + self.client.event(Reset { self_id: self.id }); + if let Some(mi) = &state.monitor_info + && mi.vrr_capable + { + self.client.event(Capable { self_id: self.id }); + } + if state.vrr { + self.client.event(Enabled { self_id: self.id }); + } + } +} + +impl JayHeadManagerExtVrrStateV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtVrrStateV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/state.rs b/src/state.rs index f54fe3e9..b30b0da1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -453,6 +453,9 @@ impl ConnectorData { self.head_managers .handle_non_desktop_override_changed(s.non_desktop_override); } + if old.vrr != s.vrr { + self.head_managers.handle_vrr_change(s.vrr); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 5891835f..73f356eb 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -59,6 +59,7 @@ pub fn handle(state: &Rc, connector: &Rc) { monitor_info: None, inherent_non_desktop: false, override_non_desktop: backend_state.non_desktop_override, + vrr: backend_state.vrr, }; let data = Rc::new(ConnectorData { id, diff --git a/wire/jay_head_ext_vrr_state_v1.txt b/wire/jay_head_ext_vrr_state_v1.txt new file mode 100644 index 00000000..5ff3c63d --- /dev/null +++ b/wire/jay_head_ext_vrr_state_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event capable { + +} + +event enabled { + +} diff --git a/wire/jay_head_manager_ext_vrr_state_v1.txt b/wire/jay_head_manager_ext_vrr_state_v1.txt new file mode 100644 index 00000000..5f7c21db --- /dev/null +++ b/wire/jay_head_manager_ext_vrr_state_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_vrr_state_v1) (new), +} From 4482c3168bf466c3ceb8d283837afcee61bc24ef Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 13 Jul 2025 13:51:08 +0200 Subject: [PATCH 15/26] head-management: add tearing-state-v1 extension --- src/compositor.rs | 3 + src/ifs/head_management.rs | 26 ++++++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_tearing_state_v1.rs | 64 +++++++++++++++++++ src/state.rs | 3 + src/tasks/connector.rs | 3 + src/tree/output.rs | 17 +++-- wire/jay_head_ext_tearing_state_v1.txt | 19 ++++++ .../jay_head_manager_ext_tearing_state_v1.txt | 7 ++ 10 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_tearing_state_v1.rs create mode 100644 wire/jay_head_ext_tearing_state_v1.txt create mode 100644 wire/jay_head_manager_ext_tearing_state_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index d5f72de0..7ae496f6 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -654,6 +654,8 @@ fn create_dummy_output(state: &Rc) { inherent_non_desktop: false, override_non_desktop: None, vrr: false, + tearing_enabled: backend_state.tearing, + tearing_active: false, }; let connector_data = Rc::new(ConnectorData { id, @@ -727,6 +729,7 @@ fn create_dummy_output(state: &Rc) { tray_items: Default::default(), ext_workspace_groups: Default::default(), pinned: Default::default(), + tearing: Default::default(), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index d964a6cd..77e1e528 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -80,6 +80,8 @@ pub struct HeadState { pub inherent_non_desktop: bool, pub override_non_desktop: Option, pub vrr: bool, + pub tearing_enabled: bool, + pub tearing_active: bool, } impl HeadState { @@ -380,4 +382,28 @@ impl HeadManagers { } } } + + pub fn handle_tearing_enabled_change(&self, enabled: bool) { + let state = &mut *self.state.borrow_mut(); + state.tearing_enabled = enabled; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.tearing_state_v1 { + ext.send_enabled(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_tearing_active_change(&self, active: bool) { + let state = &mut *self.state.borrow_mut(); + state.tearing_active = active; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.tearing_state_v1 { + ext.send_active(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 20160c2c..b52b0d5c 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -400,4 +400,5 @@ declare_extensions! { physical_display_info_v1: PhysicalDisplayInfoV1, non_desktop_info_v1: NonDesktopInfoV1, vrr_state_v1: VrrStateV1, + tearing_state_v1: TearingStateV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 96d21fc7..c279a0f0 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -9,4 +9,5 @@ pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; pub(super) mod jay_head_ext_physical_display_info_v1; +pub(super) mod jay_head_ext_tearing_state_v1; pub(super) mod jay_head_ext_vrr_state_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_tearing_state_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_tearing_state_v1.rs new file mode 100644 index 00000000..e6491d48 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_tearing_state_v1.rs @@ -0,0 +1,64 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_tearing_state_v1::{ + Active, Disabled, Enabled, Inactive, JayHeadExtTearingStateV1RequestHandler, + }, + jay_head_manager_ext_tearing_state_v1::JayHeadManagerExtTearingStateV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_tearing_state_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_enabled(shared); + self.send_active(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.tearing_enabled != tran.tearing_enabled { + self.send_enabled(shared); + } + if shared.tearing_active != tran.tearing_active { + self.send_active(shared); + } + } + + pub(in super::super) fn send_enabled(&self, state: &HeadState) { + if state.tearing_enabled { + self.client.event(Enabled { self_id: self.id }); + } else { + self.client.event(Disabled { self_id: self.id }); + } + } + + pub(in super::super) fn send_active(&self, state: &HeadState) { + if state.tearing_active { + self.client.event(Active { self_id: self.id }); + } else { + self.client.event(Inactive { self_id: self.id }); + } + } +} + +impl JayHeadManagerExtTearingStateV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtTearingStateV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/state.rs b/src/state.rs index b30b0da1..2a651d0b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -456,6 +456,9 @@ impl ConnectorData { if old.vrr != s.vrr { self.head_managers.handle_vrr_change(s.vrr); } + if old.tearing != s.tearing { + self.head_managers.handle_tearing_enabled_change(s.tearing); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 73f356eb..9394d7ac 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -60,6 +60,8 @@ pub fn handle(state: &Rc, connector: &Rc) { inherent_non_desktop: false, override_non_desktop: backend_state.non_desktop_override, vrr: backend_state.vrr, + tearing_enabled: backend_state.tearing, + tearing_active: false, }; let data = Rc::new(ConnectorData { id, @@ -240,6 +242,7 @@ impl ConnectorHandler { tray_items: Default::default(), ext_workspace_groups: Default::default(), pinned: Default::default(), + tearing: Default::default(), }); on.update_visible(); on.update_rects(); diff --git a/src/tree/output.rs b/src/tree/output.rs index e20e6256..1295ddd8 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -32,6 +32,7 @@ use { ext_workspace_manager_v1::WorkspaceManagerId, }, wp_content_type_v1::ContentType, + wp_presentation_feedback::KIND_VSYNC, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, }, @@ -47,10 +48,10 @@ use { WorkspaceNodeId, walker::NodeVisitor, }, utils::{ - asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, event_listener::EventSource, hash_map_ext::HashMapExt, - linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, - transform_ext::TransformExt, + asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, event_listener::EventSource, + hash_map_ext::HashMapExt, linkedlist::LinkedList, on_drop_event::OnDropEvent, + scroller::Scroller, transform_ext::TransformExt, }, wire::{ ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, ZwlrScreencopyFrameV1Id, @@ -107,6 +108,7 @@ pub struct OutputNode { pub tray_items: LinkedList>, pub ext_workspace_groups: CopyHashMap>, pub pinned: LinkedList>, + pub tearing: Cell, } #[derive(Copy, Clone, Debug, PartialEq)] @@ -218,6 +220,13 @@ impl OutputNode { 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); + } } pub fn update_exclusive_zones(self: &Rc) { diff --git a/wire/jay_head_ext_tearing_state_v1.txt b/wire/jay_head_ext_tearing_state_v1.txt new file mode 100644 index 00000000..89182010 --- /dev/null +++ b/wire/jay_head_ext_tearing_state_v1.txt @@ -0,0 +1,19 @@ +request destroy (destructor) { + +} + +event enabled { + +} + +event disabled { + +} + +event active { + +} + +event inactive { + +} diff --git a/wire/jay_head_manager_ext_tearing_state_v1.txt b/wire/jay_head_manager_ext_tearing_state_v1.txt new file mode 100644 index 00000000..253b0a4b --- /dev/null +++ b/wire/jay_head_manager_ext_tearing_state_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_tearing_state_v1) (new), +} From 1195613fc633ea0f8956f0fdc200322d9f43b159 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 13 Jul 2025 14:09:50 +0200 Subject: [PATCH 16/26] head-management: add format-info-v1 extension --- src/compositor.rs | 1 + src/ifs/head_management.rs | 14 ++++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_format_info_v1.rs | 49 +++++++++++++++++++ src/state.rs | 3 ++ src/tasks/connector.rs | 1 + wire/jay_head_ext_format_info_v1.txt | 7 +++ wire/jay_head_manager_ext_format_info_v1.txt | 7 +++ 9 files changed, 84 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_format_info_v1.rs create mode 100644 wire/jay_head_ext_format_info_v1.txt create mode 100644 wire/jay_head_manager_ext_format_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 7ae496f6..47df6469 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -656,6 +656,7 @@ fn create_dummy_output(state: &Rc) { vrr: false, tearing_enabled: backend_state.tearing, tearing_active: false, + format: XRGB8888, }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 77e1e528..0ffba943 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -2,6 +2,7 @@ use { crate::{ backend::{ConnectorId, Mode, MonitorInfo, transaction::BackendConnectorTransactionError}, client::ClientId, + format::Format, globals::GlobalName, ifs::head_management::{ head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, @@ -82,6 +83,7 @@ pub struct HeadState { pub vrr: bool, pub tearing_enabled: bool, pub tearing_active: bool, + pub format: &'static Format, } impl HeadState { @@ -406,4 +408,16 @@ impl HeadManagers { } } } + + pub fn handle_format_change(&self, format: &'static Format) { + let state = &mut *self.state.borrow_mut(); + state.format = format; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.format_info_v1 { + ext.send_format(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index b52b0d5c..92371a3e 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -401,4 +401,5 @@ declare_extensions! { non_desktop_info_v1: NonDesktopInfoV1, vrr_state_v1: VrrStateV1, tearing_state_v1: TearingStateV1, + format_info_v1: FormatInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index c279a0f0..422b6511 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -5,6 +5,7 @@ pub(super) mod jay_head_ext_compositor_space_scaler_v1; pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; +pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_format_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_format_info_v1.rs new file mode 100644 index 00000000..7f2605af --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_format_info_v1.rs @@ -0,0 +1,49 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_format_info_v1::{Format, JayHeadExtFormatInfoV1RequestHandler}, + jay_head_manager_ext_format_info_v1::JayHeadManagerExtFormatInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_format_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_format(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.format != tran.format { + self.send_format(shared); + } + } + + pub(in super::super) fn send_format(&self, state: &HeadState) { + self.client.event(Format { + self_id: self.id, + format: state.format.drm, + }); + } +} + +impl JayHeadManagerExtFormatInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtFormatInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/state.rs b/src/state.rs index 2a651d0b..7da56328 100644 --- a/src/state.rs +++ b/src/state.rs @@ -459,6 +459,9 @@ impl ConnectorData { if old.tearing != s.tearing { self.head_managers.handle_tearing_enabled_change(s.tearing); } + if old.format != s.format { + self.head_managers.handle_format_change(s.format); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 9394d7ac..eb2f4ff5 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -62,6 +62,7 @@ pub fn handle(state: &Rc, connector: &Rc) { vrr: backend_state.vrr, tearing_enabled: backend_state.tearing, tearing_active: false, + format: backend_state.format, }; let data = Rc::new(ConnectorData { id, diff --git a/wire/jay_head_ext_format_info_v1.txt b/wire/jay_head_ext_format_info_v1.txt new file mode 100644 index 00000000..0e674f54 --- /dev/null +++ b/wire/jay_head_ext_format_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event format { + format: u32, +} diff --git a/wire/jay_head_manager_ext_format_info_v1.txt b/wire/jay_head_manager_ext_format_info_v1.txt new file mode 100644 index 00000000..04c360de --- /dev/null +++ b/wire/jay_head_manager_ext_format_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_format_info_v1) (new), +} From 6647b93e1ed9b28581f0e212b15936a7477d3e34 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 11:03:34 +0200 Subject: [PATCH 17/26] head-management: add drm-color-space-info-v1 extension --- src/compositor.rs | 2 + src/ifs/head_management.rs | 24 ++++++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_drm_color_space_info_v1.rs | 66 +++++++++++++++++++ src/state.rs | 4 ++ src/tasks/connector.rs | 2 + wire/jay_head_ext_drm_color_space_info_v1.txt | 11 ++++ ...ad_manager_ext_drm_color_space_info_v1.txt | 7 ++ 9 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs create mode 100644 wire/jay_head_ext_drm_color_space_info_v1.txt create mode 100644 wire/jay_head_manager_ext_drm_color_space_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 47df6469..11f2fa86 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -657,6 +657,8 @@ fn create_dummy_output(state: &Rc) { tearing_enabled: backend_state.tearing, tearing_active: false, format: XRGB8888, + color_space: backend_state.color_space, + transfer_function: backend_state.transfer_function, }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 0ffba943..24370bce 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -1,6 +1,9 @@ use { crate::{ - backend::{ConnectorId, Mode, MonitorInfo, transaction::BackendConnectorTransactionError}, + backend::{ + BackendColorSpace, BackendTransferFunction, ConnectorId, Mode, MonitorInfo, + transaction::BackendConnectorTransactionError, + }, client::ClientId, format::Format, globals::GlobalName, @@ -84,6 +87,8 @@ pub struct HeadState { pub tearing_enabled: bool, pub tearing_active: bool, pub format: &'static Format, + pub color_space: BackendColorSpace, + pub transfer_function: BackendTransferFunction, } impl HeadState { @@ -420,4 +425,21 @@ impl HeadManagers { } } } + + pub fn handle_colors_change( + &self, + color_space: BackendColorSpace, + transfer_function: BackendTransferFunction, + ) { + let state = &mut *self.state.borrow_mut(); + state.color_space = color_space; + state.transfer_function = transfer_function; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.drm_color_space_info_v1 { + ext.send_state(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 92371a3e..4c1cb23c 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -402,4 +402,5 @@ declare_extensions! { vrr_state_v1: VrrStateV1, tearing_state_v1: TearingStateV1, format_info_v1: FormatInfoV1, + drm_color_space_info_v1: DrmColorSpaceInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 422b6511..40378563 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -5,6 +5,7 @@ pub(super) mod jay_head_ext_compositor_space_scaler_v1; pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; +pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs new file mode 100644 index 00000000..0b57ee44 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_info_v1.rs @@ -0,0 +1,66 @@ +use { + crate::{ + backend::CONCAP_CONNECTOR, + ifs::head_management::{HeadCommon, HeadState}, + state::ConnectorData, + wire::{ + jay_head_ext_drm_color_space_info_v1::{ + Colorimetry, HdmiEotf, JayHeadExtDrmColorSpaceInfoV1RequestHandler, + }, + jay_head_manager_ext_drm_color_space_info_v1::JayHeadManagerExtDrmColorSpaceInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_drm_color_space_info_v1! { + version = 1, + filter = filter, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl MgrName { + fn filter(&self, connector: &ConnectorData, _common: &Rc) -> bool { + connector.connector.caps().contains(CONCAP_CONNECTOR) + } +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_state(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if (shared.color_space, shared.transfer_function) + != (tran.color_space, tran.transfer_function) + { + self.send_state(shared); + } + } + + pub(in super::super) fn send_state(&self, state: &HeadState) { + self.client.event(HdmiEotf { + self_id: self.id, + eotf: state.transfer_function.to_drm() as u32, + }); + self.client.event(Colorimetry { + self_id: self.id, + colorimetry: state.color_space.to_drm() as u32, + }); + } +} + +impl JayHeadManagerExtDrmColorSpaceInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtDrmColorSpaceInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/state.rs b/src/state.rs index 7da56328..18e1bc3a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -462,6 +462,10 @@ impl ConnectorData { if old.format != s.format { self.head_managers.handle_format_change(s.format); } + if (old.color_space, old.transfer_function) != (s.color_space, s.transfer_function) { + self.head_managers + .handle_colors_change(s.color_space, s.transfer_function); + } if let Some(output) = state.outputs.get(&self.connector.id()) && let Some(node) = &output.node { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index eb2f4ff5..3a646f88 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -63,6 +63,8 @@ pub fn handle(state: &Rc, connector: &Rc) { tearing_enabled: backend_state.tearing, tearing_active: false, format: backend_state.format, + color_space: backend_state.color_space, + transfer_function: backend_state.transfer_function, }; let data = Rc::new(ConnectorData { id, diff --git a/wire/jay_head_ext_drm_color_space_info_v1.txt b/wire/jay_head_ext_drm_color_space_info_v1.txt new file mode 100644 index 00000000..d58ecd2d --- /dev/null +++ b/wire/jay_head_ext_drm_color_space_info_v1.txt @@ -0,0 +1,11 @@ +request destroy (destructor) { + +} + +event hdmi_eotf { + eotf: u32, +} + +event colorimetry { + colorimetry: u32, +} diff --git a/wire/jay_head_manager_ext_drm_color_space_info_v1.txt b/wire/jay_head_manager_ext_drm_color_space_info_v1.txt new file mode 100644 index 00000000..54f2b290 --- /dev/null +++ b/wire/jay_head_manager_ext_drm_color_space_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_drm_color_space_info_v1) (new), +} From 61570bdef7d2aef2feaa5f953274e56d4a288330 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 11:31:18 +0200 Subject: [PATCH 18/26] head-management: add non-desktop-override-v1 extension --- src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_non_desktop_override_v1.rs | 52 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 8 +++ wire/jay_head_ext_non_desktop_override_v1.txt | 15 ++++++ ...ad_manager_ext_non_desktop_override_v1.txt | 7 +++ 7 files changed, 85 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_override_v1.rs create mode 100644 wire/jay_head_ext_non_desktop_override_v1.txt create mode 100644 wire/jay_head_manager_ext_non_desktop_override_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 24370bce..5757da6b 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -124,6 +124,7 @@ enum HeadOp { SetTransform(Transform), SetScale(Scale), SetMode(usize), + SetNonDesktopOverride(Option), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 4c1cb23c..9bef0657 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -403,4 +403,5 @@ declare_extensions! { tearing_state_v1: TearingStateV1, format_info_v1: FormatInfoV1, drm_color_space_info_v1: DrmColorSpaceInfoV1, + non_desktop_override_v1: NonDesktopOverrideV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 40378563..f354121c 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -10,6 +10,7 @@ pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; +pub(super) mod jay_head_ext_non_desktop_override_v1; pub(super) mod jay_head_ext_physical_display_info_v1; pub(super) mod jay_head_ext_tearing_state_v1; pub(super) mod jay_head_ext_vrr_state_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_override_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_override_v1.rs new file mode 100644 index 00000000..9649294a --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_non_desktop_override_v1.rs @@ -0,0 +1,52 @@ +use { + crate::{ + ifs::head_management::HeadOp, + wire::{ + jay_head_ext_non_desktop_override_v1::{ + DisableOverride, JayHeadExtNonDesktopOverrideV1RequestHandler, OverrideDesktop, + OverrideNonDesktop, + }, + jay_head_manager_ext_non_desktop_override_v1::JayHeadManagerExtNonDesktopOverrideV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_non_desktop_override_v1! { + version = 1, +} + +impl JayHeadManagerExtNonDesktopOverrideV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtNonDesktopOverrideV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn disable_override(&self, _req: DisableOverride, _slf: &Rc) -> Result<(), Self::Error> { + self.common.push_op(HeadOp::SetNonDesktopOverride(None))?; + Ok(()) + } + + fn override_desktop(&self, _req: OverrideDesktop, _slf: &Rc) -> Result<(), Self::Error> { + self.common + .push_op(HeadOp::SetNonDesktopOverride(Some(false)))?; + Ok(()) + } + + fn override_non_desktop( + &self, + _req: OverrideNonDesktop, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.common + .push_op(HeadOp::SetNonDesktopOverride(Some(true)))?; + Ok(()) + } +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index b9f24195..932fab91 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -265,6 +265,7 @@ impl JayHeadManagerSessionV1 { let mut new = old; new.enabled = desired.connector_enabled; new.mode = desired.mode; + new.non_desktop_override = desired.override_non_desktop; if old == new { continue; } @@ -422,6 +423,13 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { to_send |= MODE_INFO; to_send |= COMPOSITOR_SPACE_INFO_SIZE; } + HeadOp::SetNonDesktopOverride(m) => { + state.override_non_desktop = m; + state.update_in_compositor_space(snapshot.wl_output); + to_send |= COMPOSITOR_SPACE_INFO_FULL; + to_send |= CORE_INFO; + to_send |= NON_DESKTOP_INFO; + } } } if to_send.contains(CORE_INFO) diff --git a/wire/jay_head_ext_non_desktop_override_v1.txt b/wire/jay_head_ext_non_desktop_override_v1.txt new file mode 100644 index 00000000..077cce30 --- /dev/null +++ b/wire/jay_head_ext_non_desktop_override_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +request disable_override { + +} + +request override_desktop { + +} + +request override_non_desktop { + +} diff --git a/wire/jay_head_manager_ext_non_desktop_override_v1.txt b/wire/jay_head_manager_ext_non_desktop_override_v1.txt new file mode 100644 index 00000000..4ffa7dd4 --- /dev/null +++ b/wire/jay_head_manager_ext_non_desktop_override_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_non_desktop_override_v1) (new), +} From 3e2707174eca81a6002ba7994756d4660528beb6 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 14:47:12 +0200 Subject: [PATCH 19/26] head-management: add jay-vrr-mode-info-v1 extension --- src/compositor.rs | 1 + src/config/handler.rs | 3 +- src/ifs/head_management.rs | 20 +++++++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_jay_vrr_mode_info_v1.rs | 49 +++++++++++++++++++ src/ifs/jay_randr.rs | 3 +- src/tasks/connector.rs | 1 + src/tree/output.rs | 11 +++++ wire/jay_head_ext_jay_vrr_mode_info_v1.txt | 7 +++ ..._head_manager_ext_jay_vrr_mode_info_v1.txt | 7 +++ 11 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_info_v1.rs create mode 100644 wire/jay_head_ext_jay_vrr_mode_info_v1.txt create mode 100644 wire/jay_head_manager_ext_jay_vrr_mode_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 11f2fa86..119496eb 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -654,6 +654,7 @@ fn create_dummy_output(state: &Rc) { inherent_non_desktop: false, override_non_desktop: None, vrr: false, + vrr_mode: VrrMode::Never.to_config(), tearing_enabled: backend_state.tearing, tearing_active: false, format: XRGB8888, diff --git a/src/config/handler.rs b/src/config/handler.rs index 62f3f43c..89186d4e 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1375,8 +1375,7 @@ impl ConfigProxyHandler { match connector { Some(c) => { let connector = self.get_output_node(c)?; - connector.global.persistent.vrr_mode.set(mode); - connector.update_presentation_type(); + connector.set_vrr_mode(mode); } _ => self.state.default_vrr_mode.set(mode), } diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 5757da6b..ec270be9 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -17,7 +17,7 @@ use { utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, wire::JayHeadManagerSessionV1Id, }, - jay_config::video::Transform, + jay_config::video::{Transform, VrrMode}, std::{ cell::{Cell, RefCell}, rc::Rc, @@ -84,6 +84,7 @@ pub struct HeadState { pub inherent_non_desktop: bool, pub override_non_desktop: Option, pub vrr: bool, + pub vrr_mode: VrrMode, pub tearing_enabled: bool, pub tearing_active: bool, pub format: &'static Format, @@ -229,6 +230,7 @@ impl HeadManagers { state.size = n.global.pos.get().size(); state.mode = n.global.mode.get(); state.transform = n.global.persistent.transform.get(); + state.vrr_mode = n.global.persistent.vrr_mode.get().to_config(); } for head in self.managers.lock().values() { skip_in_transaction!(head); @@ -264,6 +266,10 @@ impl HeadManagers { ext.send_state(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.jay_vrr_mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } } } @@ -391,6 +397,18 @@ impl HeadManagers { } } + pub fn handle_vrr_mode_change(&self, vrr_mode: VrrMode) { + let state = &mut *self.state.borrow_mut(); + state.vrr_mode = vrr_mode; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.jay_vrr_mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } + } + } + pub fn handle_tearing_enabled_change(&self, enabled: bool) { let state = &mut *self.state.borrow_mut(); state.tearing_enabled = enabled; diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 9bef0657..125f3c2f 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -404,4 +404,5 @@ declare_extensions! { format_info_v1: FormatInfoV1, drm_color_space_info_v1: DrmColorSpaceInfoV1, non_desktop_override_v1: NonDesktopOverrideV1, + jay_vrr_mode_info_v1: JayVrrModeInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index f354121c..b9445087 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -7,6 +7,7 @@ pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; +pub(super) mod jay_head_ext_jay_vrr_mode_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_info_v1.rs new file mode 100644 index 00000000..b92c4b39 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_info_v1.rs @@ -0,0 +1,49 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_jay_vrr_mode_info_v1::{JayHeadExtJayVrrModeInfoV1RequestHandler, Mode}, + jay_head_manager_ext_jay_vrr_mode_info_v1::JayHeadManagerExtJayVrrModeInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_jay_vrr_mode_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_mode(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.vrr_mode != tran.vrr_mode { + self.send_mode(shared); + } + } + + pub(in super::super) fn send_mode(&self, state: &HeadState) { + self.client.event(Mode { + self_id: self.id, + mode: state.vrr_mode.0, + }); + } +} + +impl JayHeadManagerExtJayVrrModeInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtJayVrrModeInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 2ab860d9..ff196dea 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -419,8 +419,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; - c.global.persistent.vrr_mode.set(mode); - c.update_presentation_type(); + c.set_vrr_mode(mode); return Ok(()); } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 3a646f88..8393f89f 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -60,6 +60,7 @@ pub fn handle(state: &Rc, connector: &Rc) { inherent_non_desktop: false, override_non_desktop: backend_state.non_desktop_override, vrr: backend_state.vrr, + vrr_mode: Default::default(), tearing_enabled: backend_state.tearing, tearing_active: false, format: backend_state.format, diff --git a/src/tree/output.rs b/src/tree/output.rs index 1295ddd8..e1151073 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1324,6 +1324,17 @@ impl OutputNode { } self.state.tree_changed(); } + + pub fn set_vrr_mode(&self, mode: &'static 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.to_config()); + } + } } pub struct OutputTitle { diff --git a/wire/jay_head_ext_jay_vrr_mode_info_v1.txt b/wire/jay_head_ext_jay_vrr_mode_info_v1.txt new file mode 100644 index 00000000..aafc7b68 --- /dev/null +++ b/wire/jay_head_ext_jay_vrr_mode_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event mode { + mode: u32, +} diff --git a/wire/jay_head_manager_ext_jay_vrr_mode_info_v1.txt b/wire/jay_head_manager_ext_jay_vrr_mode_info_v1.txt new file mode 100644 index 00000000..e29efb62 --- /dev/null +++ b/wire/jay_head_manager_ext_jay_vrr_mode_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_jay_vrr_mode_info_v1) (new), +} From a7f8a26df74ed577f0045ffaebca837c245c8e69 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 15:00:56 +0200 Subject: [PATCH 20/26] head-management: add jay-tearing-mode-info-v1 extension --- src/compositor.rs | 1 + src/config/handler.rs | 3 +- src/ifs/head_management.rs | 20 +++++++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_jay_tearing_mode_info_v1.rs | 51 +++++++++++++++++++ src/ifs/jay_randr.rs | 3 +- src/tasks/connector.rs | 1 + src/tree/output.rs | 23 ++++++--- .../jay_head_ext_jay_tearing_mode_info_v1.txt | 7 +++ ...d_manager_ext_jay_tearing_mode_info_v1.txt | 7 +++ 11 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_info_v1.rs create mode 100644 wire/jay_head_ext_jay_tearing_mode_info_v1.txt create mode 100644 wire/jay_head_manager_ext_jay_tearing_mode_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 119496eb..382bc46e 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -657,6 +657,7 @@ fn create_dummy_output(state: &Rc) { vrr_mode: VrrMode::Never.to_config(), tearing_enabled: backend_state.tearing, tearing_active: false, + tearing_mode: TearingMode::Never.to_config(), format: XRGB8888, color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, diff --git a/src/config/handler.rs b/src/config/handler.rs index 89186d4e..b5bcc141 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1413,8 +1413,7 @@ impl ConfigProxyHandler { match connector { Some(c) => { let connector = self.get_output_node(c)?; - connector.global.persistent.tearing_mode.set(mode); - connector.update_presentation_type(); + connector.set_tearing_mode(mode); } _ => self.state.default_tearing_mode.set(mode), } diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index ec270be9..006093fe 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -17,7 +17,7 @@ use { utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, wire::JayHeadManagerSessionV1Id, }, - jay_config::video::{Transform, VrrMode}, + jay_config::video::{TearingMode, Transform, VrrMode}, std::{ cell::{Cell, RefCell}, rc::Rc, @@ -87,6 +87,7 @@ pub struct HeadState { pub vrr_mode: VrrMode, pub tearing_enabled: bool, pub tearing_active: bool, + pub tearing_mode: TearingMode, pub format: &'static Format, pub color_space: BackendColorSpace, pub transfer_function: BackendTransferFunction, @@ -231,6 +232,7 @@ impl HeadManagers { state.mode = n.global.mode.get(); state.transform = n.global.persistent.transform.get(); state.vrr_mode = n.global.persistent.vrr_mode.get().to_config(); + state.tearing_mode = n.global.persistent.tearing_mode.get().to_config(); } for head in self.managers.lock().values() { skip_in_transaction!(head); @@ -270,6 +272,10 @@ impl HeadManagers { ext.send_mode(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.jay_tearing_mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } } } @@ -433,6 +439,18 @@ impl HeadManagers { } } + pub fn handle_tearing_mode_change(&self, tearing_mode: TearingMode) { + let state = &mut *self.state.borrow_mut(); + state.tearing_mode = tearing_mode; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.jay_tearing_mode_info_v1 { + ext.send_mode(state); + head.session.schedule_done(); + } + } + } + pub fn handle_format_change(&self, format: &'static Format) { let state = &mut *self.state.borrow_mut(); state.format = format; diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 125f3c2f..67ae0447 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -405,4 +405,5 @@ declare_extensions! { drm_color_space_info_v1: DrmColorSpaceInfoV1, non_desktop_override_v1: NonDesktopOverrideV1, jay_vrr_mode_info_v1: JayVrrModeInfoV1, + jay_tearing_mode_info_v1: JayTearingModeInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index b9445087..a217dbf9 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -7,6 +7,7 @@ pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; +pub(super) mod jay_head_ext_jay_tearing_mode_info_v1; pub(super) mod jay_head_ext_jay_vrr_mode_info_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_info_v1.rs new file mode 100644 index 00000000..efd4cbcd --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_info_v1.rs @@ -0,0 +1,51 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{ + jay_head_ext_jay_tearing_mode_info_v1::{ + JayHeadExtJayTearingModeInfoV1RequestHandler, Mode, + }, + jay_head_manager_ext_jay_tearing_mode_info_v1::JayHeadManagerExtJayTearingModeInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_jay_tearing_mode_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_mode(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.tearing_mode != tran.tearing_mode { + self.send_mode(shared); + } + } + + pub(in super::super) fn send_mode(&self, state: &HeadState) { + self.client.event(Mode { + self_id: self.id, + mode: state.tearing_mode.0, + }); + } +} + +impl JayHeadManagerExtJayTearingModeInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtJayTearingModeInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index ff196dea..29e7ea2f 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -446,8 +446,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(c) = self.get_output_node(req.output) else { return Ok(()); }; - c.global.persistent.tearing_mode.set(mode); - c.update_presentation_type(); + c.set_tearing_mode(mode); return Ok(()); } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 8393f89f..fb9dd1ad 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -63,6 +63,7 @@ pub fn handle(state: &Rc, connector: &Rc) { vrr_mode: Default::default(), tearing_enabled: backend_state.tearing, tearing_active: false, + tearing_mode: Default::default(), format: backend_state.format, color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, diff --git a/src/tree/output.rs b/src/tree/output.rs index e1151073..83927c97 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1335,6 +1335,17 @@ impl OutputNode { .handle_vrr_mode_change(mode.to_config()); } } + + pub fn set_tearing_mode(&self, mode: &'static 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.to_config()); + } + } } pub struct OutputTitle { @@ -1785,13 +1796,13 @@ impl TearingMode { Some(res) } - pub fn to_config(&self) -> ConfigVrrMode { + pub fn to_config(&self) -> ConfigTearingMode { 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, + 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, } } } diff --git a/wire/jay_head_ext_jay_tearing_mode_info_v1.txt b/wire/jay_head_ext_jay_tearing_mode_info_v1.txt new file mode 100644 index 00000000..aafc7b68 --- /dev/null +++ b/wire/jay_head_ext_jay_tearing_mode_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event mode { + mode: u32, +} diff --git a/wire/jay_head_manager_ext_jay_tearing_mode_info_v1.txt b/wire/jay_head_manager_ext_jay_tearing_mode_info_v1.txt new file mode 100644 index 00000000..b89dc989 --- /dev/null +++ b/wire/jay_head_manager_ext_jay_tearing_mode_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_jay_tearing_mode_info_v1) (new), +} From b53267fa8a1756fcf9f82e610234df820b9f2121 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 15:11:39 +0200 Subject: [PATCH 21/26] head-management: add jay-vrr-mode-setter-v1 extension --- src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_jay_vrr_mode_setter_v1.rs | 60 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 13 ++++ wire/jay_head_ext_jay_vrr_mode_setter_v1.txt | 15 +++++ ...ead_manager_ext_jay_vrr_mode_setter_v1.txt | 7 +++ 7 files changed, 98 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_setter_v1.rs create mode 100644 wire/jay_head_ext_jay_vrr_mode_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_jay_vrr_mode_setter_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 006093fe..7ce6b5cd 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -127,6 +127,7 @@ enum HeadOp { SetScale(Scale), SetMode(usize), SetNonDesktopOverride(Option), + SetVrrMode(VrrMode), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 67ae0447..c4b36d3b 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -406,4 +406,5 @@ declare_extensions! { non_desktop_override_v1: NonDesktopOverrideV1, jay_vrr_mode_info_v1: JayVrrModeInfoV1, jay_tearing_mode_info_v1: JayTearingModeInfoV1, + jay_vrr_mode_setter_v1: JayVrrModeSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index a217dbf9..345e5a24 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -9,6 +9,7 @@ pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_jay_tearing_mode_info_v1; pub(super) mod jay_head_ext_jay_vrr_mode_info_v1; +pub(super) mod jay_head_ext_jay_vrr_mode_setter_v1; pub(super) mod jay_head_ext_mode_info_v1; pub(super) mod jay_head_ext_mode_setter_v1; pub(super) mod jay_head_ext_non_desktop_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_setter_v1.rs new file mode 100644 index 00000000..4244384c --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_vrr_mode_setter_v1.rs @@ -0,0 +1,60 @@ +use { + crate::{ + ifs::head_management::{HeadOp, HeadState}, + wire::{ + jay_head_ext_jay_vrr_mode_setter_v1::{ + JayHeadExtJayVrrModeSetterV1RequestHandler, SetMode, SupportedMode, + }, + jay_head_manager_ext_jay_vrr_mode_setter_v1::JayHeadManagerExtJayVrrModeSetterV1RequestHandler, + }, + }, + jay_config::video::VrrMode, + std::rc::Rc, +}; + +impl_jay_vrr_mode_setter_v1! { + version = 1, + after_announce = after_announce, +} + +impl HeadName { + fn after_announce(&self, _shared: &HeadState) { + self.send_supported_mode(VrrMode::NEVER); + self.send_supported_mode(VrrMode::ALWAYS); + self.send_supported_mode(VrrMode::VARIANT_1); + self.send_supported_mode(VrrMode::VARIANT_2); + self.send_supported_mode(VrrMode::VARIANT_3); + } + + pub(in super::super) fn send_supported_mode(&self, mode: VrrMode) { + self.client.event(SupportedMode { + self_id: self.id, + mode: mode.0, + }); + } +} + +impl JayHeadManagerExtJayVrrModeSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtJayVrrModeSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_mode(&self, req: SetMode, _slf: &Rc) -> Result<(), Self::Error> { + if req.mode > VrrMode::VARIANT_3.0 { + return Err(ErrorName::UnknownMode(req.mode)); + } + self.common.push_op(HeadOp::SetVrrMode(VrrMode(req.mode)))?; + Ok(()) + } +} + +error! { + #[error("Unknown VRR mode {0}")] + UnknownMode(u32), +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 932fab91..9d098579 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -385,6 +385,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { COMPOSITOR_SPACE_INFO_SCALE = 1 << 5, MODE_INFO = 1 << 6, NON_DESKTOP_INFO = 1 << 7, + VRR_MODE_INFO = 1 << 8, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -430,6 +431,10 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { to_send |= CORE_INFO; to_send |= NON_DESKTOP_INFO; } + HeadOp::SetVrrMode(m) => { + state.vrr_mode = m; + to_send |= VRR_MODE_INFO; + } } } if to_send.contains(CORE_INFO) @@ -468,6 +473,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_state(state); } + if to_send.contains(VRR_MODE_INFO) + && let Some(i) = &head.ext.jay_vrr_mode_info_v1 + { + i.send_mode(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) @@ -503,6 +513,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { node.set_position(desired.position.0, desired.position.1); node.set_preferred_scale(desired.scale); node.update_transform(desired.transform); + node.set_vrr_mode(VrrMode::from_config(desired.vrr_mode).unwrap()); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -524,6 +535,8 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { pos.pos.set(desired.position); pos.scale.set(desired.scale); pos.transform.set(desired.transform); + pos.vrr_mode + .set(VrrMode::from_config(desired.vrr_mode).unwrap()); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/wire/jay_head_ext_jay_vrr_mode_setter_v1.txt b/wire/jay_head_ext_jay_vrr_mode_setter_v1.txt new file mode 100644 index 00000000..34288cf8 --- /dev/null +++ b/wire/jay_head_ext_jay_vrr_mode_setter_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event supported_mode { + mode: u32, +} + +request set_mode { + mode: u32, +} diff --git a/wire/jay_head_manager_ext_jay_vrr_mode_setter_v1.txt b/wire/jay_head_manager_ext_jay_vrr_mode_setter_v1.txt new file mode 100644 index 00000000..e67da63a --- /dev/null +++ b/wire/jay_head_manager_ext_jay_vrr_mode_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_jay_vrr_mode_setter_v1) (new), +} From e0120ed3bb184e3392c4d2f842b22cf30856df62 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 15:18:03 +0200 Subject: [PATCH 22/26] head-management: add jay-tearing-mode-setter-v1 extension --- src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + ...jay_head_ext_jay_tearing_mode_setter_v1.rs | 61 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 13 ++++ ...ay_head_ext_jay_tearing_mode_setter_v1.txt | 15 +++++ ...manager_ext_jay_tearing_mode_setter_v1.txt | 7 +++ 7 files changed, 99 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_setter_v1.rs create mode 100644 wire/jay_head_ext_jay_tearing_mode_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_jay_tearing_mode_setter_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 7ce6b5cd..0d691b5f 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -128,6 +128,7 @@ enum HeadOp { SetMode(usize), SetNonDesktopOverride(Option), SetVrrMode(VrrMode), + SetTearingMode(TearingMode), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index c4b36d3b..5b7dfcfa 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -407,4 +407,5 @@ declare_extensions! { jay_vrr_mode_info_v1: JayVrrModeInfoV1, jay_tearing_mode_info_v1: JayTearingModeInfoV1, jay_vrr_mode_setter_v1: JayVrrModeSetterV1, + jay_tearing_mode_setter_v1: JayTearingModeSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 345e5a24..e5ca8933 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -8,6 +8,7 @@ pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_jay_tearing_mode_info_v1; +pub(super) mod jay_head_ext_jay_tearing_mode_setter_v1; pub(super) mod jay_head_ext_jay_vrr_mode_info_v1; pub(super) mod jay_head_ext_jay_vrr_mode_setter_v1; pub(super) mod jay_head_ext_mode_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_setter_v1.rs new file mode 100644 index 00000000..4c8a5f96 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_jay_tearing_mode_setter_v1.rs @@ -0,0 +1,61 @@ +use { + crate::{ + ifs::head_management::{HeadOp, HeadState}, + wire::{ + jay_head_ext_jay_tearing_mode_setter_v1::{ + JayHeadExtJayTearingModeSetterV1RequestHandler, SetMode, SupportedMode, + }, + jay_head_manager_ext_jay_tearing_mode_setter_v1::JayHeadManagerExtJayTearingModeSetterV1RequestHandler, + }, + }, + jay_config::video::TearingMode, + std::rc::Rc, +}; + +impl_jay_tearing_mode_setter_v1! { + version = 1, + after_announce = after_announce, +} + +impl HeadName { + fn after_announce(&self, _shared: &HeadState) { + self.send_supported_mode(TearingMode::NEVER); + self.send_supported_mode(TearingMode::ALWAYS); + self.send_supported_mode(TearingMode::VARIANT_1); + self.send_supported_mode(TearingMode::VARIANT_2); + self.send_supported_mode(TearingMode::VARIANT_3); + } + + pub(in super::super) fn send_supported_mode(&self, mode: TearingMode) { + self.client.event(SupportedMode { + self_id: self.id, + mode: mode.0, + }); + } +} + +impl JayHeadManagerExtJayTearingModeSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtJayTearingModeSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_mode(&self, req: SetMode, _slf: &Rc) -> Result<(), Self::Error> { + if req.mode > TearingMode::VARIANT_3.0 { + return Err(ErrorName::UnknownMode(req.mode)); + } + self.common + .push_op(HeadOp::SetTearingMode(TearingMode(req.mode)))?; + Ok(()) + } +} + +error! { + #[error("Unknown tearing mode {0}")] + UnknownMode(u32), +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 9d098579..c049c315 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -386,6 +386,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { MODE_INFO = 1 << 6, NON_DESKTOP_INFO = 1 << 7, VRR_MODE_INFO = 1 << 8, + TEARING_MODE_INFO = 1 << 9, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -435,6 +436,10 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.vrr_mode = m; to_send |= VRR_MODE_INFO; } + HeadOp::SetTearingMode(m) => { + state.tearing_mode = m; + to_send |= TEARING_MODE_INFO; + } } } if to_send.contains(CORE_INFO) @@ -478,6 +483,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_mode(state); } + if to_send.contains(TEARING_MODE_INFO) + && let Some(i) = &head.ext.jay_tearing_mode_info_v1 + { + i.send_mode(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) @@ -514,6 +524,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { node.set_preferred_scale(desired.scale); node.update_transform(desired.transform); node.set_vrr_mode(VrrMode::from_config(desired.vrr_mode).unwrap()); + node.set_tearing_mode(TearingMode::from_config(desired.tearing_mode).unwrap()); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -537,6 +548,8 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { pos.transform.set(desired.transform); pos.vrr_mode .set(VrrMode::from_config(desired.vrr_mode).unwrap()); + pos.tearing_mode + .set(TearingMode::from_config(desired.tearing_mode).unwrap()); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/wire/jay_head_ext_jay_tearing_mode_setter_v1.txt b/wire/jay_head_ext_jay_tearing_mode_setter_v1.txt new file mode 100644 index 00000000..34288cf8 --- /dev/null +++ b/wire/jay_head_ext_jay_tearing_mode_setter_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event supported_mode { + mode: u32, +} + +request set_mode { + mode: u32, +} diff --git a/wire/jay_head_manager_ext_jay_tearing_mode_setter_v1.txt b/wire/jay_head_manager_ext_jay_tearing_mode_setter_v1.txt new file mode 100644 index 00000000..1f5e9efa --- /dev/null +++ b/wire/jay_head_manager_ext_jay_tearing_mode_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_jay_tearing_mode_setter_v1) (new), +} From 15e68fc551b8ff421d9062d70008de79672b8b76 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 15:46:57 +0200 Subject: [PATCH 23/26] head-management: add format-setter-v1 extension --- src/compositor.rs | 1 + src/ifs/head_management.rs | 14 ++++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_format_setter_v1.rs | 78 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 11 +++ src/tasks/connector.rs | 2 + src/utils/rc_eq.rs | 1 + wire/jay_head_ext_format_setter_v1.txt | 15 ++++ .../jay_head_manager_ext_format_setter_v1.txt | 7 ++ 10 files changed, 131 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_format_setter_v1.rs create mode 100644 wire/jay_head_ext_format_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_format_setter_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 382bc46e..1217fa0f 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -661,6 +661,7 @@ fn create_dummy_output(state: &Rc) { format: XRGB8888, color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, + supported_formats: Default::default(), }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 0d691b5f..e15cd19b 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -91,6 +91,7 @@ pub struct HeadState { pub format: &'static Format, pub color_space: BackendColorSpace, pub transfer_function: BackendTransferFunction, + pub supported_formats: RcEq>, } impl HeadState { @@ -129,6 +130,7 @@ enum HeadOp { SetNonDesktopOverride(Option), SetVrrMode(VrrMode), SetTearingMode(TearingMode), + SetFormat(&'static Format), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -481,4 +483,16 @@ impl HeadManagers { } } } + + pub fn handle_formats_change(&self, formats: &Rc>) { + let state = &mut *self.state.borrow_mut(); + state.supported_formats.0 = formats.clone(); + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.format_setter_v1 { + ext.send_supported_formats(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 5b7dfcfa..f6862a77 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -408,4 +408,5 @@ declare_extensions! { jay_tearing_mode_info_v1: JayTearingModeInfoV1, jay_vrr_mode_setter_v1: JayVrrModeSetterV1, jay_tearing_mode_setter_v1: JayTearingModeSetterV1, + format_setter_v1: FormatSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index e5ca8933..3eb0ee22 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -7,6 +7,7 @@ pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_drm_color_space_info_v1; pub(super) mod jay_head_ext_format_info_v1; +pub(super) mod jay_head_ext_format_setter_v1; pub(super) mod jay_head_ext_jay_tearing_mode_info_v1; pub(super) mod jay_head_ext_jay_tearing_mode_setter_v1; pub(super) mod jay_head_ext_jay_vrr_mode_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_format_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_format_setter_v1.rs new file mode 100644 index 00000000..09207aa7 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_format_setter_v1.rs @@ -0,0 +1,78 @@ +use { + crate::{ + format::formats, + ifs::head_management::{HeadOp, HeadState}, + wire::{ + jay_head_ext_format_setter_v1::{ + JayHeadExtFormatSetterV1RequestHandler, Reset, SetFormat, SupportedFormat, + }, + jay_head_manager_ext_format_setter_v1::JayHeadManagerExtFormatSetterV1RequestHandler, + }, + }, + isnt::std_1::primitive::IsntSlice2Ext, + std::rc::Rc, +}; + +impl_format_setter_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_supported_formats(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.supported_formats != tran.supported_formats { + self.send_supported_formats(shared); + } + } + + pub(in super::super) fn send_supported_formats(&self, state: &HeadState) { + self.client.event(Reset { self_id: self.id }); + for format in &*state.supported_formats { + self.client.event(SupportedFormat { + self_id: self.id, + format: format.drm, + }); + } + } +} + +impl JayHeadManagerExtFormatSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtFormatSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_format(&self, req: SetFormat, _slf: &Rc) -> Result<(), Self::Error> { + let Some(format) = formats().get(&req.format) else { + return Err(ErrorName::UnknownFormat(req.format)); + }; + if self + .common + .transaction_state + .borrow() + .supported_formats + .not_contains(format) + { + return Err(ErrorName::UnsupportedFormat(req.format)); + } + self.common.push_op(HeadOp::SetFormat(format))?; + Ok(()) + } +} + +error! { + #[error("Unknown format {0}")] + UnknownFormat(u32), + #[error("Unsupported format {0}")] + UnsupportedFormat(u32), +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index c049c315..94d97dd8 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -266,6 +266,7 @@ impl JayHeadManagerSessionV1 { new.enabled = desired.connector_enabled; new.mode = desired.mode; new.non_desktop_override = desired.override_non_desktop; + new.format = desired.format; if old == new { continue; } @@ -387,6 +388,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { NON_DESKTOP_INFO = 1 << 7, VRR_MODE_INFO = 1 << 8, TEARING_MODE_INFO = 1 << 9, + FORMAT_INFO = 1 << 10, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -440,6 +442,10 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.tearing_mode = m; to_send |= TEARING_MODE_INFO; } + HeadOp::SetFormat(f) => { + state.format = f; + to_send |= FORMAT_INFO; + } } } if to_send.contains(CORE_INFO) @@ -488,6 +494,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_mode(state); } + if to_send.contains(FORMAT_INFO) + && let Some(i) = &head.ext.format_info_v1 + { + i.send_format(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index fb9dd1ad..668b5fe5 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -67,6 +67,7 @@ pub fn handle(state: &Rc, connector: &Rc) { format: backend_state.format, color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, + supported_formats: Default::default(), }; let data = Rc::new(ConnectorData { id, @@ -324,6 +325,7 @@ impl ConnectorHandler { self.state.refresh_hardware_cursors(); } ConnectorEvent::FormatsChanged(formats) => { + self.data.head_managers.handle_formats_change(&formats); on.global.formats.set(formats); } ConnectorEvent::State(state) => { diff --git a/src/utils/rc_eq.rs b/src/utils/rc_eq.rs index 378fcc24..9c7e855c 100644 --- a/src/utils/rc_eq.rs +++ b/src/utils/rc_eq.rs @@ -4,6 +4,7 @@ pub fn rc_eq(a: &Rc, b: &Rc) -> bool { Rc::as_ptr(a) as *const u8 == Rc::as_ptr(b) as *const u8 } +#[derive(Default)] pub struct RcEq(pub Rc); impl Clone for RcEq { diff --git a/wire/jay_head_ext_format_setter_v1.txt b/wire/jay_head_ext_format_setter_v1.txt new file mode 100644 index 00000000..4608eeda --- /dev/null +++ b/wire/jay_head_ext_format_setter_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event supported_format { + format: u32, +} + +request set_format { + format: u32, +} diff --git a/wire/jay_head_manager_ext_format_setter_v1.txt b/wire/jay_head_manager_ext_format_setter_v1.txt new file mode 100644 index 00000000..5d9db104 --- /dev/null +++ b/wire/jay_head_manager_ext_format_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_format_setter_v1) (new), +} From 1a306c4fa9c2d81573df979556620ce8ade63a2e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 16:23:45 +0200 Subject: [PATCH 24/26] head-management: add drm-color-space-setter-v1 extension --- src/ifs/head_management.rs | 10 ++ .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_drm_color_space_setter_v1.rs | 129 ++++++++++++++++++ .../jay_head_manager_session_v1.rs | 16 +++ ...jay_head_ext_drm_color_space_setter_v1.txt | 23 ++++ ..._manager_ext_drm_color_space_setter_v1.txt | 7 + 7 files changed, 187 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs create mode 100644 wire/jay_head_ext_drm_color_space_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_drm_color_space_setter_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index e15cd19b..d3d734b9 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -131,6 +131,8 @@ enum HeadOp { SetVrrMode(VrrMode), SetTearingMode(TearingMode), SetFormat(&'static Format), + SetTransferFunction(BackendTransferFunction), + SetColorSpace(BackendColorSpace), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] @@ -280,6 +282,10 @@ impl HeadManagers { ext.send_mode(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.drm_color_space_setter_v1 { + ext.send_supported(state); + head.session.schedule_done(); + } } } @@ -310,6 +316,10 @@ impl HeadManagers { ext.send_state(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.drm_color_space_setter_v1 { + ext.send_supported(state); + head.session.schedule_done(); + } } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index f6862a77..99bbe9a1 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -409,4 +409,5 @@ declare_extensions! { jay_vrr_mode_setter_v1: JayVrrModeSetterV1, jay_tearing_mode_setter_v1: JayTearingModeSetterV1, format_setter_v1: FormatSetterV1, + drm_color_space_setter_v1: DrmColorSpaceSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 3eb0ee22..a901d8d9 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -6,6 +6,7 @@ pub(super) mod jay_head_ext_compositor_space_transformer_v1; pub(super) mod jay_head_ext_connector_info_v1; pub(super) mod jay_head_ext_core_info_v1; pub(super) mod jay_head_ext_drm_color_space_info_v1; +pub(super) mod jay_head_ext_drm_color_space_setter_v1; pub(super) mod jay_head_ext_format_info_v1; pub(super) mod jay_head_ext_format_setter_v1; pub(super) mod jay_head_ext_jay_tearing_mode_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs new file mode 100644 index 00000000..d6f7f486 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_drm_color_space_setter_v1.rs @@ -0,0 +1,129 @@ +use { + crate::{ + backend::{BackendColorSpace, BackendTransferFunction}, + ifs::head_management::{HeadOp, HeadState}, + video::drm::{ + DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, HDMI_EOTF_SMPTE_ST2084, + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + }, + wire::{ + jay_head_ext_drm_color_space_setter_v1::{ + JayHeadExtDrmColorSpaceSetterV1RequestHandler, Reset, SetColorimetry, SetHdmiEotf, + SupportedColorimetry, SupportedHdmiEotf, + }, + jay_head_manager_ext_drm_color_space_setter_v1::JayHeadManagerExtDrmColorSpaceSetterV1RequestHandler, + }, + }, + isnt::std_1::primitive::IsntSlice2Ext, + std::rc::Rc, +}; + +impl_drm_color_space_setter_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_supported(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.monitor_info != tran.monitor_info { + self.send_supported(shared); + } + } + + pub(in super::super) fn send_supported(&self, state: &HeadState) { + self.client.event(Reset { self_id: self.id }); + let Some(mi) = &state.monitor_info else { + return; + }; + self.send_supported_eotf(HDMI_EOTF_TRADITIONAL_GAMMA_SDR); + for tf in &mi.transfer_functions { + self.send_supported_eotf(tf.to_drm()); + } + self.send_supported_colorimetry(DRM_MODE_COLORIMETRY_DEFAULT); + for cs in &mi.color_spaces { + self.send_supported_colorimetry(cs.to_drm()); + } + } + + fn send_supported_eotf(&self, eotf: u8) { + self.client.event(SupportedHdmiEotf { + self_id: self.id, + eotf: eotf as u32, + }); + } + + fn send_supported_colorimetry(&self, colorimetry: u64) { + self.client.event(SupportedColorimetry { + self_id: self.id, + colorimetry: colorimetry as u32, + }); + } +} + +impl JayHeadManagerExtDrmColorSpaceSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtDrmColorSpaceSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn set_hdmi_eotf(&self, req: SetHdmiEotf, _slf: &Rc) -> Result<(), Self::Error> { + const DEFAULT: u32 = HDMI_EOTF_TRADITIONAL_GAMMA_SDR as u32; + const PQ: u32 = HDMI_EOTF_SMPTE_ST2084 as u32; + let eotf = match req.eotf { + DEFAULT => BackendTransferFunction::Default, + PQ => BackendTransferFunction::Pq, + _ => return Err(ErrorName::UnknownEotf(req.eotf)), + }; + if eotf != BackendTransferFunction::Default { + let state = &*self.common.transaction_state.borrow(); + let Some(mi) = &state.monitor_info else { + return Err(ErrorName::UnsupportedEotf(req.eotf)); + }; + if mi.transfer_functions.not_contains(&eotf) { + return Err(ErrorName::UnsupportedEotf(req.eotf)); + } + } + self.common.push_op(HeadOp::SetTransferFunction(eotf))?; + Ok(()) + } + + fn set_colorimetry(&self, req: SetColorimetry, _slf: &Rc) -> Result<(), Self::Error> { + let cs = match req.colorimetry as u64 { + DRM_MODE_COLORIMETRY_DEFAULT => BackendColorSpace::Default, + DRM_MODE_COLORIMETRY_BT2020_RGB => BackendColorSpace::Bt2020, + _ => return Err(ErrorName::UnknownColorimetry(req.colorimetry)), + }; + if cs != BackendColorSpace::Default { + let state = &*self.common.transaction_state.borrow(); + let Some(mi) = &state.monitor_info else { + return Err(ErrorName::UnsupportedColorimetry(req.colorimetry)); + }; + if mi.color_spaces.not_contains(&cs) { + return Err(ErrorName::UnsupportedColorimetry(req.colorimetry)); + } + } + self.common.push_op(HeadOp::SetColorSpace(cs))?; + Ok(()) + } +} + +error! { + #[error("Unknown EOTF {0}")] + UnknownEotf(u32), + #[error("Unknown colorimetry {0}")] + UnknownColorimetry(u32), + #[error("Unsupported EOTF {0}")] + UnsupportedEotf(u32), + #[error("Unsupported colorimetry {0}")] + UnsupportedColorimetry(u32), +} diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 94d97dd8..c6fb87f5 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -267,6 +267,8 @@ impl JayHeadManagerSessionV1 { new.mode = desired.mode; new.non_desktop_override = desired.override_non_desktop; new.format = desired.format; + new.color_space = desired.color_space; + new.transfer_function = desired.transfer_function; if old == new { continue; } @@ -389,6 +391,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { VRR_MODE_INFO = 1 << 8, TEARING_MODE_INFO = 1 << 9, FORMAT_INFO = 1 << 10, + DRM_COLOR_SPACE_INFO = 1 << 11, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -446,6 +449,14 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.format = f; to_send |= FORMAT_INFO; } + HeadOp::SetTransferFunction(e) => { + state.transfer_function = e; + to_send |= DRM_COLOR_SPACE_INFO; + } + HeadOp::SetColorSpace(c) => { + state.color_space = c; + to_send |= DRM_COLOR_SPACE_INFO; + } } } if to_send.contains(CORE_INFO) @@ -499,6 +510,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_format(state); } + if to_send.contains(DRM_COLOR_SPACE_INFO) + && let Some(i) = &head.ext.drm_color_space_info_v1 + { + i.send_state(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) diff --git a/wire/jay_head_ext_drm_color_space_setter_v1.txt b/wire/jay_head_ext_drm_color_space_setter_v1.txt new file mode 100644 index 00000000..9ce7eec0 --- /dev/null +++ b/wire/jay_head_ext_drm_color_space_setter_v1.txt @@ -0,0 +1,23 @@ +request destroy (destructor) { + +} + +event reset { + +} + +event supported_hdmi_eotf { + eotf: u32, +} + +event supported_colorimetry { + colorimetry: u32, +} + +request set_hdmi_eotf { + eotf: u32, +} + +request set_colorimetry { + colorimetry: u32, +} diff --git a/wire/jay_head_manager_ext_drm_color_space_setter_v1.txt b/wire/jay_head_manager_ext_drm_color_space_setter_v1.txt new file mode 100644 index 00000000..178120ba --- /dev/null +++ b/wire/jay_head_manager_ext_drm_color_space_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_drm_color_space_setter_v1) (new), +} From e0f1dd549d7623a833dadd0b3e76ff6ba55f875b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 16:47:25 +0200 Subject: [PATCH 25/26] head-management: add brightness-info-v1 extension --- src/compositor.rs | 1 + src/ifs/head_management.rs | 24 +++++- .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_brightness_info_v1.rs | 77 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 2 + src/tasks/connector.rs | 1 + src/tree/output.rs | 10 ++- wire/jay_head_ext_brightness_info_v1.txt | 15 ++++ ...ay_head_manager_ext_brightness_info_v1.txt | 7 ++ 10 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs create mode 100644 wire/jay_head_ext_brightness_info_v1.txt create mode 100644 wire/jay_head_manager_ext_brightness_info_v1.txt diff --git a/src/compositor.rs b/src/compositor.rs index 1217fa0f..020b960f 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -662,6 +662,7 @@ fn create_dummy_output(state: &Rc) { color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, supported_formats: Default::default(), + brightness: None, }; let connector_data = Rc::new(ConnectorData { id, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index d3d734b9..93facc4e 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -67,7 +67,7 @@ struct HeadCommon { pending: RefCell>, } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, PartialEq)] pub struct HeadState { pub name: RcEq, pub wl_output: Option, @@ -92,6 +92,7 @@ pub struct HeadState { pub color_space: BackendColorSpace, pub transfer_function: BackendTransferFunction, pub supported_formats: RcEq>, + pub brightness: Option, } impl HeadState { @@ -286,6 +287,11 @@ impl HeadManagers { ext.send_supported(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.brightness_info_v1 { + ext.send_implied_default_brightness(state); + ext.send_brightness(state); + head.session.schedule_done(); + } } } @@ -491,6 +497,10 @@ impl HeadManagers { ext.send_state(state); head.session.schedule_done(); } + if let Some(ext) = &head.ext.brightness_info_v1 { + ext.send_implied_default_brightness(state); + head.session.schedule_done(); + } } } @@ -505,4 +515,16 @@ impl HeadManagers { } } } + + pub fn handle_brightness_change(&self, brightness: Option) { + let state = &mut *self.state.borrow_mut(); + state.brightness = brightness; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.brightness_info_v1 { + ext.send_brightness(state); + head.session.schedule_done(); + } + } + } } diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 99bbe9a1..6c13a37c 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -410,4 +410,5 @@ declare_extensions! { jay_tearing_mode_setter_v1: JayTearingModeSetterV1, format_setter_v1: FormatSetterV1, drm_color_space_setter_v1: DrmColorSpaceSetterV1, + brightness_info_v1: BrightnessInfoV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index a901d8d9..0f797e1e 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,3 +1,4 @@ +pub(super) mod jay_head_ext_brightness_info_v1; pub(super) mod jay_head_ext_compositor_space_enabler_v1; pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs new file mode 100644 index 00000000..d1438f4b --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_info_v1.rs @@ -0,0 +1,77 @@ +use { + crate::{ + backend::BackendTransferFunction, + cmm::cmm_luminance::Luminance, + ifs::head_management::HeadState, + wire::{ + jay_head_ext_brightness_info_v1::{ + Brightness, DefaultBrightness, ImpliedDefaultBrightness, + JayHeadExtBrightnessInfoV1RequestHandler, + }, + jay_head_manager_ext_brightness_info_v1::JayHeadManagerExtBrightnessInfoV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_brightness_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_implied_default_brightness(shared); + self.send_brightness(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.transfer_function != tran.transfer_function { + self.send_implied_default_brightness(shared); + } + if shared.brightness != tran.brightness { + self.send_brightness(shared); + } + } + + pub(in super::super) fn send_implied_default_brightness(&self, shared: &HeadState) { + let lux = match shared.transfer_function { + BackendTransferFunction::Default => shared + .monitor_info + .as_ref() + .and_then(|m| m.luminance.as_ref()) + .map(|l| l.max) + .unwrap_or(Luminance::SRGB.white.0), + BackendTransferFunction::Pq => Luminance::ST2084_PQ.white.0, + }; + self.client.event(ImpliedDefaultBrightness { + self_id: self.id, + lux: (lux as f32).to_bits(), + }) + } + + pub(in super::super) fn send_brightness(&self, shared: &HeadState) { + match shared.brightness { + None => self.client.event(DefaultBrightness { self_id: self.id }), + Some(b) => self.client.event(Brightness { + self_id: self.id, + lux: (b as f32).to_bits(), + }), + } + } +} + +impl JayHeadManagerExtBrightnessInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtBrightnessInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index c6fb87f5..7fb77948 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -392,6 +392,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { TEARING_MODE_INFO = 1 << 9, FORMAT_INFO = 1 << 10, DRM_COLOR_SPACE_INFO = 1 << 11, + BRIGHTNESS_INFO = 1 << 12, COMPOSITOR_SPACE_INFO_ENABLED = 1 << 13, } for head in self.heads.lock().values() { @@ -452,6 +453,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { HeadOp::SetTransferFunction(e) => { state.transfer_function = e; to_send |= DRM_COLOR_SPACE_INFO; + to_send |= BRIGHTNESS_INFO; } HeadOp::SetColorSpace(c) => { state.color_space = c; diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 668b5fe5..3853a195 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -68,6 +68,7 @@ pub fn handle(state: &Rc, connector: &Rc) { color_space: backend_state.color_space, transfer_function: backend_state.transfer_function, supported_formats: Default::default(), + brightness: None, }; let data = Rc::new(ConnectorData { id, diff --git a/src/tree/output.rs b/src/tree/output.rs index 83927c97..dd2aa0a0 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -913,8 +913,14 @@ impl OutputNode { } pub fn set_brightness(&self, brightness: Option) { - self.global.persistent.brightness.set(brightness); - self.update_color_description(); + let old = self.global.persistent.brightness.replace(brightness); + if old != brightness { + self.update_color_description(); + self.global + .connector + .head_managers + .handle_brightness_change(brightness); + } } fn find_stacked_at( diff --git a/wire/jay_head_ext_brightness_info_v1.txt b/wire/jay_head_ext_brightness_info_v1.txt new file mode 100644 index 00000000..95b6bdcf --- /dev/null +++ b/wire/jay_head_ext_brightness_info_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event implied_default_brightness { + lux: u32, +} + +event default_brightness { + +} + +event brightness { + lux: u32, +} diff --git a/wire/jay_head_manager_ext_brightness_info_v1.txt b/wire/jay_head_manager_ext_brightness_info_v1.txt new file mode 100644 index 00000000..b8271532 --- /dev/null +++ b/wire/jay_head_manager_ext_brightness_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_brightness_info_v1) (new), +} From 2d27dd3d644c4050af44dc96e27abf709b996bbb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 14 Jul 2025 16:52:35 +0200 Subject: [PATCH 26/26] head-management: add brightness-setter-v1 extension --- src/ifs/head_management.rs | 1 + .../head_management/head_management_macros.rs | 1 + src/ifs/head_management/jay_head_ext.rs | 1 + .../jay_head_ext_brightness_setter_v1.rs | 41 +++++++++++++++++++ .../jay_head_manager_session_v1.rs | 11 +++++ wire/jay_head_ext_brightness_setter_v1.txt | 11 +++++ ..._head_manager_ext_brightness_setter_v1.txt | 7 ++++ 7 files changed, 73 insertions(+) create mode 100644 src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_setter_v1.rs create mode 100644 wire/jay_head_ext_brightness_setter_v1.txt create mode 100644 wire/jay_head_manager_ext_brightness_setter_v1.txt diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 93facc4e..fbb30b70 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -134,6 +134,7 @@ enum HeadOp { SetFormat(&'static Format), SetTransferFunction(BackendTransferFunction), SetColorSpace(BackendColorSpace), + SetBrightness(Option), } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs index 6c13a37c..8168e846 100644 --- a/src/ifs/head_management/head_management_macros.rs +++ b/src/ifs/head_management/head_management_macros.rs @@ -411,4 +411,5 @@ declare_extensions! { format_setter_v1: FormatSetterV1, drm_color_space_setter_v1: DrmColorSpaceSetterV1, brightness_info_v1: BrightnessInfoV1, + brightness_setter_v1: BrightnessSetterV1, } diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs index 0f797e1e..d672e75f 100644 --- a/src/ifs/head_management/jay_head_ext.rs +++ b/src/ifs/head_management/jay_head_ext.rs @@ -1,4 +1,5 @@ pub(super) mod jay_head_ext_brightness_info_v1; +pub(super) mod jay_head_ext_brightness_setter_v1; pub(super) mod jay_head_ext_compositor_space_enabler_v1; pub(super) mod jay_head_ext_compositor_space_info_v1; pub(super) mod jay_head_ext_compositor_space_positioner_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_setter_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_setter_v1.rs new file mode 100644 index 00000000..309e9134 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_brightness_setter_v1.rs @@ -0,0 +1,41 @@ +use { + crate::{ + ifs::head_management::HeadOp, + wire::{ + jay_head_ext_brightness_setter_v1::{ + JayHeadExtBrightnessSetterV1RequestHandler, SetBrightness, UnsetBrightness, + }, + jay_head_manager_ext_brightness_setter_v1::JayHeadManagerExtBrightnessSetterV1RequestHandler, + }, + }, + std::rc::Rc, +}; + +impl_brightness_setter_v1! { + version = 1, +} + +impl JayHeadManagerExtBrightnessSetterV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtBrightnessSetterV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); + + fn unset_brightness(&self, _req: UnsetBrightness, _slf: &Rc) -> Result<(), Self::Error> { + self.common.push_op(HeadOp::SetBrightness(None))?; + Ok(()) + } + + fn set_brightness(&self, req: SetBrightness, _slf: &Rc) -> Result<(), Self::Error> { + self.common + .push_op(HeadOp::SetBrightness(Some(f32::from_bits(req.lux) as f64)))?; + Ok(()) + } +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 7fb77948..7053dfa4 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -459,6 +459,10 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { state.color_space = c; to_send |= DRM_COLOR_SPACE_INFO; } + HeadOp::SetBrightness(b) => { + state.brightness = b; + to_send |= BRIGHTNESS_INFO; + } } } if to_send.contains(CORE_INFO) @@ -517,6 +521,11 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { { i.send_state(state); } + if to_send.contains(BRIGHTNESS_INFO) + && let Some(i) = &head.ext.brightness_info_v1 + { + i.send_brightness(state); + } } slf.schedule_transaction_result(req.result, None)?; Ok(()) @@ -554,6 +563,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { node.update_transform(desired.transform); node.set_vrr_mode(VrrMode::from_config(desired.vrr_mode).unwrap()); node.set_tearing_mode(TearingMode::from_config(desired.tearing_mode).unwrap()); + node.set_brightness(desired.brightness); } else if let Some(mi) = &desired.monitor_info { let pos = &self.client.state.persistent_output_states; let pos = match pos.get(&mi.output_id) { @@ -579,6 +589,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { .set(VrrMode::from_config(desired.vrr_mode).unwrap()); pos.tearing_mode .set(TearingMode::from_config(desired.tearing_mode).unwrap()); + pos.brightness.set(desired.brightness); } } slf.schedule_transaction_result(req.result, None)?; diff --git a/wire/jay_head_ext_brightness_setter_v1.txt b/wire/jay_head_ext_brightness_setter_v1.txt new file mode 100644 index 00000000..99475121 --- /dev/null +++ b/wire/jay_head_ext_brightness_setter_v1.txt @@ -0,0 +1,11 @@ +request destroy (destructor) { + +} + +request unset_brightness { + +} + +request set_brightness { + lux: u32, +} diff --git a/wire/jay_head_manager_ext_brightness_setter_v1.txt b/wire/jay_head_manager_ext_brightness_setter_v1.txt new file mode 100644 index 00000000..ece58ce7 --- /dev/null +++ b/wire/jay_head_manager_ext_brightness_setter_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_brightness_setter_v1) (new), +}