diff --git a/Cargo.lock b/Cargo.lock index dcbafa8c..060bcd72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,6 +627,7 @@ dependencies = [ "tiny-skia", "tracy-client-sys", "uapi", + "with_builtin_macros", ] [[package]] @@ -1740,6 +1741,26 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "with_builtin_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24deb3cd6e530e7617b12b1f0f1ce160a3a71d92feb351c4db5156d1d10e398a" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2259ae9b1285596f1ee52ce8f627013c65853d4d7f271cb10bfe2d048769804a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index ca184928..5924b75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] } regex = "1.11.1" cfg-if = "1.0.0" opera = "1.0.1" +with_builtin_macros = "0.1.0" [build-dependencies] repc = "0.1.1" diff --git a/src/client.rs b/src/client.rs index f7052bae..f7f46712 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,6 +64,7 @@ bitflags! { CAP_INPUT_METHOD = 1 << 10, CAP_WORKSPACE = 1 << 11, CAP_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, + CAP_HEAD_MANAGER = 1 << 13, } pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); diff --git a/src/client/objects.rs b/src/client/objects.rs index 88c440a5..ab625f91 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -6,6 +6,7 @@ use { ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1, ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, + head_management::jay_head_error_v1::JayHeadErrorV1, ipc::{ data_control::{ ext_data_control_source_v1::ExtDataControlSourceV1, @@ -41,12 +42,13 @@ use { }, wire::{ ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id, - ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId, - JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, - WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, - WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, - XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, - ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, + ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayHeadErrorV1Id, + JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, + WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, + WlSurfaceId, WpDrmLeaseConnectorV1Id, WpImageDescriptionV1Id, + WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, + XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, + ZwpTabletToolV2Id, }, }, std::{cell::RefCell, rc::Rc}, @@ -87,6 +89,7 @@ pub struct Objects { pub ext_workspace_groups: CopyHashMap>, pub wp_image_description: CopyHashMap>, + pub jay_head_errors: CopyHashMap>, ids: RefCell>, } @@ -126,6 +129,7 @@ impl Objects { ext_data_sources: Default::default(), ext_workspace_groups: Default::default(), wp_image_description: Default::default(), + jay_head_errors: Default::default(), ids: RefCell::new(vec![]), } } @@ -168,6 +172,7 @@ impl Objects { self.ext_copy_sessions.clear(); self.ext_data_sources.clear(); self.ext_workspace_groups.clear(); + self.jay_head_errors.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/compositor.rs b/src/compositor.rs index edc3f916..ad857998 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -29,6 +29,9 @@ use { format::XRGB8888, globals::Globals, ifs::{ + head_management::{ + HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done, + }, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, wl_seat::handle_position_hint_requests, @@ -62,6 +65,7 @@ use { numcell::NumCell, oserror::OsError, queue::AsyncQueue, + rc_eq::RcEq, refcounted::RefCounted, run_toplevel::RunToplevel, tri::Try, @@ -339,6 +343,9 @@ fn start_compositor2( node_at_tree: Default::default(), position_hint_requests: Default::default(), backend_connector_state_serials: Default::default(), + head_names: Default::default(), + head_managers: Default::default(), + head_managers_async: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -523,6 +530,11 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { "position hint requests", handle_position_hint_requests(state.clone()), ), + eng.spawn2( + "jay head manager send done", + Phase::Layout, + handle_jay_head_manager_done(state.clone()), + ), ] } @@ -602,9 +614,6 @@ fn create_dummy_output(state: &Rc) { tearing_mode: Cell::new(&TearingMode::Never), brightness: Cell::new(None), }); - let connector = Rc::new(DummyOutput { - id: state.connector_ids.next(), - }) as Rc; let mode = backend::Mode { width: 0, height: 0, @@ -622,11 +631,21 @@ fn create_dummy_output(state: &Rc) { color_space: Default::default(), transfer_function: Default::default(), }; + let id = state.connector_ids.next(); + let connector = Rc::new(DummyOutput { id }) as Rc; + let name = Rc::new("Dummy".to_string()); + let head_name = state.head_names.next(); + let head_state = HeadState { + name: RcEq(name.clone()), + wl_output: None, + monitor_info: None, + }; let connector_data = Rc::new(ConnectorData { + id, connector, handler: Cell::new(None), connected: Cell::new(true), - name: "Dummy".to_string(), + name, drm_dev: None, async_event: Default::default(), damaged: Cell::new(false), @@ -634,6 +653,7 @@ fn create_dummy_output(state: &Rc) { needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), state: Cell::new(backend_state), + head_managers: HeadManagers::new(head_name, head_state), }); let schedule = Rc::new(OutputSchedule::new( &state.ring, diff --git a/src/config/handler.rs b/src/config/handler.rs index 514c68fb..62f3f43c 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1175,7 +1175,7 @@ impl ConfigProxyHandler { fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_connector(connector)?; self.respond(Response::GetConnectorName { - name: connector.name.clone(), + name: connector.name.deref().clone(), }); Ok(()) } diff --git a/src/globals.rs b/src/globals.rs index 6ce60fa6..2aeab076 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -10,6 +10,7 @@ use { ext_image_copy::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1Global, ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, + head_management::jay_head_manager_v1::JayHeadManagerV1Global, ipc::{ data_control::{ ext_data_control_manager_v1::ExtDataControlManagerV1Global, @@ -225,6 +226,7 @@ impl Globals { add_singleton!(ExtWorkspaceManagerV1Global); add_singleton!(WpColorManagerV1Global); add_singleton!(XdgToplevelTagManagerV1Global); + add_singleton!(JayHeadManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index d6af56ed..c3995f05 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -9,6 +9,7 @@ pub mod ext_image_copy; pub mod ext_output_image_capture_source_manager_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; +pub mod head_management; pub mod ipc; pub mod jay_client_query; pub mod jay_color_management; diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs new file mode 100644 index 00000000..b5a539ae --- /dev/null +++ b/src/ifs/head_management.rs @@ -0,0 +1,191 @@ +use { + crate::{ + backend::{ConnectorId, MonitorInfo, transaction::BackendConnectorTransactionError}, + client::ClientId, + globals::GlobalName, + ifs::head_management::{ + head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, + jay_head_v1::JayHeadV1, + }, + state::OutputData, + utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq}, + wire::JayHeadManagerSessionV1Id, + }, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + thiserror::Error, +}; + +#[macro_use] +mod head_management_macros; +pub mod jay_head_error_v1; +mod jay_head_ext; +pub mod jay_head_manager_session_v1; +pub mod jay_head_manager_v1; +mod jay_head_transaction_result_v1; +mod jay_head_v1; + +linear_ids!(HeadNames, HeadName, u64); + +struct Head { + session: Rc, + common: Rc, + head: Rc, + ext: HeadExts, +} + +#[derive(Error, Debug)] +enum HeadTransactionError { + #[error("The connector {} has been removed", .0)] + HeadRemoved(ConnectorId), + #[error("The display connected to connector {} has changed", .0)] + MonitorChanged(ConnectorId), + #[error("The transaction has already failed")] + AlreadyFailed, + #[error(transparent)] + Backend(#[from] BackendConnectorTransactionError), +} + +struct HeadCommon { + mgr: Rc, + name: HeadName, + id: ConnectorId, + removed: Cell, + + shared: Rc>, + snapshot_state: RefCell, + transaction_state: RefCell, + pending: RefCell>, +} + +#[derive(Clone, Eq, PartialEq)] +pub struct HeadState { + pub name: RcEq, + pub wl_output: Option, + pub monitor_info: Option>, +} + +enum HeadOp {} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +enum HeadMgrState { + #[default] + Init, + Started, + StopScheduled, + Stopped, +} + +struct HeadMgrCommon { + state: Cell, + in_transaction: Cell, + transaction_failed: Cell, +} + +impl HeadCommon { + fn assert_removed(&self) -> Result<(), HeadCommonError> { + if self.removed.get() { + Ok(()) + } else { + Err(HeadCommonError::NotYetRemoved) + } + } + + fn assert_in_transaction(&self) -> Result<(), HeadCommonError> { + if self.mgr.in_transaction.get() { + Ok(()) + } else { + Err(HeadCommonError::NotInTransaction) + } + } + + #[expect(dead_code)] + fn push_op(&self, op: HeadOp) -> Result<(), HeadCommonError> { + self.assert_in_transaction()?; + self.pending.borrow_mut().push(op); + Ok(()) + } +} + +impl HeadMgrCommon { + fn assert_stopped(&self) -> Result<(), HeadCommonError> { + if self.state.get() == HeadMgrState::Stopped { + Ok(()) + } else { + Err(HeadCommonError::NotYetStopped) + } + } +} + +#[derive(Debug, Error)] +pub enum HeadCommonError { + #[error("Head has not yet been removed")] + NotYetRemoved, + #[error("Manager is not inside a transaction")] + NotInTransaction, + #[error("Manager has not yet been stopped")] + NotYetStopped, +} + +pub struct HeadManagers { + name: HeadName, + state: Rc>, + managers: CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc>, +} + +macro_rules! skip_in_transaction { + ($mgr:expr) => { + if $mgr.common.mgr.in_transaction.get() { + continue; + } + }; +} + +impl HeadManagers { + pub fn new(name: HeadName, state: HeadState) -> Self { + Self { + name, + state: Rc::new(RefCell::new(state)), + managers: Default::default(), + } + } + + pub fn handle_removed(&self) { + for head in self.managers.lock().drain_values() { + skip_in_transaction!(head); + head.session.heads.remove(&self.name); + head.head.send_removed(); + head.session.schedule_done(); + } + } + + pub fn handle_output_connected(&self, output: &OutputData) { + let state = &mut *self.state.borrow_mut(); + state.monitor_info = Some(RcEq(output.monitor_info.clone())); + if let Some(n) = &output.node { + state.wl_output = Some(n.global.name); + } + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.core_info_v1 { + ext.send_wl_output(state); + head.session.schedule_done(); + } + } + } + + pub fn handle_output_disconnected(&self) { + let state = &mut *self.state.borrow_mut(); + state.wl_output = None; + state.monitor_info = None; + for head in self.managers.lock().values() { + skip_in_transaction!(head); + if let Some(ext) = &head.ext.core_info_v1 { + ext.send_wl_output(state); + head.session.schedule_done(); + } + } + } +} diff --git a/src/ifs/head_management/head_management_macros.rs b/src/ifs/head_management/head_management_macros.rs new file mode 100644 index 00000000..a99a79d4 --- /dev/null +++ b/src/ifs/head_management/head_management_macros.rs @@ -0,0 +1,392 @@ +use { + crate::{ + client::ClientError, + ifs::head_management::{ + Head, HeadState, + jay_head_manager_session_v1::{JayHeadManagerSessionV1, JayHeadManagerSessionV1Error}, + jay_head_manager_v1::JayHeadManagerV1, + jay_head_v1::JayHeadV1, + }, + object::{ObjectId, Version}, + state::ConnectorData, + utils::clonecell::CloneCell, + }, + std::rc::Rc, +}; + +macro_rules! error_ { + ($error_name:ident, $($tt:tt)*) => { + #[derive(Debug, thiserror::Error)] + pub enum $error_name { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] super::super::HeadCommonError), + $($tt)* + } + efrom!($error_name, ClientError, crate::client::ClientError); + }; +} + +macro_rules! impl_head_ext { + ( + $snake:ident, + $camel:ident, + $macro_name:ident, + $mgr_module:ident, + $mgr_name:ident, + $mgr_id_name:ident, + $head_module:ident, + $head_name:ident, + $head_id_name:ident, + $version:expr, + $(filter = $filter:ident,)? + $(after_announce = $after_announce:ident,)? + $(after_transaction = $after_transaction:ident,)? + ) => { + pub(in super::super) struct $mgr_name { + pub(in super::super) id: crate::wire::$mgr_id_name, + pub(in super::super) client: std::rc::Rc, + pub(in super::super) tracker: crate::leaks::Tracker, + pub(in super::super) version: crate::object::Version, + pub(in super::super) common: std::rc::Rc, + } + + impl $mgr_name { + pub(in super::super) const VERSION: crate::object::Version = crate::object::Version($version); + pub(in super::super) const NAME: &str = concat!("jay_head_manager_ext_", stringify!($snake)); + } + + object_base! { + self = $mgr_name; + version = self.version; + } + + impl crate::object::Object for $mgr_name {} + + simple_add_obj!($mgr_name); + + pub(in super::super) struct $head_name { + pub(in super::super) id: crate::wire::$head_id_name, + pub(in super::super) client: std::rc::Rc, + pub(in super::super) tracker: crate::leaks::Tracker, + pub(in super::super) version: crate::object::Version, + pub(in super::super) common: std::rc::Rc, + } + + object_base! { + self = $head_name; + version = self.version; + } + + impl crate::object::Object for $head_name {} + + simple_add_obj!($head_name); + + impl $mgr_name { + pub(in super::super) fn announce( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + connector: &crate::state::ConnectorData, + common: &std::rc::Rc, + ) -> Result>, crate::client::ClientError> { + $( + if !self.$filter(connector, common) { + return Ok(None); + } + )? + let obj = std::rc::Rc::new($head_name { + id: self.client.new_id()?, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: common.clone(), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + self.send_head(&obj); + Ok(Some(obj)) + } + + fn send_head(&self, obj: &$head_name) { + self.client.event(crate::wire::$mgr_module::Head { + self_id: self.id, + head: obj.id, + }); + } + } + + impl $head_name { + pub(in super::super) fn after_announce_wrapper( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + shared: &super::super::HeadState, + ) { + $( + self.$after_announce(shared); + )? + } + + pub(in super::super) fn after_transaction_wrapper( + &self, + #[allow(clippy::allow_attributes, unused_variables)] + shared: &super::super::HeadState, + #[allow(clippy::allow_attributes, unused_variables)] + tran: &super::super::HeadState, + ) { + $( + self.$after_transaction(shared, tran); + )? + } + } + } +} + +#[rustfmt::skip] +macro_rules! declare_extension_type_macro { + ( + ($dollar:tt), + $snake:ident, + $camel:ident, + $macro_name:ident, + $macro_name_int:ident, + $mgr_module:ident, + $mgr_name:ident, + $mgr_id_name:ident, + $mgr_request_handler:ident, + $head_module:ident, + $head_name:ident, + $head_id_name:ident, + $head_request_handler:ident, + $error_name:ident, + ) => { + macro_rules! $macro_name { + ( + version = $version:expr, + $dollar(filter = $filter:ident,)? + $dollar(after_announce = $after_announce:ident,)? + $dollar(after_transaction = $after_transaction:ident,)? + ) => { + $macro_name_int! { + ($), + version = $version, + $dollar(filter = $filter,)? + $dollar(after_announce = $after_announce,)? + $dollar(after_transaction = $after_transaction,)? + } + } + } + + macro_rules! $macro_name_int { + ( + ($dollar2:tt), + version = $version:expr, + $dollar(filter = $filter:ident,)? + $dollar(after_announce = $after_announce:ident,)? + $dollar(after_transaction = $after_transaction:ident,)? + ) => { + impl_head_ext!( + $snake, + $camel, + $macro_name, + $mgr_module, + $mgr_name, + $mgr_id_name, + $head_module, + $head_name, + $head_id_name, + $version, + $dollar(filter = $filter,)? + $dollar(after_announce = $after_announce,)? + $dollar(after_transaction = $after_transaction,)? + ); + + macro_rules! head_common_req { + () => { + head_common_req_!($snake); + } + } + + macro_rules! mgr_common_req { + () => { + mgr_common_req_!($snake); + } + } + + type ErrorName = $error_name; + type MgrName = $mgr_name; + type HeadName = $head_name; + + macro_rules! error { + ($dollar2($tt:tt)*) => { + error_!($error_name, $dollar2($tt)*); + } + } + } + } + }; +} + +macro_rules! mgr_common_req_ { + ($snake:ident) => { + with_builtin_macros::with_eager_expansions! { + fn destroy( + &self, + _req: crate::wire::#{concat_idents!(jay_head_manager_ext_, $snake)}::Destroy, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.common.assert_stopped()?; + self.client.remove_obj(self)?; + Ok(()) + } + } + }; +} + +macro_rules! head_common_req_ { + ($snake:ident) => { + with_builtin_macros::with_eager_expansions! { + fn destroy( + &self, + _req: crate::wire::#{concat_idents!(jay_head_ext_, $snake)}::Destroy, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.common.assert_removed()?; + self.client.remove_obj(self)?; + Ok(()) + } + } + }; +} + +macro_rules! declare_extensions { + ($($snake:ident: $camel:ident,)*) => { + #[derive(linearize::Linearize)] + pub(super) enum HeadExtension { + $($camel,)* + } + + pub(super) fn send_available_extensions(mgr: &JayHeadManagerV1) { + use linearize::Linearize; + with_builtin_macros::with_eager_expansions! { + $( + type $camel = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)}; + mgr.send_extension( + HeadExtension::$camel.linearize() as _, + $camel::NAME, + $camel::VERSION, + ); + )* + } + } + + pub(super) fn announce_head( + session: &Rc, + head: &Rc, + connector: &ConnectorData, + ) -> Result, ClientError> { + session.send_head_start(head, connector.head_managers.name); + let head = super::Head { + session: session.clone(), + common: head.common.clone(), + head: head.clone(), + ext: HeadExts { + $( + $snake: match session.ext.$snake.get() { + Some(f) => f.announce(connector, &head.common)?, + _ => None, + }, + )* + }, + }; + session.send_head_complete(); + let shared = &*connector.head_managers.state.borrow(); + $( + if let Some(ext) = &head.ext.$snake { + ext.after_announce_wrapper(shared); + } + )* + Ok(Rc::new(head)) + } + + pub(super) fn bind_extension(session: &JayHeadManagerSessionV1, ext: HeadExtension, name: u32, version: u32, id: ObjectId) -> Result<(), JayHeadManagerSessionV1Error> { + match ext { + $( + HeadExtension::$camel => { + if session.ext.$snake.is_some() { + return Err(JayHeadManagerSessionV1Error::AlreadyBound(name)); + } + let version = Version(version); + with_builtin_macros::with_eager_expansions! { + type T = super::jay_head_ext::#{concat_idents!(jay_head_ext_, $snake)}::#{concat_idents!(JayHeadManagerExt, $camel)}; + if version > T::VERSION { + return Err(JayHeadManagerSessionV1Error::UnsupportedVersion(name, version)); + } + let obj = Rc::new(T { + id: id.into(), + client: session.client.clone(), + tracker: Default::default(), + version, + common: session.common.clone(), + }); + } + track!(session.client, obj); + session.client.add_client_obj(&obj)?; + session.ext.$snake.set(Some(obj)); + } + )* + } + Ok(()) + } + + with_builtin_macros::with_eager_expansions! { + #[derive(Default)] + pub(super) struct MgrExts { + $( + pub(super) $snake: CloneCell>>, + )* + } + + pub(super) struct HeadExts { + $( + pub(super) $snake: Option>, + )* + } + } + + impl HeadExts { + pub(super) fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + $( + if let Some(ext) = &self.$snake { + ext.after_transaction_wrapper(shared, tran); + } + )* + } + } + + with_builtin_macros::with_eager_expansions! { + $( + declare_extension_type_macro!( + ($), + $snake, + $camel, + #{concat_idents!(impl_, $snake)}, + #{concat_idents!(impl_, $snake, _int)}, + #{concat_idents!(jay_head_manager_ext_, $snake)}, + #{concat_idents!(JayHeadManagerExt, $camel)}, + #{concat_idents!(JayHeadManagerExt, $camel, Id)}, + #{concat_idents!(JayHeadManagerExt, $camel, RequestHandler)}, + #{concat_idents!(jay_head_ext_, $snake)}, + #{concat_idents!(JayHeadExt, $camel)}, + #{concat_idents!(JayHeadExt, $camel, Id)}, + #{concat_idents!(JayHeadExt, $camel, RequestHandler)}, + #{concat_idents!(JayHeadExt, $camel, Error)}, + ); + )* + } + }; +} + +declare_extensions! { + core_info_v1: CoreInfoV1, +} diff --git a/src/ifs/head_management/jay_head_error_v1.rs b/src/ifs/head_management/jay_head_error_v1.rs new file mode 100644 index 00000000..4560a99a --- /dev/null +++ b/src/ifs/head_management/jay_head_error_v1.rs @@ -0,0 +1,77 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::head_management::HeadTransactionError, + leaks::Tracker, + object::{Object, Version}, + utils::errorfmt::ErrorFmt, + wire::{JayHeadErrorV1Id, jay_head_error_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct JayHeadErrorV1 { + pub(super) id: JayHeadErrorV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) error: Rc, +} + +impl JayHeadErrorV1 { + pub fn send(&self) { + let msg = ErrorFmt(&*self.error).to_string(); + self.send_message(&msg); + match &*self.error { + HeadTransactionError::HeadRemoved(_) | HeadTransactionError::MonitorChanged(_) => { + self.send_out_of_date(); + } + HeadTransactionError::AlreadyFailed => { + self.send_already_failed(); + } + HeadTransactionError::Backend(_) => {} + } + self.client.event(Done { self_id: self.id }); + } + + fn send_message(&self, message: &str) { + self.client.event(Message { + self_id: self.id, + msg: message, + }); + } + + fn send_out_of_date(&self) { + self.client.event(OutOfDate { self_id: self.id }); + } + + fn send_already_failed(&self) { + self.client.event(AlreadyFailed { self_id: self.id }); + } +} + +impl JayHeadErrorV1RequestHandler for JayHeadErrorV1 { + type Error = JayHeadErrorV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = JayHeadErrorV1; + version = self.version; +} + +impl Object for JayHeadErrorV1 {} + +dedicated_add_obj!(JayHeadErrorV1, JayHeadErrorV1Id, jay_head_errors); + +#[derive(Debug, Error)] +pub enum JayHeadErrorV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayHeadErrorV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_ext.rs b/src/ifs/head_management/jay_head_ext.rs new file mode 100644 index 00000000..d6d1abf0 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext.rs @@ -0,0 +1 @@ +pub(super) mod jay_head_ext_core_info_v1; diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs new file mode 100644 index 00000000..9bb30759 --- /dev/null +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_core_info_v1.rs @@ -0,0 +1,61 @@ +use { + crate::{ + ifs::head_management::HeadState, + wire::{jay_head_ext_core_info_v1::*, jay_head_manager_ext_core_info_v1::*}, + }, + std::rc::Rc, +}; + +impl_core_info_v1! { + version = 1, + after_announce = after_announce, + after_transaction = after_transaction, +} + +impl HeadName { + fn after_announce(&self, shared: &HeadState) { + self.send_name(shared); + self.send_wl_output(shared); + } + + fn after_transaction(&self, shared: &HeadState, tran: &HeadState) { + if shared.wl_output != tran.wl_output { + self.send_wl_output(shared); + } + } + + fn send_name(&self, state: &HeadState) { + self.client.event(Name { + self_id: self.id, + name: Some(&**state.name), + }); + } + + pub(in super::super) fn send_wl_output(&self, state: &HeadState) { + match state.wl_output { + None => { + self.client.event(NoWlOutput { self_id: self.id }); + } + Some(name) => { + self.client.event(WlOutput { + self_id: self.id, + global_name: name.raw(), + }); + } + } + } +} + +impl JayHeadManagerExtCoreInfoV1RequestHandler for MgrName { + type Error = ErrorName; + + mgr_common_req!(); +} + +impl JayHeadExtCoreInfoV1RequestHandler for HeadName { + type Error = ErrorName; + + head_common_req!(); +} + +error!(); diff --git a/src/ifs/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs new file mode 100644 index 00000000..022dbe55 --- /dev/null +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -0,0 +1,488 @@ +use { + super::HeadMgrCommon, + crate::{ + backend::transaction::{ConnectorTransaction, PreparedConnectorTransaction}, + client::{Client, ClientError}, + ifs::{ + head_management::{ + Head, HeadCommon, HeadCommonError, HeadMgrState, HeadName, HeadTransactionError, + head_management_macros::{HeadExtension, MgrExts, announce_head, bind_extension}, + jay_head_manager_v1::JayHeadManagerV1, + jay_head_transaction_result_v1::JayHeadTransactionResultV1, + jay_head_v1::JayHeadV1, + }, + wl_output::PersistentOutputState, + }, + leaks::Tracker, + object::{Object, Version}, + state::{ConnectorData, State}, + tree::{TearingMode, VrrMode}, + utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + wire::{ + JayHeadManagerSessionV1Id, JayHeadTransactionResultV1Id, + jay_head_manager_session_v1::{ + ApplyChanges, BeginTransaction, BindExtension, CommitTransaction, Destroy, + HeadComplete, HeadStart, JayHeadManagerSessionV1RequestHandler, + RollbackTransaction, Start, Stop, Stopped, TestTransaction, TransactionEnded, + TransactionStarted, + }, + }, + }, + linearize::LinearizeExt, + std::{cell::Cell, mem, ops::Deref, rc::Rc}, + thiserror::Error, +}; + +pub struct HeadManagerEvent { + mgr: Rc, + ty: HeadManagerEventType, +} + +pub(super) enum HeadManagerEventType { + Done, + TransactionStarted, + TransactionEnded, + TransactionResult(Rc), + Stopped, +} + +pub async fn handle_jay_head_manager_done(state: Rc) { + loop { + let ev = state.head_managers_async.pop().await; + let session = ev.mgr; + if session.mgr.destroyed.get() { + continue; + } + if session.mgr.done_scheduled.take() { + session.mgr.send_done(); + } + if session.common.state.get() == HeadMgrState::Stopped { + continue; + } + match ev.ty { + HeadManagerEventType::Done => {} + HeadManagerEventType::TransactionStarted => { + session.send_transaction_started(); + } + HeadManagerEventType::TransactionEnded => { + session.send_transaction_ended(); + } + HeadManagerEventType::Stopped => { + session.common.state.set(HeadMgrState::Stopped); + session.send_stopped(); + } + HeadManagerEventType::TransactionResult(res) => { + if !res.destroyed.get() { + res.send(); + } + } + } + } +} + +pub struct JayHeadManagerSessionV1 { + pub(super) id: JayHeadManagerSessionV1Id, + pub(super) mgr: Rc, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) common: Rc, + pub(super) serial: NumCell, + pub(super) heads: CopyHashMap>, + pub(super) ext: MgrExts, +} + +impl JayHeadManagerSessionV1 { + pub fn announce(self: &Rc, connector: &ConnectorData) { + if self.common.in_transaction.get() { + return; + } + if let Err(e) = self.try_announce(connector) { + self.client.error(e); + } + } + + fn try_announce(self: &Rc, connector: &ConnectorData) -> Result<(), ClientError> { + let shared = connector.head_managers.state.clone(); + let common = Rc::new(HeadCommon { + mgr: self.common.clone(), + name: connector.head_managers.name, + id: connector.id, + removed: Cell::new(false), + shared: shared.clone(), + snapshot_state: shared.deref().clone(), + transaction_state: shared.deref().clone(), + pending: Default::default(), + }); + let obj = Rc::new(JayHeadV1 { + id: self.client.new_id()?, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: common.clone(), + }); + track!(self.client, obj); + self.client.add_server_obj(&obj); + let head = announce_head(self, &obj, connector)?; + connector + .head_managers + .managers + .set((self.client.id, self.id), head.clone()); + self.heads.set(common.name, head); + Ok(()) + } + + fn schedule_event(self: &Rc, ty: HeadManagerEventType) { + self.client + .state + .head_managers_async + .push(HeadManagerEvent { + mgr: self.clone(), + ty, + }); + } + + pub(super) fn schedule_done(self: &Rc) { + if !self.mgr.done_scheduled.replace(true) { + self.serial.fetch_add(1); + self.schedule_event(HeadManagerEventType::Done); + } + } + + fn schedule_transaction_started(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::TransactionStarted); + } + + fn schedule_transaction_ended(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::TransactionEnded); + } + + fn schedule_stopped(self: &Rc) { + self.mgr.done_scheduled.set(true); + self.schedule_event(HeadManagerEventType::Stopped); + } + + fn send_transaction_started(&self) { + self.client.event(TransactionStarted { self_id: self.id }); + } + + fn send_transaction_ended(&self) { + self.client.event(TransactionEnded { self_id: self.id }); + } + + fn send_stopped(&self) { + self.client.event(Stopped { self_id: self.id }); + } + + pub(super) fn send_head_start(&self, head: &JayHeadV1, name: HeadName) { + self.client.event(HeadStart { + self_id: self.id, + head: head.id, + name: name.0, + }); + } + + pub(super) fn send_head_complete(&self) { + self.client.event(HeadComplete { self_id: self.id }); + } + + fn detach(&self, send_removed: bool) { + let id = (self.client.id, self.id); + for data in self.client.state.connectors.lock().values() { + if let Some(head) = data.head_managers.managers.remove(&id) { + if send_removed { + head.head.send_removed(); + } + } + } + self.client.state.head_managers.remove(&id); + self.heads.clear(); + } + + fn schedule_transaction_result( + self: &Rc, + id: JayHeadTransactionResultV1Id, + error: Option, + ) -> Result<(), JayHeadManagerSessionV1Error> { + if error.is_some() { + self.common.transaction_failed.set(true); + } + self.mgr.done_scheduled.set(true); + let res = Rc::new(JayHeadTransactionResultV1 { + id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + error: error.map(Rc::new), + destroyed: Cell::new(false), + }); + track!(self.client, res); + self.client.add_client_obj(&res)?; + self.schedule_event(HeadManagerEventType::TransactionResult(res)); + Ok(()) + } + + fn after_transaction_ended(self: &Rc) { + let mut to_remove = vec![]; + for head in self.heads.lock().values() { + if self.client.state.connectors.not_contains(&head.common.id) { + head.head.send_removed(); + to_remove.push(head.common.name); + continue; + } + let _ = head.common.pending.take(); + let tran = &*head.common.transaction_state.borrow(); + let shared = &*head.common.shared.borrow(); + head.ext.after_transaction(shared, tran); + } + for name in to_remove { + self.heads.remove(&name); + } + for connector in self.client.state.connectors.lock().values() { + if !self.heads.contains(&connector.head_managers.name) { + self.announce(connector); + } + } + self.schedule_transaction_ended(); + } + + fn prepare_transaction(&self) -> Result { + let mut tran = ConnectorTransaction::new(&self.client.state); + for head in self.heads.lock().values() { + let current = &*head.common.shared.borrow(); + let snapshot = &*head.common.snapshot_state.borrow(); + let desired = &*head.common.transaction_state.borrow(); + if desired == current || desired == snapshot { + continue; + } + let Some(connector) = self.client.state.connectors.get(&head.common.id) else { + return Err(HeadTransactionError::HeadRemoved(head.common.id)); + }; + let old = connector.state.get(); + #[expect(unused_mut)] + let mut new = old; + if old == new { + continue; + } + if current.monitor_info != desired.monitor_info { + return Err(HeadTransactionError::MonitorChanged(head.common.id)); + } + tran.add(&connector.connector, new)?; + } + Ok(tran.prepare()?) + } + + fn commit_transaction(&self) -> Result<(), HeadTransactionError> { + self.prepare_transaction()?.apply()?.commit(); + Ok(()) + } +} + +impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 { + type Error = JayHeadManagerSessionV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_stopped()?; + self.mgr.sessions.fetch_sub(1); + self.client.remove_obj(self)?; + Ok(()) + } + + fn bind_extension(&self, req: BindExtension<'_>, _slf: &Rc) -> Result<(), Self::Error> { + if self.common.state.get() != HeadMgrState::Init { + return Err(JayHeadManagerSessionV1Error::AlreadyStarted); + } + let Some(ext) = HeadExtension::from_linear(req.name as usize) else { + return Err(JayHeadManagerSessionV1Error::UnknownExtension(req.name)); + }; + bind_extension(self, ext, req.name, req.version, req.id)?; + Ok(()) + } + + fn stop(&self, _req: Stop, slf: &Rc) -> Result<(), Self::Error> { + if self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::InTransaction); + } + match self.common.state.get() { + HeadMgrState::Init | HeadMgrState::Started => {} + HeadMgrState::StopScheduled | HeadMgrState::Stopped => return Ok(()), + } + self.common.state.set(HeadMgrState::StopScheduled); + self.detach(true); + self.serial.fetch_add(1); + slf.schedule_stopped(); + Ok(()) + } + + fn start(&self, _req: Start, slf: &Rc) -> Result<(), Self::Error> { + if self.common.state.get() != HeadMgrState::Init { + return Err(JayHeadManagerSessionV1Error::AlreadyStarted); + } + self.common.state.set(HeadMgrState::Started); + self.client + .state + .head_managers + .set((self.client.id, self.id), slf.clone()); + for connector in self.client.state.connectors.lock().values() { + slf.announce(connector); + } + slf.schedule_done(); + Ok(()) + } + + fn begin_transaction(&self, _req: BeginTransaction, slf: &Rc) -> Result<(), Self::Error> { + if self.common.in_transaction.replace(true) { + return Err(JayHeadManagerSessionV1Error::AlreadyInTransaction); + } + for head in self.heads.lock().values() { + let snapshot = head.common.shared.borrow().clone(); + *head.common.transaction_state.borrow_mut() = snapshot.clone(); + *head.common.snapshot_state.borrow_mut() = snapshot; + head.common.pending.borrow_mut().clear(); + } + self.common.transaction_failed.set(false); + slf.schedule_transaction_started(); + Ok(()) + } + + fn rollback_transaction( + &self, + _req: RollbackTransaction, + slf: &Rc, + ) -> Result<(), Self::Error> { + if !self.common.in_transaction.replace(false) { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + slf.after_transaction_ended(); + Ok(()) + } + + fn apply_changes(&self, req: ApplyChanges, slf: &Rc) -> Result<(), Self::Error> { + if !self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + macro_rules! schedule_result { + ($res:expr) => { + slf.schedule_transaction_result(req.result, Some($res))?; + return Ok(()); + }; + } + if self.common.transaction_failed.get() { + schedule_result!(HeadTransactionError::AlreadyFailed); + } + bitflags! { + ToSend: u32; + CORE_INFO = 1 << 0, + } + for head in self.heads.lock().values() { + let pending = mem::take(&mut *head.common.pending.borrow_mut()); + #[expect(unused_variables)] + let snapshot = &*head.common.snapshot_state.borrow(); + let state = &mut *head.common.transaction_state.borrow_mut(); + #[expect(unused_mut)] + let mut to_send = ToSend::default(); + #[expect(clippy::never_loop)] + for op in pending { + match op {} + } + if to_send.contains(CORE_INFO) + && let Some(i) = &head.ext.core_info_v1 + { + i.send_wl_output(state); + } + } + slf.schedule_transaction_result(req.result, None)?; + Ok(()) + } + + fn test_transaction(&self, req: TestTransaction, slf: &Rc) -> Result<(), Self::Error> { + if !self.common.in_transaction.get() { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + let res = self.prepare_transaction().err(); + slf.schedule_transaction_result(req.result, res)?; + Ok(()) + } + + fn commit_transaction( + &self, + req: CommitTransaction, + slf: &Rc, + ) -> Result<(), Self::Error> { + if !self.common.in_transaction.replace(false) { + return Err(JayHeadManagerSessionV1Error::NotInTransaction); + } + slf.after_transaction_ended(); + if let Err(e) = self.commit_transaction() { + slf.schedule_transaction_result(req.result, Some(e))?; + return Ok(()); + } + for head in self.heads.lock().values() { + let desired = &*head.common.transaction_state.borrow(); + if let Some(output) = self.client.state.outputs.get(&head.common.id) + && let Some(node) = &output.node + { + let _ = node; + } else if let Some(mi) = &desired.monitor_info { + let pos = &self.client.state.persistent_output_states; + let pos = match pos.get(&mi.output_id) { + Some(ps) => ps, + _ => { + let ps = Rc::new(PersistentOutputState { + transform: Default::default(), + scale: Default::default(), + pos: Default::default(), + vrr_mode: Cell::new(&VrrMode::Never), + vrr_cursor_hz: Default::default(), + tearing_mode: Cell::new(&TearingMode::Never), + brightness: Default::default(), + }); + pos.set(mi.output_id.clone(), ps.clone()); + ps + } + }; + let _ = pos; + } + } + slf.schedule_transaction_result(req.result, None)?; + Ok(()) + } +} + +object_base! { + self = JayHeadManagerSessionV1; + version = self.version; +} + +impl Object for JayHeadManagerSessionV1 { + fn break_loops(&self) { + self.detach(false); + } +} + +simple_add_obj!(JayHeadManagerSessionV1); + +#[derive(Debug, Error)] +pub enum JayHeadManagerSessionV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] HeadCommonError), + #[error("Manager was already started")] + AlreadyStarted, + #[error("There is no extension with name {}", .0)] + UnknownExtension(u32), + #[error("The extension with name {} is already bound", .0)] + AlreadyBound(u32), + #[error("The extension with name {} does not support version {}", .0, .1 .0)] + UnsupportedVersion(u32, Version), + #[error("There already is an active transaction")] + AlreadyInTransaction, + #[error("There is no active transaction")] + NotInTransaction, + #[error("There is an active transaction")] + InTransaction, +} +efrom!(JayHeadManagerSessionV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_manager_v1.rs b/src/ifs/head_management/jay_head_manager_v1.rs new file mode 100644 index 00000000..3ce36535 --- /dev/null +++ b/src/ifs/head_management/jay_head_manager_v1.rs @@ -0,0 +1,158 @@ +use { + crate::{ + client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError}, + globals::{Global, GlobalName}, + ifs::head_management::{ + HeadMgrCommon, head_management_macros::send_available_extensions, + jay_head_manager_session_v1::JayHeadManagerSessionV1, + }, + leaks::Tracker, + object::{Object, Version}, + utils::numcell::NumCell, + wire::{ + JayHeadManagerV1Id, + jay_head_manager_v1::{ + CreateSession, Destroy, Done, Extension, ExtensionsDone, + JayHeadManagerV1RequestHandler, + }, + }, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct JayHeadManagerV1Global { + pub name: GlobalName, +} + +impl JayHeadManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: JayHeadManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), JayHeadManagerV1Error> { + let mgr = Rc::new(JayHeadManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + done_scheduled: Cell::new(false), + sessions: Default::default(), + destroyed: Default::default(), + }); + track!(client, mgr); + client.add_client_obj(&mgr)?; + send_available_extensions(&mgr); + mgr.send_extensions_done(); + Ok(()) + } +} + +global_base!( + JayHeadManagerV1Global, + JayHeadManagerV1, + JayHeadManagerV1Error +); + +simple_add_global!(JayHeadManagerV1Global); + +impl Global for JayHeadManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_HEAD_MANAGER + } +} + +pub(super) struct JayHeadManagerV1 { + pub(super) id: JayHeadManagerV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) done_scheduled: Cell, + pub(super) sessions: NumCell, + pub(super) destroyed: Cell, +} + +impl JayHeadManagerV1 { + pub(super) fn send_extension(&self, name: u32, interface: &str, version: Version) { + self.client.event(Extension { + self_id: self.id, + name, + interface, + version: version.0, + }); + } + + fn send_extensions_done(&self) { + self.client.event(ExtensionsDone { self_id: self.id }); + } + + pub(super) fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } +} + +impl JayHeadManagerV1RequestHandler for JayHeadManagerV1 { + type Error = JayHeadManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + if self.sessions.get() > 0 { + return Err(JayHeadManagerV1Error::HasSessions); + } + self.destroyed.set(true); + self.client.remove_obj(self)?; + Ok(()) + } + + fn create_session(&self, req: CreateSession, slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(JayHeadManagerSessionV1 { + id: req.session, + mgr: slf.clone(), + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + common: Rc::new(HeadMgrCommon { + state: Default::default(), + in_transaction: Cell::new(false), + transaction_failed: Cell::new(false), + }), + serial: Default::default(), + heads: Default::default(), + ext: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + self.sessions.fetch_add(1); + Ok(()) + } +} + +object_base! { + self = JayHeadManagerV1; + version = self.version; +} + +impl Object for JayHeadManagerV1 {} + +simple_add_obj!(JayHeadManagerV1); + +#[derive(Debug, Error)] +pub enum JayHeadManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Manager still has sessions")] + HasSessions, +} +efrom!(JayHeadManagerV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_transaction_result_v1.rs b/src/ifs/head_management/jay_head_transaction_result_v1.rs new file mode 100644 index 00000000..53732db2 --- /dev/null +++ b/src/ifs/head_management/jay_head_transaction_result_v1.rs @@ -0,0 +1,82 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::head_management::{HeadTransactionError, jay_head_error_v1::JayHeadErrorV1}, + leaks::Tracker, + object::{Object, Version}, + wire::{JayHeadTransactionResultV1Id, jay_head_transaction_result_v1::*}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub(super) struct JayHeadTransactionResultV1 { + pub(super) id: JayHeadTransactionResultV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) error: Option>, + pub(super) destroyed: Cell, +} + +impl JayHeadTransactionResultV1 { + pub(super) fn send(&self) { + match self.error { + None => self.send_success(), + _ => self.send_failed(), + } + } + + fn send_success(&self) { + self.client.event(Success { self_id: self.id }); + } + + fn send_failed(&self) { + self.client.event(Failed { self_id: self.id }); + } +} + +impl JayHeadTransactionResultV1RequestHandler for JayHeadTransactionResultV1 { + type Error = JayHeadTransactionResultV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + self.destroyed.set(true); + Ok(()) + } + + fn get_error(&self, req: GetError, _slf: &Rc) -> Result<(), Self::Error> { + let Some(err) = &self.error else { + return Err(JayHeadTransactionResultV1Error::NoError); + }; + let err = Rc::new(JayHeadErrorV1 { + id: req.error, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + error: err.clone(), + }); + track!(self.client, err); + self.client.add_client_obj(&err)?; + err.send(); + Ok(()) + } +} + +object_base! { + self = JayHeadTransactionResultV1; + version = self.version; +} + +impl Object for JayHeadTransactionResultV1 {} + +simple_add_obj!(JayHeadTransactionResultV1); + +#[derive(Debug, Error)] +pub enum JayHeadTransactionResultV1Error { + #[error(transparent)] + ClientError(Box), + #[error("The transaction result was success")] + NoError, +} +efrom!(JayHeadTransactionResultV1Error, ClientError); diff --git a/src/ifs/head_management/jay_head_v1.rs b/src/ifs/head_management/jay_head_v1.rs new file mode 100644 index 00000000..06721b07 --- /dev/null +++ b/src/ifs/head_management/jay_head_v1.rs @@ -0,0 +1,55 @@ +use { + super::HeadCommon, + crate::{ + client::{Client, ClientError}, + ifs::head_management::HeadCommonError, + leaks::Tracker, + object::{Object, Version}, + wire::{JayHeadV1Id, jay_head_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub(super) struct JayHeadV1 { + pub(super) id: JayHeadV1Id, + pub(super) client: Rc, + pub(super) tracker: Tracker, + pub(super) version: Version, + pub(super) common: Rc, +} + +impl JayHeadV1 { + pub(super) fn send_removed(&self) { + self.common.removed.set(true); + self.client.event(Removed { self_id: self.id }); + } +} + +impl JayHeadV1RequestHandler for JayHeadV1 { + type Error = JayHeadV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.common.assert_removed()?; + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = JayHeadV1; + version = self.version; +} + +impl Object for JayHeadV1 {} + +simple_add_obj!(JayHeadV1); + +#[derive(Debug, Error)] +pub enum JayHeadV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + Common(#[from] HeadCommonError), +} +efrom!(JayHeadV1Error, ClientError); diff --git a/src/state.rs b/src/state.rs index a1277b09..51f4951d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -40,6 +40,10 @@ use { ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, ext_idle_notification_v1::ExtIdleNotificationV1, ext_session_lock_v1::ExtSessionLockV1, + head_management::{ + HeadManagers, HeadNames, + jay_head_manager_session_v1::{HeadManagerEvent, JayHeadManagerSessionV1}, + }, ipc::{ DataOfferIds, DataSourceIds, data_control::DataControlDeviceIds, x_data_device::XIpcDeviceIds, @@ -107,8 +111,9 @@ use { }, wheel::Wheel, wire::{ - ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayRenderCtxId, JaySeatEventsId, - JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, ZwpLinuxDmabufFeedbackV1Id, + ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id, + JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId, ZwlrForeignToplevelManagerV1Id, + ZwpLinuxDmabufFeedbackV1Id, }, xwayland::{self, XWaylandEvent}, }, @@ -257,6 +262,10 @@ pub struct State { pub node_at_tree: RefCell>, pub position_hint_requests: AsyncQueue, pub backend_connector_state_serials: BackendConnectorStateSerials, + pub head_names: HeadNames, + pub head_managers: + CopyHashMap<(ClientId, JayHeadManagerSessionV1Id), Rc>, + pub head_managers_async: AsyncQueue, } // impl Drop for State { @@ -368,10 +377,11 @@ pub struct DeviceHandlerData { } pub struct ConnectorData { + pub id: ConnectorId, pub connector: Rc, pub handler: Cell>>, pub connected: Cell, - pub name: String, + pub name: Rc, pub drm_dev: Option>, pub async_event: Rc, pub damaged: Cell, @@ -379,11 +389,12 @@ pub struct ConnectorData { pub needs_vblank_emulation: Cell, pub damage_intersect: Cell, pub state: Cell, + pub head_managers: HeadManagers, } pub struct OutputData { pub connector: Rc, - pub monitor_info: MonitorInfo, + pub monitor_info: Rc, pub node: Option>, pub lease_connectors: Rc>, } @@ -1017,6 +1028,8 @@ impl State { self.tl_matcher_manager.clear(); self.node_at_tree.borrow_mut().clear(); self.position_hint_requests.clear(); + self.head_managers.clear(); + self.head_managers_async.clear(); } pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index ca5eea28..555d2900 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -7,13 +7,16 @@ use { format::XRGB8888, globals::GlobalName, ifs::{ + head_management::{HeadManagers, HeadState}, jay_tray_v1::JayTrayV1Global, wl_output::{PersistentOutputState, WlOutputGlobal}, }, output_schedule::OutputSchedule, state::{ConnectorData, OutputData, State}, tree::{OutputNode, WsMoveConfig, move_ws_to_output}, - utils::{asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt}, + utils::{ + asyncevent::AsyncEvent, clonecell::CloneCell, hash_map_ext::HashMapExt, rc_eq::RcEq, + }, }, std::{cell::Cell, collections::VecDeque, rc::Rc}, }; @@ -39,11 +42,18 @@ pub fn handle(state: &Rc, connector: &Rc) { transfer_function: Default::default(), }; let id = connector.id(); + let name = Rc::new(connector.kernel_id().to_string()); + let head_state = HeadState { + name: RcEq(name.clone()), + wl_output: None, + monitor_info: None, + }; let data = Rc::new(ConnectorData { + id, connector: connector.clone(), handler: Default::default(), connected: Cell::new(false), - name: connector.kernel_id().to_string(), + name, drm_dev: drm_dev.clone(), async_event: Rc::new(AsyncEvent::default()), damaged: Cell::new(false), @@ -51,6 +61,7 @@ pub fn handle(state: &Rc, connector: &Rc) { needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), state: Cell::new(backend_state), + head_managers: HeadManagers::new(state.head_names.next(), head_state), }); if let Some(dev) = drm_dev { dev.connectors.set(id, data.clone()); @@ -62,6 +73,9 @@ pub fn handle(state: &Rc, connector: &Rc) { }; let future = state.eng.spawn("connector handler", oh.handle()); data.handler.set(Some(future)); + for mgr in state.head_managers.lock().values() { + mgr.announce(&data); + } if state.connectors.set(id, data).is_some() { panic!("Connector id has been reused"); } @@ -100,6 +114,7 @@ impl ConnectorHandler { } self.data.handler.set(None); self.state.connectors.remove(&self.id); + self.data.head_managers.handle_removed(); } async fn handle_connected(&self, info: MonitorInfo) { @@ -113,6 +128,7 @@ impl ConnectorHandler { self.handle_desktop_connected(info, name).await; } self.data.connected.set(false); + self.data.head_managers.handle_output_disconnected(); log::info!("Connector {} disconnected", self.data.connector.kernel_id()); } @@ -218,11 +234,11 @@ impl ConnectorHandler { .add_output_scale(on.global.persistent.scale.get()); let output_data = Rc::new(OutputData { connector: self.data.clone(), - monitor_info: info, + monitor_info: Rc::new(info), node: Some(on.clone()), lease_connectors: Default::default(), }); - self.state.outputs.set(self.id, output_data); + self.state.outputs.set(self.id, output_data.clone()); on.schedule_update_render_data(); self.state.root.outputs.set(self.id, on.clone()); self.state.output_extents_changed(); @@ -274,6 +290,9 @@ impl ConnectorHandler { self.state.tree_changed(); on.update_presentation_type(); self.state.workspace_managers.announce_output(&on); + self.data + .head_managers + .handle_output_connected(&output_data); 'outer: loop { while let Some(event) = self.data.connector.event() { match event { @@ -362,7 +381,7 @@ impl ConnectorHandler { async fn handle_non_desktop_connected(&self, monitor_info: MonitorInfo) { let output_data = Rc::new(OutputData { connector: self.data.clone(), - monitor_info, + monitor_info: Rc::new(monitor_info), node: None, lease_connectors: Default::default(), }); @@ -387,6 +406,9 @@ impl ConnectorHandler { if let Some(config) = self.state.config.get() { config.connector_connected(self.id); } + self.data + .head_managers + .handle_output_connected(&output_data); 'outer: loop { while let Some(event) = self.data.connector.event() { match event { diff --git a/src/utils/copyhashmap.rs b/src/utils/copyhashmap.rs index 48cc2f1a..d534e1db 100644 --- a/src/utils/copyhashmap.rs +++ b/src/utils/copyhashmap.rs @@ -66,6 +66,14 @@ impl CopyHashMap { unsafe { self.map.get().deref().contains_key(k) } } + pub fn not_contains(&self, k: &Q) -> bool + where + Q: Hash + Eq + ?Sized, + K: Borrow, + { + !self.contains(k) + } + pub fn lock(&self) -> Locked<'_, K, V> { Locked { source: self, diff --git a/src/utils/rc_eq.rs b/src/utils/rc_eq.rs index 725b3a3d..378fcc24 100644 --- a/src/utils/rc_eq.rs +++ b/src/utils/rc_eq.rs @@ -1,5 +1,29 @@ -use std::rc::Rc; +use std::{ops::Deref, rc::Rc}; pub fn rc_eq(a: &Rc, b: &Rc) -> bool { Rc::as_ptr(a) as *const u8 == Rc::as_ptr(b) as *const u8 } + +pub struct RcEq(pub Rc); + +impl Clone for RcEq { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for RcEq { + fn eq(&self, other: &Self) -> bool { + rc_eq(&self.0, &other.0) + } +} + +impl Eq for RcEq {} + +impl Deref for RcEq { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/wire/jay_head_error_v1.txt b/wire/jay_head_error_v1.txt new file mode 100644 index 00000000..48c2e1b5 --- /dev/null +++ b/wire/jay_head_error_v1.txt @@ -0,0 +1,23 @@ +request destroy (destructor) { + +} + +event message { + msg: str, +} + +event already_failed { + +} + +event out_of_date { + +} + +event extension { + name: u32, +} + +event done { + +} diff --git a/wire/jay_head_ext_core_info_v1.txt b/wire/jay_head_ext_core_info_v1.txt new file mode 100644 index 00000000..26098bfc --- /dev/null +++ b/wire/jay_head_ext_core_info_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event wl_output { + global_name: u32, +} + +event no_wl_output { + +} + +event name { + name: optstr, +} diff --git a/wire/jay_head_manager_ext_core_info_v1.txt b/wire/jay_head_manager_ext_core_info_v1.txt new file mode 100644 index 00000000..5a733921 --- /dev/null +++ b/wire/jay_head_manager_ext_core_info_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event head { + head: id(jay_head_ext_core_info_v1) (new), +} diff --git a/wire/jay_head_manager_session_v1.txt b/wire/jay_head_manager_session_v1.txt new file mode 100644 index 00000000..c0c07b39 --- /dev/null +++ b/wire/jay_head_manager_session_v1.txt @@ -0,0 +1,59 @@ +request destroy (destructor) { + +} + +request bind_extension { + name: u32, + interface: str, + version: u32, + id: id(object) (new), +} + +request stop { + +} + +event stopped { + +} + +request start { + +} + +event head_start { + head: id(jay_head_v1) (new), + name: u64, +} + +event head_complete { + +} + +request begin_transaction { + +} + +request rollback_transaction { + +} + +request apply_changes { + result: id(jay_head_transaction_result_v1) (new), +} + +request test_transaction { + result: id(jay_head_transaction_result_v1) (new), +} + +request commit_transaction { + result: id(jay_head_transaction_result_v1) (new), +} + +event transaction_started { + +} + +event transaction_ended { + +} diff --git a/wire/jay_head_manager_v1.txt b/wire/jay_head_manager_v1.txt new file mode 100644 index 00000000..1c678dec --- /dev/null +++ b/wire/jay_head_manager_v1.txt @@ -0,0 +1,21 @@ +request destroy (destructor) { + +} + +event extension { + name: u32, + interface: str, + version: u32, +} + +event extensions_done { + +} + +request create_session { + session: id(jay_head_manager_session_v1) (new), +} + +event done { + +} diff --git a/wire/jay_head_transaction_result_v1.txt b/wire/jay_head_transaction_result_v1.txt new file mode 100644 index 00000000..9fd49a488 --- /dev/null +++ b/wire/jay_head_transaction_result_v1.txt @@ -0,0 +1,15 @@ +request destroy (destructor) { + +} + +event success { + +} + +event failed { + +} + +request get_error { + error: id(jay_head_error_v1) (new), +} diff --git a/wire/jay_head_v1.txt b/wire/jay_head_v1.txt new file mode 100644 index 00000000..facc42f6 --- /dev/null +++ b/wire/jay_head_v1.txt @@ -0,0 +1,7 @@ +request destroy (destructor) { + +} + +event removed { + +}