From c66f5798b7f4cacd44a5000ca5b73ceb4ef3ffaf Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 26 Feb 2025 14:32:57 +0100 Subject: [PATCH 1/3] 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), +} From 248eb324a519515581b0dc63ab15f5bc2cef7fb0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 26 Feb 2025 16:16:38 +0100 Subject: [PATCH 2/3] config: allow disabling color-management --- jay-config/src/_private/client.rs | 4 + jay-config/src/_private/ipc.rs | 3 + jay-config/src/lib.rs | 9 +++ src/cli.rs | 8 +- src/cli/color_management.rs | 75 +++++++++++++++++++ src/compositor.rs | 1 + src/config/handler.rs | 7 ++ src/globals.rs | 7 +- src/ifs.rs | 1 + .../color_management/wp_color_manager_v1.rs | 5 ++ src/ifs/jay_color_management.rs | 64 ++++++++++++++++ src/ifs/jay_compositor.rs | 19 ++++- src/state.rs | 1 + src/tools/tool_client.rs | 2 +- toml-config/src/config.rs | 6 +- toml-config/src/config/parsers.rs | 1 + .../src/config/parsers/color_management.rs | 48 ++++++++++++ toml-config/src/config/parsers/config.rs | 16 ++++ toml-config/src/lib.rs | 11 ++- toml-spec/spec/spec.generated.json | 15 ++++ toml-spec/spec/spec.generated.md | 40 ++++++++++ toml-spec/spec/spec.yaml | 35 +++++++++ wire/jay_color_management.txt | 15 ++++ wire/jay_compositor.txt | 4 + 24 files changed, 388 insertions(+), 9 deletions(-) create mode 100644 src/cli/color_management.rs create mode 100644 src/ifs/jay_color_management.rs create mode 100644 toml-config/src/config/parsers/color_management.rs create mode 100644 wire/jay_color_management.txt diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index f4f263f4..d60f2864 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -763,6 +763,10 @@ impl Client { self.send(&ClientMessage::SetUiDragThreshold { threshold }); } + pub fn set_color_management_enabled(&self, enabled: bool) { + self.send(&ClientMessage::SetColorManagementEnabled { enabled }); + } + pub fn connector_connected(&self, connector: Connector) -> bool { let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); get_response!(res, false, ConnectorConnected { connected }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index a68b05ec..6b3e5116 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -530,6 +530,9 @@ pub enum ClientMessage<'a> { SetIdleGracePeriod { period: Duration, }, + SetColorManagementEnabled { + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index bc55cc56..164551d3 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -264,3 +264,12 @@ pub fn set_ui_drag_enabled(enabled: bool) { pub fn set_ui_drag_threshold(threshold: i32) { get!().set_ui_drag_threshold(threshold); } + +/// Enables or disables the color-management protocol. +/// +/// The default is `false`. +/// +/// Affected applications must be restarted for this to take effect. +pub fn set_color_management_enabled(enabled: bool) { + get!().set_color_management_enabled(enabled); +} diff --git a/src/cli.rs b/src/cli.rs index 50c92b31..f809a36a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ mod color; +mod color_management; mod damage_tracking; mod duration; mod generate; @@ -17,8 +18,8 @@ mod xwayland; use { crate::{ cli::{ - damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, - xwayland::XwaylandArgs, + color_management::ColorManagementArgs, damage_tracking::DamageTrackingArgs, + idle::IdleCmd, input::InputArgs, randr::RandrArgs, xwayland::XwaylandArgs, }, compositor::start_compositor, format::{Format, ref_formats}, @@ -78,6 +79,8 @@ pub enum Cmd { DamageTracking(DamageTrackingArgs), /// Inspect/modify xwayland settings. Xwayland(XwaylandArgs), + /// Inspect/modify the color-management settings. + ColorManagement(ColorManagementArgs), #[cfg(feature = "it")] RunTests, } @@ -235,6 +238,7 @@ pub fn main() { Cmd::Input(a) => input::main(cli.global, a), Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a), Cmd::Xwayland(a) => xwayland::main(cli.global, a), + Cmd::ColorManagement(a) => color_management::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/color_management.rs b/src/cli/color_management.rs new file mode 100644 index 00000000..b70a8c9d --- /dev/null +++ b/src/cli/color_management.rs @@ -0,0 +1,75 @@ +use { + crate::{ + cli::GlobalArgs, + tools::tool_client::{Handle, ToolClient, with_tool_client}, + wire::{JayColorManagementId, jay_color_management, jay_compositor}, + }, + clap::{Args, Subcommand}, + std::{cell::Cell, rc::Rc}, +}; + +#[derive(Args, Debug)] +pub struct ColorManagementArgs { + #[clap(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug, Default)] +pub enum ColorManagementCmd { + /// Print the color-management status. + #[default] + Status, + /// Enable the color-management protocol. + Enable, + /// Disable the color-management protocol. + Disable, +} + +pub fn main(global: GlobalArgs, args: ColorManagementArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let cm = ColorManagement { tc: tc.clone() }; + cm.run(args).await; + }); +} + +struct ColorManagement { + tc: Rc, +} + +impl ColorManagement { + async fn run(self, args: ColorManagementArgs) { + let tc = &self.tc; + let comp = tc.jay_compositor().await; + let id = tc.id(); + tc.send(jay_compositor::GetColorManagement { self_id: comp, id }); + match args.command.unwrap_or_default() { + ColorManagementCmd::Status => self.status(id).await, + ColorManagementCmd::Enable => self.set_enabled(id, true).await, + ColorManagementCmd::Disable => self.set_enabled(id, false).await, + } + } + + async fn status(self, id: JayColorManagementId) { + let tc = &self.tc; + tc.send(jay_color_management::Get { self_id: id }); + let enabled = Rc::new(Cell::new(false)); + jay_color_management::Enabled::handle(tc, id, enabled.clone(), |iv, msg| { + iv.set(msg.enabled != 0); + }); + tc.round_trip().await; + if enabled.get() { + println!("Enabled"); + } else { + println!("Disabled"); + } + } + + async fn set_enabled(self, id: JayColorManagementId, enabled: bool) { + let tc = &self.tc; + tc.send(jay_color_management::SetEnabled { + self_id: id, + enabled: enabled as _, + }); + tc.round_trip().await; + } +} diff --git a/src/compositor.rs b/src/compositor.rs index ab01b1d5..06ca97d2 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -283,6 +283,7 @@ fn start_compositor2( tray_item_ids: Default::default(), data_control_device_ids: Default::default(), workspace_managers: Default::default(), + color_management_enabled: Cell::new(false), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index 5d8537c5..f18ccc79 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -926,6 +926,10 @@ impl ConfigProxyHandler { self.state.explicit_sync_enabled.set(enabled); } + fn handle_set_color_management_enabled(&self, enabled: bool) { + self.state.color_management_enabled.set(enabled); + } + fn handle_get_socket_path(&self) { match self.state.acceptor.get() { Some(a) => { @@ -1986,6 +1990,9 @@ impl ConfigProxyHandler { ClientMessage::SetIdleGracePeriod { period } => { self.handle_set_idle_grace_period(period) } + ClientMessage::SetColorManagementEnabled { enabled } => { + self.handle_set_color_management_enabled(enabled) + } } Ok(()) } diff --git a/src/globals.rs b/src/globals.rs index a50d0e62..738e647c 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -135,6 +135,10 @@ pub trait Global: GlobalBase { fn xwayland_only(&self) -> bool { false } + fn exposed(&self, state: &State) -> bool { + let _ = state; + true + } } pub struct Globals { @@ -297,7 +301,8 @@ impl Globals { ($singleton:expr) => { for global in globals.values() { if global.singleton() == $singleton { - if caps.contains(global.required_caps()) + if global.exposed(®istry.client.state) + && caps.contains(global.required_caps()) && (xwayland || !global.xwayland_only()) { registry.send_global(global); diff --git a/src/ifs.rs b/src/ifs.rs index 4108e80c..d33343b9 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -10,6 +10,7 @@ pub mod ext_output_image_capture_source_manager_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod ipc; +pub mod jay_color_management; pub mod jay_compositor; pub mod jay_damage_tracking; pub mod jay_ei_session; diff --git a/src/ifs/color_management/wp_color_manager_v1.rs b/src/ifs/color_management/wp_color_manager_v1.rs index 2bfbb2ef..72119287 100644 --- a/src/ifs/color_management/wp_color_manager_v1.rs +++ b/src/ifs/color_management/wp_color_manager_v1.rs @@ -14,6 +14,7 @@ use { }, leaks::Tracker, object::{Object, Version}, + state::State, wire::{ WpColorManagerV1Id, wp_color_manager_v1::{SupportedIntent, *}, @@ -198,6 +199,10 @@ impl Global for WpColorManagerV1Global { fn version(&self) -> u32 { 1 } + + fn exposed(&self, state: &State) -> bool { + state.color_management_enabled.get() + } } simple_add_global!(WpColorManagerV1Global); diff --git a/src/ifs/jay_color_management.rs b/src/ifs/jay_color_management.rs new file mode 100644 index 00000000..565160f3 --- /dev/null +++ b/src/ifs/jay_color_management.rs @@ -0,0 +1,64 @@ +use { + crate::{ + client::{Client, ClientError}, + leaks::Tracker, + object::{Object, Version}, + wire::{JayColorManagementId, jay_color_management::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct JayColorManagement { + pub id: JayColorManagementId, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl JayColorManagement { + fn send_enabled(&self) { + self.client.event(Enabled { + self_id: self.id, + enabled: self.client.state.color_management_enabled.get() as u32, + }); + } +} + +impl JayColorManagementRequestHandler for JayColorManagement { + type Error = JayColorManagementError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn get(&self, _req: Get, _slf: &Rc) -> Result<(), Self::Error> { + self.send_enabled(); + Ok(()) + } + + fn set_enabled(&self, req: SetEnabled, _slf: &Rc) -> Result<(), Self::Error> { + self.client + .state + .color_management_enabled + .set(req.enabled != 0); + Ok(()) + } +} + +object_base! { + self = JayColorManagement; + version = self.version; +} + +impl Object for JayColorManagement {} + +simple_add_obj!(JayColorManagement); + +#[derive(Debug, Error)] +pub enum JayColorManagementError { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayColorManagementError, ClientError); diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 86893814..2da874f1 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -4,6 +4,7 @@ use { client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, globals::{Global, GlobalName}, ifs::{ + jay_color_management::JayColorManagement, jay_ei_session_builder::JayEiSessionBuilder, jay_idle::JayIdle, jay_input::JayInput, @@ -72,7 +73,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 13 + 14 } fn required_caps(&self) -> ClientCaps { @@ -439,6 +440,22 @@ impl JayCompositorRequestHandler for JayCompositor { obj.done(tl); Ok(()) } + + fn get_color_management( + &self, + req: GetColorManagement, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let obj = Rc::new(JayColorManagement { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + Ok(()) + } } object_base! { diff --git a/src/state.rs b/src/state.rs index d3b2694d..07580bc3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -233,6 +233,7 @@ pub struct State { pub tray_item_ids: TrayItemIds, pub data_control_device_ids: DataControlDeviceIds, pub workspace_managers: WorkspaceManagerState, + pub color_management_enabled: Cell, } // impl Drop for State { diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 12f801fc..04ab2684 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -332,7 +332,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(13), + version: s.jay_compositor.1.min(14), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 43730a22..d13a3522 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -11,7 +11,10 @@ use { crate::{ config::{ context::Context, - parsers::config::{ConfigParser, ConfigParserError}, + parsers::{ + color_management::ColorManagement, + config::{ConfigParser, ConfigParserError}, + }, }, toml::{self}, }, @@ -358,6 +361,7 @@ pub struct Config { pub libei: Libei, pub ui_drag: UiDrag, pub xwayland: Option, + pub color_management: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index 57a8d41d..a04a1ce1 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -8,6 +8,7 @@ use { pub mod action; mod color; +pub mod color_management; pub mod config; mod connector; mod connector_match; diff --git a/toml-config/src/config/parsers/color_management.rs b/toml-config/src/config/parsers/color_management.rs new file mode 100644 index 00000000..53a64bb9 --- /dev/null +++ b/toml-config/src/config/parsers/color_management.rs @@ -0,0 +1,48 @@ +use { + crate::{ + config::{ + context::Context, + extractor::{Extractor, ExtractorError, bol, opt}, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + }, + toml::{ + toml_span::{DespanExt, Span, Spanned}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ColorManagementParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + Extract(#[from] ExtractorError), +} + +pub struct ColorManagementParser<'a>(pub &'a Context<'a>); + +#[derive(Clone, Debug)] +pub struct ColorManagement { + pub enabled: Option, +} + +impl Parser for ColorManagementParser<'_> { + type Value = ColorManagement; + type Error = ColorManagementParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (enabled,) = ext.extract((opt(bol("enabled")),))?; + Ok(ColorManagement { + enabled: enabled.despan(), + }) + } +} diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 4ca65a88..e1ad68bc 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -7,6 +7,7 @@ use { parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ action::ActionParser, + color_management::ColorManagementParser, connector::ConnectorsParser, drm_device::DrmDevicesParser, drm_device_match::DrmDeviceMatchParser, @@ -117,6 +118,7 @@ impl Parser for ConfigParser<'_> { ui_drag_val, xwayland_val, ), + (color_management_val,), ) = ext.extract(( ( opt(val("keymap")), @@ -154,6 +156,7 @@ impl Parser for ConfigParser<'_> { opt(val("ui-drag")), opt(val("xwayland")), ), + (opt(val("color-management")),), ))?; let mut keymap = None; if let Some(value) = keymap_val { @@ -366,6 +369,18 @@ impl Parser for ConfigParser<'_> { } } } + let mut color_management = None; + if let Some(value) = color_management_val { + match value.parse(&mut ColorManagementParser(self.0)) { + Ok(v) => color_management = Some(v), + Err(e) => { + log::warn!( + "Could not parse the color-management settings: {}", + self.0.error(e) + ); + } + } + } Ok(Config { keymap, repeat_rate, @@ -396,6 +411,7 @@ impl Parser for ConfigParser<'_> { libei, ui_drag, xwayland, + color_management, }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 7fa7cb2d..4883548c 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -23,9 +23,9 @@ use { is_reload, keyboard::{Keymap, ModifiedKeySym}, logging::set_log_level, - on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, - set_explicit_sync_enabled, set_idle, set_idle_grace_period, set_ui_drag_enabled, - set_ui_drag_threshold, + on_devices_enumerated, on_idle, quit, reload, set_color_management_enabled, + set_default_workspace_capture, set_explicit_sync_enabled, set_idle, set_idle_grace_period, + set_ui_drag_enabled, set_ui_drag_threshold, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, switch_to_vt, theme::{reset_colors, reset_font, reset_sizes, set_font}, @@ -1078,6 +1078,11 @@ fn load_config(initial_load: bool, persistent: &Rc) { set_x_scaling_mode(mode); } } + if let Some(cm) = config.color_management { + if let Some(enabled) = cm.enabled { + set_color_management_enabled(enabled); + } + } } fn create_command(exec: &Exec) -> Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 94862eb6..4b3dfc8b 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -427,6 +427,17 @@ "type": "string", "description": "A color.\n\nThe format should be one of the following:\n\n- `#rgb`\n- `#rrggbb`\n- `#rgba`\n- `#rrggbba`\n" }, + "ColorManagement": { + "description": "Describes color-management settings.\n\n- Example:\n\n ```toml\n [color-management]\n enabled = true\n ```\n", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the color management protocol is enabled.\n\nThis has no effect on running applications.\n\nThe default is `false`.\n" + } + }, + "required": [] + }, "ComplexShortcut": { "description": "Describes a complex shortcut.\n\n- Example:\n\n ```toml\n [complex-shortcuts.XF86AudioRaiseVolume]\n mod-mask = \"alt\"\n action = { type = \"exec\", exec = [\"pactl\", \"set-sink-volume\", \"0\", \"+10%\"] }\n ```\n", "type": "object", @@ -597,6 +608,10 @@ "xwayland": { "description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", "$ref": "#/$defs/Xwayland" + }, + "color-management": { + "description": "Configures the color-management settings.\n\n- Example:\n\n ```toml\n [color-management]\n enabled = true\n ```\n", + "$ref": "#/$defs/ColorManagement" } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index a494591f..3b9c8352 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -590,6 +590,33 @@ The format should be one of the following: Values of this type should be strings. + +### `ColorManagement` + +Describes color-management settings. + +- Example: + + ```toml + [color-management] + enabled = true + ``` + +Values of this type should be tables. + +The table has the following fields: + +- `enabled` (optional): + + Whether the color management protocol is enabled. + + This has no effect on running applications. + + The default is `false`. + + The value of this field should be a boolean. + + ### `ComplexShortcut` @@ -1178,6 +1205,19 @@ The table has the following fields: The value of this field should be a [Xwayland](#types-Xwayland). +- `color-management` (optional): + + Configures the color-management settings. + + - Example: + + ```toml + [color-management] + enabled = true + ``` + + The value of this field should be a [ColorManagement](#types-ColorManagement). + ### `Connector` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 9035e2f1..89752fbb 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -2286,6 +2286,18 @@ Config: ```toml xwayland = { scaling-mode = "downscaled" } ``` + color-management: + ref: ColorManagement + required: false + description: | + Configures the color-management settings. + + - Example: + + ```toml + [color-management] + enabled = true + ``` Idle: @@ -2727,3 +2739,26 @@ XScalingMode: Additionally, this mode requires the X window to scale its contents itself. In the example above, you might achieve this by setting the environment variable `GDK_SCALE=2`. + + +ColorManagement: + kind: table + description: | + Describes color-management settings. + + - Example: + + ```toml + [color-management] + enabled = true + ``` + fields: + enabled: + description: | + Whether the color management protocol is enabled. + + This has no effect on running applications. + + The default is `false`. + kind: boolean + required: false diff --git a/wire/jay_color_management.txt b/wire/jay_color_management.txt new file mode 100644 index 00000000..8d75eb0a --- /dev/null +++ b/wire/jay_color_management.txt @@ -0,0 +1,15 @@ +request destroy { + +} + +request get { + +} + +request set_enabled { + enabled: u32, +} + +event enabled { + enabled: u32, +} diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index 84f70406..79866072 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -101,6 +101,10 @@ request get_toplevel (since = 12) { toplevel_id: str, } +request get_color_management (since = 14) { + id: id(jay_color_management), +} + # events event client_id { From fcd2e3ab33e0962cf0a09876c283f07ec28c65ac Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 26 Feb 2025 16:24:22 +0100 Subject: [PATCH 3/3] color-management-v1: only expose the protocol with vulkan --- src/cli/color_management.rs | 10 +++++++++- src/gfx_api.rs | 4 ++++ src/gfx_apis/vulkan.rs | 4 ++++ src/ifs/color_management/wp_color_manager_v1.rs | 2 +- src/ifs/jay_color_management.rs | 8 ++++++++ src/state.rs | 10 ++++++++++ wire/jay_color_management.txt | 4 ++++ 7 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/cli/color_management.rs b/src/cli/color_management.rs index b70a8c9d..edae33ca 100644 --- a/src/cli/color_management.rs +++ b/src/cli/color_management.rs @@ -56,9 +56,17 @@ impl ColorManagement { jay_color_management::Enabled::handle(tc, id, enabled.clone(), |iv, msg| { iv.set(msg.enabled != 0); }); + let available = Rc::new(Cell::new(false)); + jay_color_management::Available::handle(tc, id, available.clone(), |iv, msg| { + iv.set(msg.available != 0); + }); tc.round_trip().await; if enabled.get() { - println!("Enabled"); + print!("Enabled"); + if !available.get() { + print!(" (Unavailable)"); + } + println!(); } else { println!("Disabled"); } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 4577f8a3..67a06bc1 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -742,6 +742,10 @@ pub trait GfxContext: Debug { width: i32, height: i32, ) -> Result, GfxError>; + + fn supports_color_management(&self) -> bool { + false + } } #[derive(Clone, Debug)] diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 24b13165..8a1e568d 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -363,6 +363,10 @@ impl GfxContext for Context { let buffer = self.0.acquire_blend_buffer(width, height)?; Ok(buffer) } + + fn supports_color_management(&self) -> bool { + self.0.device.descriptor_buffer.is_some() + } } impl Drop for Context { diff --git a/src/ifs/color_management/wp_color_manager_v1.rs b/src/ifs/color_management/wp_color_manager_v1.rs index 72119287..acdf3d9d 100644 --- a/src/ifs/color_management/wp_color_manager_v1.rs +++ b/src/ifs/color_management/wp_color_manager_v1.rs @@ -201,7 +201,7 @@ impl Global for WpColorManagerV1Global { } fn exposed(&self, state: &State) -> bool { - state.color_management_enabled.get() + state.color_management_available() } } diff --git a/src/ifs/jay_color_management.rs b/src/ifs/jay_color_management.rs index 565160f3..5d5194e9 100644 --- a/src/ifs/jay_color_management.rs +++ b/src/ifs/jay_color_management.rs @@ -23,6 +23,13 @@ impl JayColorManagement { enabled: self.client.state.color_management_enabled.get() as u32, }); } + + fn send_available(&self) { + self.client.event(Available { + self_id: self.id, + available: self.client.state.color_management_available() as u32, + }); + } } impl JayColorManagementRequestHandler for JayColorManagement { @@ -35,6 +42,7 @@ impl JayColorManagementRequestHandler for JayColorManagement { fn get(&self, _req: Get, _slf: &Rc) -> Result<(), Self::Error> { self.send_enabled(); + self.send_available(); Ok(()) } diff --git a/src/state.rs b/src/state.rs index 07580bc3..d56b523f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1315,6 +1315,16 @@ impl State { pub fn tray_icon_size(&self) -> i32 { (self.theme.sizes.title_height.get() - 2).max(0) } + + pub fn color_management_available(&self) -> bool { + if !self.color_management_enabled.get() { + return false; + } + let Some(ctx) = self.render_ctx.get() else { + return false; + }; + ctx.supports_color_management() + } } #[derive(Debug, Error)] diff --git a/wire/jay_color_management.txt b/wire/jay_color_management.txt index 8d75eb0a..82f8d79c 100644 --- a/wire/jay_color_management.txt +++ b/wire/jay_color_management.txt @@ -13,3 +13,7 @@ request set_enabled { event enabled { enabled: u32, } + +event available { + available: u32, +}