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/backend.rs b/src/backend.rs index c93dbe8e..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, @@ -124,6 +125,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 +140,9 @@ pub trait Connector: Any { fn damage(&self); fn drm_dev(&self) -> Option; fn effectively_locked(&self) -> bool; + fn caps(&self) -> ConnectorCaps { + ConnectorCaps::none() + } fn drm_feedback(&self) -> Option> { None } 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/backends/metal/video.rs b/src/backends/metal/video.rs index 4b1aaaed..ecf24277 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, @@ -726,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 }); } @@ -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() } @@ -1910,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/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..020b960f 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, @@ -72,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, @@ -339,6 +346,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 +533,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 +617,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 +634,42 @@ 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()), + position: (0, 0), + size: (0, 0), + active: false, + connected: false, + transform: Transform::None, + scale: Default::default(), + wl_output: None, + connector_enabled: true, + in_compositor_space: false, + mode: Default::default(), + monitor_info: None, + inherent_non_desktop: false, + override_non_desktop: None, + vrr: false, + 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, + supported_formats: Default::default(), + brightness: 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 +677,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, @@ -692,6 +736,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/config/handler.rs b/src/config/handler.rs index 514c68fb..b5bcc141 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(()) } @@ -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), } @@ -1414,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/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..fbb30b70 --- /dev/null +++ b/src/ifs/head_management.rs @@ -0,0 +1,531 @@ +use { + crate::{ + backend::{ + BackendColorSpace, BackendTransferFunction, ConnectorId, Mode, MonitorInfo, + transaction::BackendConnectorTransactionError, + }, + client::ClientId, + format::Format, + globals::GlobalName, + ifs::head_management::{ + 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::{TearingMode, Transform, VrrMode}, + 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, PartialEq)] +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), + pub mode: Mode, + pub transform: Transform, + pub scale: Scale, + pub monitor_info: Option>, + 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 tearing_mode: TearingMode, + pub format: &'static Format, + pub color_space: BackendColorSpace, + pub transfer_function: BackendTransferFunction, + pub supported_formats: RcEq>, + pub brightness: 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; + } + if self.override_non_desktop == Some(true) { + return; + } + 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), + SetConnectorEnabled(bool), + SetTransform(Transform), + SetScale(Scale), + SetMode(usize), + SetNonDesktopOverride(Option), + SetVrrMode(VrrMode), + SetTearingMode(TearingMode), + SetFormat(&'static Format), + SetTransferFunction(BackendTransferFunction), + SetColorSpace(BackendColorSpace), + SetBrightness(Option), +} + +#[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) + } + } + + 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.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(); + 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(); + state.tearing_mode = n.global.persistent.tearing_mode.get().to_config(); + } + 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.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(); + } + 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(); + } + if let Some(ext) = &head.ext.core_info_v1 { + 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(); + } + if let Some(ext) = &head.ext.vrr_state_v1 { + 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(); + } + if let Some(ext) = &head.ext.jay_tearing_mode_info_v1 { + 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(); + } + if let Some(ext) = &head.ext.brightness_info_v1 { + ext.send_implied_default_brightness(state); + ext.send_brightness(state); + head.session.schedule_done(); + } + } + } + + 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() { + 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.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(); + } + if let Some(ext) = &head.ext.physical_display_info_v1 { + 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(); + } + if let Some(ext) = &head.ext.drm_color_space_setter_v1 { + ext.send_supported(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(); + 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 { + ext.send_position(state); + 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(); + } + } + } + + 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(); + } + } + } + + 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(); + } + } + } + + 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(); + } + } + } + + 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(); + } + } + } + + 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; + 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(); + } + } + } + + 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; + 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(); + } + } + } + + 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(); + } + if let Some(ext) = &head.ext.brightness_info_v1 { + ext.send_implied_default_brightness(state); + head.session.schedule_done(); + } + } + } + + 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(); + } + } + } + + 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 new file mode 100644 index 00000000..8168e846 --- /dev/null +++ b/src/ifs/head_management/head_management_macros.rs @@ -0,0 +1,415 @@ +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, + compositor_space_info_v1: CompositorSpaceInfoV1, + compositor_space_positioner_v1: CompositorSpacePositionerV1, + compositor_space_transformer_v1: CompositorSpaceTransformerV1, + compositor_space_scaler_v1: CompositorSpaceScalerV1, + compositor_space_enabler_v1: CompositorSpaceEnablerV1, + connector_info_v1: ConnectorInfoV1, + mode_info_v1: ModeInfoV1, + mode_setter_v1: ModeSetterV1, + physical_display_info_v1: PhysicalDisplayInfoV1, + non_desktop_info_v1: NonDesktopInfoV1, + vrr_state_v1: VrrStateV1, + tearing_state_v1: TearingStateV1, + format_info_v1: FormatInfoV1, + drm_color_space_info_v1: DrmColorSpaceInfoV1, + non_desktop_override_v1: NonDesktopOverrideV1, + jay_vrr_mode_info_v1: JayVrrModeInfoV1, + jay_tearing_mode_info_v1: JayTearingModeInfoV1, + jay_vrr_mode_setter_v1: JayVrrModeSetterV1, + jay_tearing_mode_setter_v1: JayTearingModeSetterV1, + 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_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..d672e75f --- /dev/null +++ b/src/ifs/head_management/jay_head_ext.rs @@ -0,0 +1,24 @@ +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; +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_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; +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; +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_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_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_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_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_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_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_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_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/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_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/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_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/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_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/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_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/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_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_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_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_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_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..0c1fff34 --- /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_effective { + 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/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/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/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..7053dfa4 --- /dev/null +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -0,0 +1,634 @@ +use { + super::HeadMgrCommon, + crate::{ + backend::transaction::{ConnectorTransaction, PreparedConnectorTransaction}, + client::{Client, ClientError}, + ifs::{ + head_management::{ + 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, + 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(); + let mut new = old; + new.enabled = desired.connector_enabled; + 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; + } + 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, + 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, + MODE_INFO = 1 << 6, + NON_DESKTOP_INFO = 1 << 7, + VRR_MODE_INFO = 1 << 8, + 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() { + let pending = mem::take(&mut *head.common.pending.borrow_mut()); + let snapshot = &*head.common.snapshot_state.borrow(); + let state = &mut *head.common.transaction_state.borrow_mut(); + let mut to_send = ToSend::default(); + for op in pending { + match op { + HeadOp::SetPosition(x, y) => { + 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(); + to_send |= COMPOSITOR_SPACE_INFO_TRANSFORM; + to_send |= COMPOSITOR_SPACE_INFO_SIZE; + } + HeadOp::SetScale(s) => { + state.scale = s; + state.update_size(); + 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; + } + 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; + } + HeadOp::SetVrrMode(m) => { + state.vrr_mode = m; + to_send |= VRR_MODE_INFO; + } + HeadOp::SetTearingMode(m) => { + state.tearing_mode = m; + to_send |= TEARING_MODE_INFO; + } + HeadOp::SetFormat(f) => { + state.format = f; + to_send |= FORMAT_INFO; + } + HeadOp::SetTransferFunction(e) => { + state.transfer_function = e; + to_send |= DRM_COLOR_SPACE_INFO; + to_send |= BRIGHTNESS_INFO; + } + HeadOp::SetColorSpace(c) => { + 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) + && let Some(i) = &head.ext.core_info_v1 + { + 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); + } + } + } + if to_send.contains(MODE_INFO) + && let Some(i) = &head.ext.mode_info_v1 + { + i.send_mode(state); + } + if to_send.contains(NON_DESKTOP_INFO) + && let Some(i) = &head.ext.non_desktop_info_v1 + { + 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); + } + if to_send.contains(TEARING_MODE_INFO) + && let Some(i) = &head.ext.jay_tearing_mode_info_v1 + { + i.send_mode(state); + } + if to_send.contains(FORMAT_INFO) + && let Some(i) = &head.ext.format_info_v1 + { + 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); + } + 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(()) + } + + 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 + { + 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()); + 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) { + 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 + } + }; + 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()); + pos.tearing_mode + .set(TearingMode::from_config(desired.tearing_mode).unwrap()); + pos.brightness.set(desired.brightness); + } + } + 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/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 2ab860d9..29e7ea2f 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(()); } @@ -447,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/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/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/state.rs b/src/state.rs index b8e3dfad..18e1bc3a 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>, } @@ -422,14 +433,44 @@ 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 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 old.non_desktop_override != s.non_desktop_override { + 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 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 (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 { - node.update_state(s); - } else { - self.state.set(s); + node.update_state(old, s); } - Ok(()) } } @@ -1010,6 +1051,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 9b9b752b..3853a195 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -7,14 +7,18 @@ 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, + }, }, + jay_config::video::Transform, std::{cell::Cell, collections::VecDeque, rc::Rc}, }; @@ -39,11 +43,39 @@ 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()), + position: (0, 0), + size: (0, 0), + active: backend_state.active, + connected: false, + transform: Transform::None, + scale: Default::default(), + wl_output: None, + connector_enabled: backend_state.enabled, + in_compositor_space: false, + mode: Default::default(), + monitor_info: None, + 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, + tearing_mode: Default::default(), + format: backend_state.format, + color_space: backend_state.color_space, + transfer_function: backend_state.transfer_function, + supported_formats: Default::default(), + brightness: 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 +83,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 +95,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,22 +136,21 @@ 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) { 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 { + if info.non_desktop_effective { self.handle_non_desktop_connected(info).await; } else { 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()); } @@ -214,6 +249,7 @@ impl ConnectorHandler { tray_items: Default::default(), ext_workspace_groups: Default::default(), pinned: Default::default(), + tearing: Default::default(), }); on.update_visible(); on.update_rects(); @@ -221,11 +257,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(); @@ -277,6 +313,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 { @@ -287,10 +326,11 @@ 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) => { - on.update_state(state); + self.data.set_state(&self.state, state); } ev => unreachable!("received unexpected event {:?}", ev), } @@ -365,7 +405,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(), }); @@ -390,6 +430,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/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..dd2aa0a0 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) { @@ -470,6 +479,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,18 +813,30 @@ 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); } } 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) { @@ -847,14 +872,13 @@ 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, 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); @@ -889,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( @@ -1300,6 +1330,28 @@ 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 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 { @@ -1750,13 +1802,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/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..9c7e855c 100644 --- a/src/utils/rc_eq.rs +++ b/src/utils/rc_eq.rs @@ -1,5 +1,30 @@ -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 } + +#[derive(Default)] +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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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_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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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), +} 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 { + +}