From c66f5798b7f4cacd44a5000ca5b73ceb4ef3ffaf Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 26 Feb 2025 14:32:57 +0100 Subject: [PATCH] color-management-v1: initial implementation --- docs/features.md | 2 + release-notes.md | 1 + src/client/objects.rs | 9 +- src/globals.rs | 2 + src/ifs.rs | 1 + src/ifs/color_management.rs | 56 +++++ .../wp_color_management_output_v1.rs | 68 ++++++ ...wp_color_management_surface_feedback_v1.rs | 76 ++++++ .../wp_color_management_surface_v1.rs | 73 ++++++ .../color_management/wp_color_manager_v1.rs | 223 ++++++++++++++++++ .../wp_image_description_creator_icc_v1.rs | 43 ++++ .../wp_image_description_creator_params_v1.rs | 134 +++++++++++ .../wp_image_description_info_v1.rs | 145 ++++++++++++ .../wp_image_description_v1.rs | 79 +++++++ wire/wp_color_management_output_v1.txt | 9 + ...p_color_management_surface_feedback_v1.txt | 14 ++ wire/wp_color_management_surface_v1.txt | 10 + wire/wp_color_manager_v1.txt | 48 ++++ wire/wp_image_description_creator_icc_v1.txt | 9 + ...wp_image_description_creator_params_v1.txt | 56 +++++ wire/wp_image_description_info_v1.txt | 60 +++++ wire/wp_image_description_v1.txt | 15 ++ 22 files changed, 1130 insertions(+), 3 deletions(-) create mode 100644 src/ifs/color_management.rs create mode 100644 src/ifs/color_management/wp_color_management_output_v1.rs create mode 100644 src/ifs/color_management/wp_color_management_surface_feedback_v1.rs create mode 100644 src/ifs/color_management/wp_color_management_surface_v1.rs create mode 100644 src/ifs/color_management/wp_color_manager_v1.rs create mode 100644 src/ifs/color_management/wp_image_description_creator_icc_v1.rs create mode 100644 src/ifs/color_management/wp_image_description_creator_params_v1.rs create mode 100644 src/ifs/color_management/wp_image_description_info_v1.rs create mode 100644 src/ifs/color_management/wp_image_description_v1.rs create mode 100644 wire/wp_color_management_output_v1.txt create mode 100644 wire/wp_color_management_surface_feedback_v1.txt create mode 100644 wire/wp_color_management_surface_v1.txt create mode 100644 wire/wp_color_manager_v1.txt create mode 100644 wire/wp_image_description_creator_icc_v1.txt create mode 100644 wire/wp_image_description_creator_params_v1.txt create mode 100644 wire/wp_image_description_info_v1.txt create mode 100644 wire/wp_image_description_v1.txt diff --git a/docs/features.md b/docs/features.md index 76df0d1f..b1795fc0 100644 --- a/docs/features.md +++ b/docs/features.md @@ -160,6 +160,7 @@ Jay supports the following wayland protocols: | wl_shm | 2 | | | wl_subcompositor | 1 | | | wp_alpha_modifier_v1 | 1 | | +| wp_color_manager_v1 | 1[^color_mng] | | | wp_commit_timing_manager_v1 | 1 | | | wp_content_type_manager_v1 | 1 | | | wp_cursor_shape_manager_v1 | 1 | | @@ -195,3 +196,4 @@ Jay supports the following wayland protocols: [^lsaccess]: Sandboxes can restrict access to this protocol. [^ts_rejected]: Seat creation is always rejected. [^composited]: Cursors are always composited. +[^color_mng]: Only SRGB is supported. diff --git a/release-notes.md b/release-notes.md index 126f6398..490438d8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ opacity on top of a red window will produce a perfectly yellow image instead of a muddy yellow. The blend buffer is only used for those areas of the screen where blending is observable. This should have no impact on performance in the common case. +- Implement color-management-v1. # 1.9.1 (2025-02-13) diff --git a/src/client/objects.rs b/src/client/objects.rs index 84a7af46..88c440a5 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -2,6 +2,7 @@ use { crate::{ client::{Client, ClientError}, ifs::{ + color_management::wp_image_description_v1::WpImageDescriptionV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1, ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, @@ -43,9 +44,9 @@ use { ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, - WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, - XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, - ZwpTabletToolV2Id, + WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, + XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, + ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, }, }, std::{cell::RefCell, rc::Rc}, @@ -85,6 +86,7 @@ pub struct Objects { pub ext_data_sources: CopyHashMap>, pub ext_workspace_groups: CopyHashMap>, + pub wp_image_description: CopyHashMap>, ids: RefCell>, } @@ -123,6 +125,7 @@ impl Objects { ext_copy_sessions: Default::default(), ext_data_sources: Default::default(), ext_workspace_groups: Default::default(), + wp_image_description: Default::default(), ids: RefCell::new(vec![]), } } diff --git a/src/globals.rs b/src/globals.rs index f5310583..a50d0e62 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -3,6 +3,7 @@ use { backend::Backend, client::{Client, ClientCaps}, ifs::{ + color_management::wp_color_manager_v1::WpColorManagerV1Global, ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_idle_notifier_v1::ExtIdleNotifierV1Global, @@ -215,6 +216,7 @@ impl Globals { add_singleton!(ExtDataControlManagerV1Global); add_singleton!(WlFixesGlobal); add_singleton!(ExtWorkspaceManagerV1Global); + add_singleton!(WpColorManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 5f55bd41..4108e80c 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,3 +1,4 @@ +pub mod color_management; pub mod ext_foreign_toplevel_handle_v1; pub mod ext_foreign_toplevel_image_capture_source_manager_v1; pub mod ext_foreign_toplevel_list_v1; diff --git a/src/ifs/color_management.rs b/src/ifs/color_management.rs new file mode 100644 index 00000000..21a66ec8 --- /dev/null +++ b/src/ifs/color_management.rs @@ -0,0 +1,56 @@ +pub mod wp_color_management_output_v1; +pub mod wp_color_management_surface_feedback_v1; +pub mod wp_color_management_surface_v1; +pub mod wp_color_manager_v1; +pub mod wp_image_description_creator_icc_v1; +pub mod wp_image_description_creator_params_v1; +pub mod wp_image_description_info_v1; +pub mod wp_image_description_v1; + +#[expect(dead_code)] +mod consts { + pub(super) const RENDER_INTENT_PERCEPTUAL: u32 = 0; + pub(super) const RENDER_INTENT_RELATIVE: u32 = 1; + pub(super) const RENDER_INTENT_SATURATION: u32 = 2; + pub(super) const RENDER_INTENT_ABSOLUTE: u32 = 3; + pub(super) const RENDER_INTENT_RELATIVE_BPC: u32 = 4; + + pub(super) const FEATURE_ICC_V2_V4: u32 = 0; + pub(super) const FEATURE_PARAMETRIC: u32 = 1; + pub(super) const FEATURE_SET_PRIMARIES: u32 = 2; + pub(super) const FEATURE_SET_TF_POWER: u32 = 3; + pub(super) const FEATURE_SET_LUMINANCES: u32 = 4; + pub(super) const FEATURE_SET_MASTERING_DISPLAY_PRIMARIES: u32 = 5; + pub(super) const FEATURE_EXTENDED_TARGET_VOLUME: u32 = 6; + pub(super) const FEATURE_WINDOWS_SCRGB: u32 = 7; + + pub(super) const PRIMARIES_SRGB: u32 = 1; + pub(super) const PRIMARIES_PAL_M: u32 = 2; + pub(super) const PRIMARIES_PAL: u32 = 3; + pub(super) const PRIMARIES_NTSC: u32 = 4; + pub(super) const PRIMARIES_GENERIC_FILM: u32 = 5; + pub(super) const PRIMARIES_BT2020: u32 = 6; + pub(super) const PRIMARIES_CIE1931_XYZ: u32 = 7; + pub(super) const PRIMARIES_DCI_P3: u32 = 8; + pub(super) const PRIMARIES_DISPLAY_P3: u32 = 9; + pub(super) const PRIMARIES_ADOBE_RGB: u32 = 10; + + pub(super) const TRANSFER_FUNCTION_BT1886: u32 = 1; + pub(super) const TRANSFER_FUNCTION_GAMMA22: u32 = 2; + pub(super) const TRANSFER_FUNCTION_GAMMA28: u32 = 3; + pub(super) const TRANSFER_FUNCTION_ST240: u32 = 4; + pub(super) const TRANSFER_FUNCTION_EXT_LINEAR: u32 = 5; + pub(super) const TRANSFER_FUNCTION_LOG_100: u32 = 6; + pub(super) const TRANSFER_FUNCTION_LOG_316: u32 = 7; + pub(super) const TRANSFER_FUNCTION_XVYCC: u32 = 8; + pub(super) const TRANSFER_FUNCTION_SRGB: u32 = 9; + pub(super) const TRANSFER_FUNCTION_EXT_SRGB: u32 = 10; + pub(super) const TRANSFER_FUNCTION_ST2084_PQ: u32 = 11; + pub(super) const TRANSFER_FUNCTION_ST428: u32 = 12; + pub(super) const TRANSFER_FUNCTION_HLG: u32 = 13; + + pub(super) const CAUSE_LOW_VERSION: u32 = 0; + pub(super) const CAUSE_UNSUPPORTED: u32 = 1; + pub(super) const CAUSE_OPERATING_SYSTEM: u32 = 2; + pub(super) const CAUSE_NO_OUTPUT: u32 = 3; +} diff --git a/src/ifs/color_management/wp_color_management_output_v1.rs b/src/ifs/color_management/wp_color_management_output_v1.rs new file mode 100644 index 00000000..e23d6ebb --- /dev/null +++ b/src/ifs/color_management/wp_color_management_output_v1.rs @@ -0,0 +1,68 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::color_management::wp_image_description_v1::WpImageDescriptionV1, + leaks::Tracker, + object::{Object, Version}, + wire::{WpColorManagementOutputV1Id, wp_color_management_output_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpColorManagementOutputV1 { + pub id: WpColorManagementOutputV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpColorManagementOutputV1 { + #[expect(dead_code)] + pub fn send_image_description_changed(&self) { + self.client + .event(ImageDescriptionChanged { self_id: self.id }); + } +} + +impl WpColorManagementOutputV1RequestHandler for WpColorManagementOutputV1 { + type Error = WpColorManagementOutputV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_image_description( + &self, + req: GetImageDescription, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let obj = Rc::new(WpImageDescriptionV1 { + id: req.image_description, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + obj.send_ready(0); + Ok(()) + } +} + +object_base! { + self = WpColorManagementOutputV1; + version = self.version; +} + +impl Object for WpColorManagementOutputV1 {} + +simple_add_obj!(WpColorManagementOutputV1); + +#[derive(Debug, Error)] +pub enum WpColorManagementOutputV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpColorManagementOutputV1Error, ClientError); diff --git a/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs b/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs new file mode 100644 index 00000000..6fb30be6 --- /dev/null +++ b/src/ifs/color_management/wp_color_management_surface_feedback_v1.rs @@ -0,0 +1,76 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::color_management::wp_image_description_v1::WpImageDescriptionV1, + leaks::Tracker, + object::{Object, Version}, + wire::{ + WpColorManagementSurfaceFeedbackV1Id, WpImageDescriptionV1Id, + wp_color_management_surface_feedback_v1::*, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpColorManagementSurfaceFeedbackV1 { + pub id: WpColorManagementSurfaceFeedbackV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpColorManagementSurfaceFeedbackV1 { + fn get_description( + &self, + id: WpImageDescriptionV1Id, + ) -> Result<(), WpColorManagementSurfaceFeedbackV1Error> { + let obj = Rc::new(WpImageDescriptionV1 { + id, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + obj.send_ready(0); + Ok(()) + } +} + +impl WpColorManagementSurfaceFeedbackV1RequestHandler for WpColorManagementSurfaceFeedbackV1 { + type Error = WpColorManagementSurfaceFeedbackV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_preferred(&self, req: GetPreferred, _slf: &Rc) -> Result<(), Self::Error> { + self.get_description(req.image_description) + } + + fn get_preferred_parametric( + &self, + req: GetPreferredParametric, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.get_description(req.image_description) + } +} + +object_base! { + self = WpColorManagementSurfaceFeedbackV1; + version = self.version; +} + +impl Object for WpColorManagementSurfaceFeedbackV1 {} + +simple_add_obj!(WpColorManagementSurfaceFeedbackV1); + +#[derive(Debug, Error)] +pub enum WpColorManagementSurfaceFeedbackV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpColorManagementSurfaceFeedbackV1Error, ClientError); diff --git a/src/ifs/color_management/wp_color_management_surface_v1.rs b/src/ifs/color_management/wp_color_management_surface_v1.rs new file mode 100644 index 00000000..6d8cf7f3 --- /dev/null +++ b/src/ifs/color_management/wp_color_management_surface_v1.rs @@ -0,0 +1,73 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::color_management::consts::RENDER_INTENT_PERCEPTUAL, + leaks::Tracker, + object::{Object, Version}, + wire::{ + WpColorManagementSurfaceV1Id, + wp_color_management_surface_v1::{ + Destroy, SetImageDescription, UnsetImageDescription, + WpColorManagementSurfaceV1RequestHandler, + }, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpColorManagementSurfaceV1 { + pub id: WpColorManagementSurfaceV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 { + type Error = WpColorManagementSurfaceV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_image_description( + &self, + req: SetImageDescription, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let _ = self.client.lookup(req.image_description)?; + if req.render_intent != RENDER_INTENT_PERCEPTUAL { + return Err(WpColorManagementSurfaceV1Error::UnsupportedRenderIntent( + req.render_intent, + )); + } + Ok(()) + } + + fn unset_image_description( + &self, + _req: UnsetImageDescription, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Ok(()) + } +} + +object_base! { + self = WpColorManagementSurfaceV1; + version = self.version; +} + +impl Object for WpColorManagementSurfaceV1 {} + +simple_add_obj!(WpColorManagementSurfaceV1); + +#[derive(Debug, Error)] +pub enum WpColorManagementSurfaceV1Error { + #[error(transparent)] + ClientError(Box), + #[error("{} is not a supported render intent", .0)] + UnsupportedRenderIntent(u32), +} +efrom!(WpColorManagementSurfaceV1Error, ClientError); diff --git a/src/ifs/color_management/wp_color_manager_v1.rs b/src/ifs/color_management/wp_color_manager_v1.rs new file mode 100644 index 00000000..2bfbb2ef --- /dev/null +++ b/src/ifs/color_management/wp_color_manager_v1.rs @@ -0,0 +1,223 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::color_management::{ + consts::{ + FEATURE_PARAMETRIC, PRIMARIES_SRGB, RENDER_INTENT_PERCEPTUAL, + TRANSFER_FUNCTION_SRGB, + }, + wp_color_management_output_v1::WpColorManagementOutputV1, + wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1, + wp_color_management_surface_v1::WpColorManagementSurfaceV1, + wp_image_description_creator_params_v1::WpImageDescriptionCreatorParamsV1, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ + WpColorManagerV1Id, + wp_color_manager_v1::{SupportedIntent, *}, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpColorManagerV1Global { + pub name: GlobalName, +} + +impl WpColorManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: WpColorManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), WpColorManagerV1Error> { + let obj = Rc::new(WpColorManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + obj.send_capabilities(); + Ok(()) + } +} + +pub struct WpColorManagerV1 { + pub id: WpColorManagerV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpColorManagerV1 { + fn send_capabilities(&self) { + self.send_supported_intent(RENDER_INTENT_PERCEPTUAL); + self.send_supported_feature(FEATURE_PARAMETRIC); + self.send_supported_tf_named(TRANSFER_FUNCTION_SRGB); + self.send_supported_primaries_named(PRIMARIES_SRGB); + self.send_done(); + } + + fn send_supported_intent(&self, render_intent: u32) { + self.client.event(SupportedIntent { + self_id: self.id, + render_intent, + }); + } + + fn send_supported_feature(&self, feature: u32) { + self.client.event(SupportedFeature { + self_id: self.id, + feature, + }); + } + + fn send_supported_tf_named(&self, tf: u32) { + self.client.event(SupportedTfNamed { + self_id: self.id, + tf, + }); + } + + fn send_supported_primaries_named(&self, primaries: u32) { + self.client.event(SupportedPrimariesNamed { + self_id: self.id, + primaries, + }); + } + + fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } +} + +impl WpColorManagerV1RequestHandler for WpColorManagerV1 { + type Error = WpColorManagerV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_output(&self, req: GetOutput, _slf: &Rc) -> Result<(), Self::Error> { + let _ = self.client.lookup(req.output)?; + let obj = Rc::new(WpColorManagementOutputV1 { + id: req.id, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn get_surface(&self, req: GetSurface, _slf: &Rc) -> Result<(), Self::Error> { + let _ = self.client.lookup(req.surface)?; + let obj = Rc::new(WpColorManagementSurfaceV1 { + id: req.id, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn get_surface_feedback( + &self, + req: GetSurfaceFeedback, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let _ = self.client.lookup(req.surface)?; + let obj = Rc::new(WpColorManagementSurfaceFeedbackV1 { + id: req.id, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn create_icc_creator( + &self, + _req: CreateIccCreator, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Err(WpColorManagerV1Error::CreateIccCreatorNotSupported) + } + + fn create_parametric_creator( + &self, + req: CreateParametricCreator, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let obj = Rc::new(WpImageDescriptionCreatorParamsV1 { + id: req.obj, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } + + fn create_windows_scrgb( + &self, + _req: CreateWindowsScrgb, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Err(WpColorManagerV1Error::CreateWindowsScrgbNotSupported) + } +} + +global_base!( + WpColorManagerV1Global, + WpColorManagerV1, + WpColorManagerV1Error +); + +impl Global for WpColorManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } +} + +simple_add_global!(WpColorManagerV1Global); + +object_base! { + self = WpColorManagerV1; + version = self.version; +} + +impl Object for WpColorManagerV1 {} + +simple_add_obj!(WpColorManagerV1); + +#[derive(Debug, Error)] +pub enum WpColorManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("create_icc_creator is not supported")] + CreateIccCreatorNotSupported, + #[error("create_windows_scrgb is not supported")] + CreateWindowsScrgbNotSupported, +} +efrom!(WpColorManagerV1Error, ClientError); diff --git a/src/ifs/color_management/wp_image_description_creator_icc_v1.rs b/src/ifs/color_management/wp_image_description_creator_icc_v1.rs new file mode 100644 index 00000000..69225108 --- /dev/null +++ b/src/ifs/color_management/wp_image_description_creator_icc_v1.rs @@ -0,0 +1,43 @@ +use { + crate::{ + client::Client, + leaks::Tracker, + object::{Object, Version}, + wire::{ + WpImageDescriptionCreatorIccV1Id, + wp_image_description_creator_icc_v1::{ + Create, SetIccFile, WpImageDescriptionCreatorIccV1RequestHandler, + }, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +#[expect(dead_code)] +pub struct WpImageDescriptionCreatorIccV1 { + pub id: WpImageDescriptionCreatorIccV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpImageDescriptionCreatorIccV1RequestHandler for WpImageDescriptionCreatorIccV1 { + type Error = Infallible; + + fn create(&self, _req: Create, _slf: &Rc) -> Result<(), Self::Error> { + unreachable!() + } + + fn set_icc_file(&self, _req: SetIccFile, _slf: &Rc) -> Result<(), Self::Error> { + unreachable!() + } +} + +object_base! { + self = WpImageDescriptionCreatorIccV1; + version = self.version; +} + +impl Object for WpImageDescriptionCreatorIccV1 {} + +simple_add_obj!(WpImageDescriptionCreatorIccV1); diff --git a/src/ifs/color_management/wp_image_description_creator_params_v1.rs b/src/ifs/color_management/wp_image_description_creator_params_v1.rs new file mode 100644 index 00000000..c17684d6 --- /dev/null +++ b/src/ifs/color_management/wp_image_description_creator_params_v1.rs @@ -0,0 +1,134 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::color_management::{ + consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB}, + wp_image_description_v1::WpImageDescriptionV1, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ + WpImageDescriptionCreatorParamsV1Id, + wp_image_description_creator_params_v1::{ + Create, SetLuminances, SetMasteringDisplayPrimaries, SetMasteringLuminance, + SetMaxCll, SetMaxFall, SetPrimaries, SetPrimariesNamed, SetTfNamed, SetTfPower, + WpImageDescriptionCreatorParamsV1RequestHandler, + }, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpImageDescriptionCreatorParamsV1 { + pub id: WpImageDescriptionCreatorParamsV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreatorParamsV1 { + type Error = WpImageDescriptionCreatorParamsV1Error; + + fn create(&self, req: Create, _slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(WpImageDescriptionV1 { + id: req.image_description, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + obj.send_ready(0); + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_tf_named(&self, req: SetTfNamed, _slf: &Rc) -> Result<(), Self::Error> { + if req.tf != TRANSFER_FUNCTION_SRGB { + return Err(WpImageDescriptionCreatorParamsV1Error::UnsupportedTf( + req.tf, + )); + } + Ok(()) + } + + fn set_tf_power(&self, _req: SetTfPower, _slf: &Rc) -> Result<(), Self::Error> { + Err(WpImageDescriptionCreatorParamsV1Error::SetTfPowerNotSupported) + } + + fn set_primaries_named( + &self, + req: SetPrimariesNamed, + _slf: &Rc, + ) -> Result<(), Self::Error> { + if req.primaries != PRIMARIES_SRGB { + return Err( + WpImageDescriptionCreatorParamsV1Error::UnsupportedPrimaries(req.primaries), + ); + } + Ok(()) + } + + fn set_primaries(&self, _req: SetPrimaries, _slf: &Rc) -> Result<(), Self::Error> { + Err(WpImageDescriptionCreatorParamsV1Error::SetPrimariesNotSupported) + } + + fn set_luminances(&self, _req: SetLuminances, _slf: &Rc) -> Result<(), Self::Error> { + Err(WpImageDescriptionCreatorParamsV1Error::SetLuminancesNotSupported) + } + + fn set_mastering_display_primaries( + &self, + _req: SetMasteringDisplayPrimaries, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringDisplayPrimariesNotSupported) + } + + fn set_mastering_luminance( + &self, + _req: SetMasteringLuminance, + _slf: &Rc, + ) -> Result<(), Self::Error> { + Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringLuminanceNotSupported) + } + + fn set_max_cll(&self, _req: SetMaxCll, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn set_max_fall(&self, _req: SetMaxFall, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +object_base! { + self = WpImageDescriptionCreatorParamsV1; + version = self.version; +} + +impl Object for WpImageDescriptionCreatorParamsV1 {} + +simple_add_obj!(WpImageDescriptionCreatorParamsV1); + +#[derive(Debug, Error)] +pub enum WpImageDescriptionCreatorParamsV1Error { + #[error(transparent)] + ClientError(Box), + #[error("set_mastering_luminance is not supported")] + SetMasteringLuminanceNotSupported, + #[error("set_mastering_display_primaries is not supported")] + SetMasteringDisplayPrimariesNotSupported, + #[error("set_luminances is not supported")] + SetLuminancesNotSupported, + #[error("set_primaries is not supported")] + SetPrimariesNotSupported, + #[error("{} is not a supported named primary", .0)] + UnsupportedPrimaries(u32), + #[error("set_tf_power is not supported")] + SetTfPowerNotSupported, + #[error("{} is not a supported named transfer function", .0)] + UnsupportedTf(u32), +} +efrom!(WpImageDescriptionCreatorParamsV1Error, ClientError); diff --git a/src/ifs/color_management/wp_image_description_info_v1.rs b/src/ifs/color_management/wp_image_description_info_v1.rs new file mode 100644 index 00000000..bb23069f --- /dev/null +++ b/src/ifs/color_management/wp_image_description_info_v1.rs @@ -0,0 +1,145 @@ +use { + crate::{ + client::Client, + ifs::color_management::consts::{PRIMARIES_SRGB, TRANSFER_FUNCTION_SRGB}, + leaks::Tracker, + object::{Object, Version}, + wire::{WpImageDescriptionInfoV1Id, wp_image_description_info_v1::*}, + }, + std::{convert::Infallible, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct WpImageDescriptionInfoV1 { + pub id: WpImageDescriptionInfoV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpImageDescriptionInfoV1 { + pub fn send_srgb(&self) { + let red = [0.64, 0.33]; + let green = [0.3, 0.6]; + let blue = [0.15, 0.06]; + let white = [0.3127, 0.3290]; + self.send_primaries(red, green, blue, white); + self.send_primaries_named(PRIMARIES_SRGB); + self.send_tf_named(TRANSFER_FUNCTION_SRGB); + self.send_luminances(0.2, 80.0, 80.0); + self.send_target_primaries(red, green, blue, white); + self.send_target_luminances(0.2, 80.0); + self.send_done(); + } + + pub fn send_done(&self) { + self.client.event(Done { self_id: self.id }); + } + + #[expect(dead_code)] + pub fn send_ic_file(&self, file: &Rc, size: usize) { + self.client.event(IccFile { + self_id: self.id, + icc: file.clone(), + icc_size: size as _, + }); + } + + pub fn send_primaries(&self, r: [f64; 2], g: [f64; 2], b: [f64; 2], w: [f64; 2]) { + let map = |c: f64| (c * 1_000_000.0) as i32; + self.client.event(Primaries { + self_id: self.id, + r_x: map(r[0]), + r_y: map(r[1]), + g_x: map(g[0]), + g_y: map(g[1]), + b_x: map(b[0]), + b_y: map(b[1]), + w_x: map(w[0]), + w_y: map(w[1]), + }); + } + + pub fn send_primaries_named(&self, primaries: u32) { + self.client.event(PrimariesNamed { + self_id: self.id, + primaries, + }); + } + + #[expect(dead_code)] + pub fn send_tf_power(&self, eexp: f64) { + self.client.event(TfPower { + self_id: self.id, + eexp: (eexp * 10_000.0) as u32, + }); + } + + pub fn send_tf_named(&self, tf: u32) { + self.client.event(TfNamed { + self_id: self.id, + tf, + }); + } + + pub fn send_luminances(&self, min_lum: f64, max_lum: f64, reference_lum: f64) { + self.client.event(Luminances { + self_id: self.id, + min_lum: (min_lum * 10_000.0) as u32, + max_lum: max_lum as _, + reference_lum: reference_lum as _, + }); + } + + pub fn send_target_primaries(&self, r: [f64; 2], g: [f64; 2], b: [f64; 2], w: [f64; 2]) { + let map = |c: f64| (c * 1_000_000.0) as i32; + self.client.event(TargetPrimaries { + self_id: self.id, + r_x: map(r[0]), + r_y: map(r[1]), + g_x: map(g[0]), + g_y: map(g[1]), + b_x: map(b[0]), + b_y: map(b[1]), + w_x: map(w[0]), + w_y: map(w[1]), + }); + } + + pub fn send_target_luminances(&self, min_lum: f64, max_lum: f64) { + self.client.event(TargetLuminance { + self_id: self.id, + min_lum: (min_lum * 10_000.0) as u32, + max_lum: max_lum as _, + }); + } + + #[expect(dead_code)] + pub fn send_target_max_cll(&self, max_cll: f64) { + self.client.event(TargetMaxCll { + self_id: self.id, + max_cll: max_cll as _, + }); + } + + #[expect(dead_code)] + pub fn send_target_max_fall(&self, max_fall: f64) { + self.client.event(TargetMaxFall { + self_id: self.id, + max_fall: max_fall as _, + }); + } +} + +impl WpImageDescriptionInfoV1RequestHandler for WpImageDescriptionInfoV1 { + type Error = Infallible; +} + +object_base! { + self = WpImageDescriptionInfoV1; + version = self.version; +} + +impl Object for WpImageDescriptionInfoV1 {} + +simple_add_obj!(WpImageDescriptionInfoV1); diff --git a/src/ifs/color_management/wp_image_description_v1.rs b/src/ifs/color_management/wp_image_description_v1.rs new file mode 100644 index 00000000..82d05875 --- /dev/null +++ b/src/ifs/color_management/wp_image_description_v1.rs @@ -0,0 +1,79 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::color_management::wp_image_description_info_v1::WpImageDescriptionInfoV1, + leaks::Tracker, + object::{Object, Version}, + wire::{WpImageDescriptionV1Id, wp_image_description_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct WpImageDescriptionV1 { + pub id: WpImageDescriptionV1Id, + pub client: Rc, + pub version: Version, + pub tracker: Tracker, +} + +impl WpImageDescriptionV1 { + #[expect(dead_code)] + pub fn send_failed(&self, cause: u32, msg: &str) { + self.client.event(Failed { + self_id: self.id, + cause, + msg, + }); + } + + pub fn send_ready(&self, identity: u32) { + self.client.event(Ready { + self_id: self.id, + identity, + }); + } +} + +impl WpImageDescriptionV1RequestHandler for WpImageDescriptionV1 { + type Error = WpImageDescriptionV1Error; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_information(&self, req: GetInformation, _slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(WpImageDescriptionInfoV1 { + id: req.information, + client: self.client.clone(), + version: self.version, + tracker: Default::default(), + }); + self.client.add_client_obj(&obj)?; + track!(self.client, obj); + obj.send_srgb(); + self.client.remove_obj(&*obj)?; + Ok(()) + } +} + +object_base! { + self = WpImageDescriptionV1; + version = self.version; +} + +impl Object for WpImageDescriptionV1 {} + +dedicated_add_obj!( + WpImageDescriptionV1, + WpImageDescriptionV1Id, + wp_image_description +); + +#[derive(Debug, Error)] +pub enum WpImageDescriptionV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(WpImageDescriptionV1Error, ClientError); diff --git a/wire/wp_color_management_output_v1.txt b/wire/wp_color_management_output_v1.txt new file mode 100644 index 00000000..6cfa80c4 --- /dev/null +++ b/wire/wp_color_management_output_v1.txt @@ -0,0 +1,9 @@ +request destroy { +} + +event image_description_changed { +} + +request get_image_description { + image_description: id(wp_image_description_v1), +} diff --git a/wire/wp_color_management_surface_feedback_v1.txt b/wire/wp_color_management_surface_feedback_v1.txt new file mode 100644 index 00000000..187345b7 --- /dev/null +++ b/wire/wp_color_management_surface_feedback_v1.txt @@ -0,0 +1,14 @@ +request destroy { +} + +event preferred_changed { + identity: u32, +} + +request get_preferred { + image_description: id(wp_image_description_v1), +} + +request get_preferred_parametric { + image_description: id(wp_image_description_v1), +} diff --git a/wire/wp_color_management_surface_v1.txt b/wire/wp_color_management_surface_v1.txt new file mode 100644 index 00000000..c7665425 --- /dev/null +++ b/wire/wp_color_management_surface_v1.txt @@ -0,0 +1,10 @@ +request destroy { +} + +request set_image_description { + image_description: id(wp_image_description_v1), + render_intent: u32, +} + +request unset_image_description { +} diff --git a/wire/wp_color_manager_v1.txt b/wire/wp_color_manager_v1.txt new file mode 100644 index 00000000..d683990b --- /dev/null +++ b/wire/wp_color_manager_v1.txt @@ -0,0 +1,48 @@ +request destroy { +} + +request get_output { + id: id(wp_color_management_output_v1), + output: id(wl_output), +} + +request get_surface { + id: id(wp_color_management_surface_v1), + surface: id(wl_surface), +} + +request get_surface_feedback { + id: id(wp_color_management_surface_feedback_v1), + surface: id(wl_surface), +} + +request create_icc_creator { + obj: id(wp_image_description_creator_icc_v1), +} + +request create_parametric_creator { + obj: id(wp_image_description_creator_params_v1), +} + +request create_windows_scrgb { + image_description: id(wp_image_description_v1), +} + +event supported_intent { + render_intent: u32, +} + +event supported_feature { + feature: u32, +} + +event supported_tf_named { + tf: u32, +} + +event supported_primaries_named { + primaries: u32, +} + +event done { +} diff --git a/wire/wp_image_description_creator_icc_v1.txt b/wire/wp_image_description_creator_icc_v1.txt new file mode 100644 index 00000000..cde069fe --- /dev/null +++ b/wire/wp_image_description_creator_icc_v1.txt @@ -0,0 +1,9 @@ +request create { + image_description: id(wp_image_description_v1), +} + +request set_icc_file { + icc_profile: fd, + offset: u32, + length: u32, +} diff --git a/wire/wp_image_description_creator_params_v1.txt b/wire/wp_image_description_creator_params_v1.txt new file mode 100644 index 00000000..83436e46 --- /dev/null +++ b/wire/wp_image_description_creator_params_v1.txt @@ -0,0 +1,56 @@ +request create { + image_description: id(wp_image_description_v1), +} + +request set_tf_named { + tf: u32, +} + +request set_tf_power { + eexp: u32, +} + +request set_primaries_named { + primaries: u32, +} + +request set_primaries { + r_x: i32, + r_y: i32, + g_x: i32, + g_y: i32, + b_x: i32, + b_y: i32, + w_x: i32, + w_y: i32, +} + +request set_luminances { + min_lum: u32, + max_lum: u32, + reference_lum: u32, +} + +request set_mastering_display_primaries { + r_x: i32, + r_y: i32, + g_x: i32, + g_y: i32, + b_x: i32, + b_y: i32, + w_x: i32, + w_y: i32, +} + +request set_mastering_luminance { + min_lum: u32, + max_lum: u32, +} + +request set_max_cll { + max_cll: u32, +} + +request set_max_fall { + max_fall: u32, +} diff --git a/wire/wp_image_description_info_v1.txt b/wire/wp_image_description_info_v1.txt new file mode 100644 index 00000000..c5bfe020 --- /dev/null +++ b/wire/wp_image_description_info_v1.txt @@ -0,0 +1,60 @@ +event done { +} + +event icc_file { + icc: fd, + icc_size: u32, +} + +event primaries { + r_x: i32, + r_y: i32, + g_x: i32, + g_y: i32, + b_x: i32, + b_y: i32, + w_x: i32, + w_y: i32, +} + +event primaries_named { + primaries: u32, +} + +event tf_power { + eexp: u32, +} + +event tf_named { + tf: u32, +} + +event luminances { + min_lum: u32, + max_lum: u32, + reference_lum: u32, +} + +event target_primaries { + r_x: i32, + r_y: i32, + g_x: i32, + g_y: i32, + b_x: i32, + b_y: i32, + w_x: i32, + w_y: i32, +} + +event target_luminance { + min_lum: u32, + max_lum: u32, +} + +event target_max_cll { + max_cll: u32, +} + +event target_max_fall { + max_fall: u32, +} diff --git a/wire/wp_image_description_v1.txt b/wire/wp_image_description_v1.txt new file mode 100644 index 00000000..f1cab457 --- /dev/null +++ b/wire/wp_image_description_v1.txt @@ -0,0 +1,15 @@ +request destroy { +} + +event failed { + cause: u32, + msg: str, +} + +event ready { + identity: u32, +} + +request get_information { + information: id(wp_image_description_info_v1), +}