1
0
Fork 0
forked from wry/wry

Merge pull request #383 from mahkoh/jorth/cm-protocol

color-management-v1: initial implementation
This commit is contained in:
mahkoh 2025-02-26 16:30:11 +01:00 committed by GitHub
commit 26db79e249
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1556 additions and 12 deletions

View file

@ -160,6 +160,7 @@ Jay supports the following wayland protocols:
| wl_shm | 2 | | | wl_shm | 2 | |
| wl_subcompositor | 1 | | | wl_subcompositor | 1 | |
| wp_alpha_modifier_v1 | 1 | | | wp_alpha_modifier_v1 | 1 | |
| wp_color_manager_v1 | 1[^color_mng] | |
| wp_commit_timing_manager_v1 | 1 | | | wp_commit_timing_manager_v1 | 1 | |
| wp_content_type_manager_v1 | 1 | | | wp_content_type_manager_v1 | 1 | |
| wp_cursor_shape_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. [^lsaccess]: Sandboxes can restrict access to this protocol.
[^ts_rejected]: Seat creation is always rejected. [^ts_rejected]: Seat creation is always rejected.
[^composited]: Cursors are always composited. [^composited]: Cursors are always composited.
[^color_mng]: Only SRGB is supported.

View file

@ -763,6 +763,10 @@ impl Client {
self.send(&ClientMessage::SetUiDragThreshold { threshold }); 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 { pub fn connector_connected(&self, connector: Connector) -> bool {
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
get_response!(res, false, ConnectorConnected { connected }); get_response!(res, false, ConnectorConnected { connected });

View file

@ -530,6 +530,9 @@ pub enum ClientMessage<'a> {
SetIdleGracePeriod { SetIdleGracePeriod {
period: Duration, period: Duration,
}, },
SetColorManagementEnabled {
enabled: bool,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -264,3 +264,12 @@ pub fn set_ui_drag_enabled(enabled: bool) {
pub fn set_ui_drag_threshold(threshold: i32) { pub fn set_ui_drag_threshold(threshold: i32) {
get!().set_ui_drag_threshold(threshold); 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);
}

View file

@ -9,6 +9,7 @@
opacity on top of a red window will produce a perfectly yellow image instead of a muddy 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 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. observable. This should have no impact on performance in the common case.
- Implement color-management-v1.
# 1.9.1 (2025-02-13) # 1.9.1 (2025-02-13)

View file

@ -1,4 +1,5 @@
mod color; mod color;
mod color_management;
mod damage_tracking; mod damage_tracking;
mod duration; mod duration;
mod generate; mod generate;
@ -17,8 +18,8 @@ mod xwayland;
use { use {
crate::{ crate::{
cli::{ cli::{
damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, color_management::ColorManagementArgs, damage_tracking::DamageTrackingArgs,
xwayland::XwaylandArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, xwayland::XwaylandArgs,
}, },
compositor::start_compositor, compositor::start_compositor,
format::{Format, ref_formats}, format::{Format, ref_formats},
@ -78,6 +79,8 @@ pub enum Cmd {
DamageTracking(DamageTrackingArgs), DamageTracking(DamageTrackingArgs),
/// Inspect/modify xwayland settings. /// Inspect/modify xwayland settings.
Xwayland(XwaylandArgs), Xwayland(XwaylandArgs),
/// Inspect/modify the color-management settings.
ColorManagement(ColorManagementArgs),
#[cfg(feature = "it")] #[cfg(feature = "it")]
RunTests, RunTests,
} }
@ -235,6 +238,7 @@ pub fn main() {
Cmd::Input(a) => input::main(cli.global, a), Cmd::Input(a) => input::main(cli.global, a),
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a), Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),
Cmd::Xwayland(a) => xwayland::main(cli.global, a), Cmd::Xwayland(a) => xwayland::main(cli.global, a),
Cmd::ColorManagement(a) => color_management::main(cli.global, a),
#[cfg(feature = "it")] #[cfg(feature = "it")]
Cmd::RunTests => crate::it::run_tests(), Cmd::RunTests => crate::it::run_tests(),
} }

View file

@ -0,0 +1,83 @@
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<ColorManagementCmd>,
}
#[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<ToolClient>,
}
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);
});
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() {
print!("Enabled");
if !available.get() {
print!(" (Unavailable)");
}
println!();
} 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;
}
}

View file

@ -2,6 +2,7 @@ use {
crate::{ crate::{
client::{Client, ClientError}, client::{Client, ClientError},
ifs::{ ifs::{
color_management::wp_image_description_v1::WpImageDescriptionV1,
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
ext_image_capture_source_v1::ExtImageCaptureSourceV1, ext_image_capture_source_v1::ExtImageCaptureSourceV1,
ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1,
@ -43,9 +44,9 @@ use {
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId, ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId,
JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId, WpImageDescriptionV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId,
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id,
ZwpTabletToolV2Id, ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id,
}, },
}, },
std::{cell::RefCell, rc::Rc}, std::{cell::RefCell, rc::Rc},
@ -85,6 +86,7 @@ pub struct Objects {
pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>, pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>,
pub ext_workspace_groups: pub ext_workspace_groups:
CopyHashMap<ExtWorkspaceGroupHandleV1Id, Rc<ExtWorkspaceGroupHandleV1>>, CopyHashMap<ExtWorkspaceGroupHandleV1Id, Rc<ExtWorkspaceGroupHandleV1>>,
pub wp_image_description: CopyHashMap<WpImageDescriptionV1Id, Rc<WpImageDescriptionV1>>,
ids: RefCell<Vec<usize>>, ids: RefCell<Vec<usize>>,
} }
@ -123,6 +125,7 @@ impl Objects {
ext_copy_sessions: Default::default(), ext_copy_sessions: Default::default(),
ext_data_sources: Default::default(), ext_data_sources: Default::default(),
ext_workspace_groups: Default::default(), ext_workspace_groups: Default::default(),
wp_image_description: Default::default(),
ids: RefCell::new(vec![]), ids: RefCell::new(vec![]),
} }
} }

View file

@ -283,6 +283,7 @@ fn start_compositor2(
tray_item_ids: Default::default(), tray_item_ids: Default::default(),
data_control_device_ids: Default::default(), data_control_device_ids: Default::default(),
workspace_managers: Default::default(), workspace_managers: Default::default(),
color_management_enabled: Cell::new(false),
}); });
state.tracker.register(ClientId::from_raw(0)); state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state); create_dummy_output(&state);

View file

@ -926,6 +926,10 @@ impl ConfigProxyHandler {
self.state.explicit_sync_enabled.set(enabled); 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) { fn handle_get_socket_path(&self) {
match self.state.acceptor.get() { match self.state.acceptor.get() {
Some(a) => { Some(a) => {
@ -1986,6 +1990,9 @@ impl ConfigProxyHandler {
ClientMessage::SetIdleGracePeriod { period } => { ClientMessage::SetIdleGracePeriod { period } => {
self.handle_set_idle_grace_period(period) self.handle_set_idle_grace_period(period)
} }
ClientMessage::SetColorManagementEnabled { enabled } => {
self.handle_set_color_management_enabled(enabled)
}
} }
Ok(()) Ok(())
} }

View file

@ -742,6 +742,10 @@ pub trait GfxContext: Debug {
width: i32, width: i32,
height: i32, height: i32,
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError>; ) -> Result<Rc<dyn GfxBlendBuffer>, GfxError>;
fn supports_color_management(&self) -> bool {
false
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -363,6 +363,10 @@ impl GfxContext for Context {
let buffer = self.0.acquire_blend_buffer(width, height)?; let buffer = self.0.acquire_blend_buffer(width, height)?;
Ok(buffer) Ok(buffer)
} }
fn supports_color_management(&self) -> bool {
self.0.device.descriptor_buffer.is_some()
}
} }
impl Drop for Context { impl Drop for Context {

View file

@ -3,6 +3,7 @@ use {
backend::Backend, backend::Backend,
client::{Client, ClientCaps}, client::{Client, ClientCaps},
ifs::{ ifs::{
color_management::wp_color_manager_v1::WpColorManagerV1Global,
ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global, ext_foreign_toplevel_image_capture_source_manager_v1::ExtForeignToplevelImageCaptureSourceManagerV1Global,
ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global,
ext_idle_notifier_v1::ExtIdleNotifierV1Global, ext_idle_notifier_v1::ExtIdleNotifierV1Global,
@ -134,6 +135,10 @@ pub trait Global: GlobalBase {
fn xwayland_only(&self) -> bool { fn xwayland_only(&self) -> bool {
false false
} }
fn exposed(&self, state: &State) -> bool {
let _ = state;
true
}
} }
pub struct Globals { pub struct Globals {
@ -215,6 +220,7 @@ impl Globals {
add_singleton!(ExtDataControlManagerV1Global); add_singleton!(ExtDataControlManagerV1Global);
add_singleton!(WlFixesGlobal); add_singleton!(WlFixesGlobal);
add_singleton!(ExtWorkspaceManagerV1Global); add_singleton!(ExtWorkspaceManagerV1Global);
add_singleton!(WpColorManagerV1Global);
} }
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) { pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
@ -295,7 +301,8 @@ impl Globals {
($singleton:expr) => { ($singleton:expr) => {
for global in globals.values() { for global in globals.values() {
if global.singleton() == $singleton { if global.singleton() == $singleton {
if caps.contains(global.required_caps()) if global.exposed(&registry.client.state)
&& caps.contains(global.required_caps())
&& (xwayland || !global.xwayland_only()) && (xwayland || !global.xwayland_only())
{ {
registry.send_global(global); registry.send_global(global);

View file

@ -1,3 +1,4 @@
pub mod color_management;
pub mod ext_foreign_toplevel_handle_v1; pub mod ext_foreign_toplevel_handle_v1;
pub mod ext_foreign_toplevel_image_capture_source_manager_v1; pub mod ext_foreign_toplevel_image_capture_source_manager_v1;
pub mod ext_foreign_toplevel_list_v1; pub mod ext_foreign_toplevel_list_v1;
@ -9,6 +10,7 @@ pub mod ext_output_image_capture_source_manager_v1;
pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_manager_v1;
pub mod ext_session_lock_v1; pub mod ext_session_lock_v1;
pub mod ipc; pub mod ipc;
pub mod jay_color_management;
pub mod jay_compositor; pub mod jay_compositor;
pub mod jay_damage_tracking; pub mod jay_damage_tracking;
pub mod jay_ei_session; pub mod jay_ei_session;

View file

@ -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;
}

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
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<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_image_description(
&self,
req: GetImageDescription,
_slf: &Rc<Self>,
) -> 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<ClientError>),
}
efrom!(WpColorManagementOutputV1Error, ClientError);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
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<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_preferred(&self, req: GetPreferred, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.get_description(req.image_description)
}
fn get_preferred_parametric(
&self,
req: GetPreferredParametric,
_slf: &Rc<Self>,
) -> 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<ClientError>),
}
efrom!(WpColorManagementSurfaceFeedbackV1Error, ClientError);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
impl WpColorManagementSurfaceV1RequestHandler for WpColorManagementSurfaceV1 {
type Error = WpColorManagementSurfaceV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn set_image_description(
&self,
req: SetImageDescription,
_slf: &Rc<Self>,
) -> 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<Self>,
) -> 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<ClientError>),
#[error("{} is not a supported render intent", .0)]
UnsupportedRenderIntent(u32),
}
efrom!(WpColorManagementSurfaceV1Error, ClientError);

View file

@ -0,0 +1,228 @@
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},
state::State,
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<Self>,
id: WpColorManagerV1Id,
client: &Rc<Client>,
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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
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<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_output(&self, req: GetOutput, _slf: &Rc<Self>) -> 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<Self>) -> 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<Self>,
) -> 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<Self>,
) -> Result<(), Self::Error> {
Err(WpColorManagerV1Error::CreateIccCreatorNotSupported)
}
fn create_parametric_creator(
&self,
req: CreateParametricCreator,
_slf: &Rc<Self>,
) -> 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<Self>,
) -> 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
}
fn exposed(&self, state: &State) -> bool {
state.color_management_available()
}
}
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<ClientError>),
#[error("create_icc_creator is not supported")]
CreateIccCreatorNotSupported,
#[error("create_windows_scrgb is not supported")]
CreateWindowsScrgbNotSupported,
}
efrom!(WpColorManagerV1Error, ClientError);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
impl WpImageDescriptionCreatorIccV1RequestHandler for WpImageDescriptionCreatorIccV1 {
type Error = Infallible;
fn create(&self, _req: Create, _slf: &Rc<Self>) -> Result<(), Self::Error> {
unreachable!()
}
fn set_icc_file(&self, _req: SetIccFile, _slf: &Rc<Self>) -> Result<(), Self::Error> {
unreachable!()
}
}
object_base! {
self = WpImageDescriptionCreatorIccV1;
version = self.version;
}
impl Object for WpImageDescriptionCreatorIccV1 {}
simple_add_obj!(WpImageDescriptionCreatorIccV1);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
impl WpImageDescriptionCreatorParamsV1RequestHandler for WpImageDescriptionCreatorParamsV1 {
type Error = WpImageDescriptionCreatorParamsV1Error;
fn create(&self, req: Create, _slf: &Rc<Self>) -> 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<Self>) -> 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<Self>) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetTfPowerNotSupported)
}
fn set_primaries_named(
&self,
req: SetPrimariesNamed,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if req.primaries != PRIMARIES_SRGB {
return Err(
WpImageDescriptionCreatorParamsV1Error::UnsupportedPrimaries(req.primaries),
);
}
Ok(())
}
fn set_primaries(&self, _req: SetPrimaries, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetPrimariesNotSupported)
}
fn set_luminances(&self, _req: SetLuminances, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetLuminancesNotSupported)
}
fn set_mastering_display_primaries(
&self,
_req: SetMasteringDisplayPrimaries,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringDisplayPrimariesNotSupported)
}
fn set_mastering_luminance(
&self,
_req: SetMasteringLuminance,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
Err(WpImageDescriptionCreatorParamsV1Error::SetMasteringLuminanceNotSupported)
}
fn set_max_cll(&self, _req: SetMaxCll, _slf: &Rc<Self>) -> Result<(), Self::Error> {
Ok(())
}
fn set_max_fall(&self, _req: SetMaxFall, _slf: &Rc<Self>) -> 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<ClientError>),
#[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);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
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<OwnedFd>, 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);

View file

@ -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<Client>,
pub version: Version,
pub tracker: Tracker<Self>,
}
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<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_information(&self, req: GetInformation, _slf: &Rc<Self>) -> 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<ClientError>),
}
efrom!(WpImageDescriptionV1Error, ClientError);

View file

@ -0,0 +1,72 @@
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<Client>,
pub tracker: Tracker<Self>,
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,
});
}
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 {
type Error = JayColorManagementError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get(&self, _req: Get, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.send_enabled();
self.send_available();
Ok(())
}
fn set_enabled(&self, req: SetEnabled, _slf: &Rc<Self>) -> 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<ClientError>),
}
efrom!(JayColorManagementError, ClientError);

View file

@ -4,6 +4,7 @@ use {
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError}, client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError},
globals::{Global, GlobalName}, globals::{Global, GlobalName},
ifs::{ ifs::{
jay_color_management::JayColorManagement,
jay_ei_session_builder::JayEiSessionBuilder, jay_ei_session_builder::JayEiSessionBuilder,
jay_idle::JayIdle, jay_idle::JayIdle,
jay_input::JayInput, jay_input::JayInput,
@ -72,7 +73,7 @@ impl Global for JayCompositorGlobal {
} }
fn version(&self) -> u32 { fn version(&self) -> u32 {
13 14
} }
fn required_caps(&self) -> ClientCaps { fn required_caps(&self) -> ClientCaps {
@ -439,6 +440,22 @@ impl JayCompositorRequestHandler for JayCompositor {
obj.done(tl); obj.done(tl);
Ok(()) Ok(())
} }
fn get_color_management(
&self,
req: GetColorManagement,
_slf: &Rc<Self>,
) -> 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! { object_base! {

View file

@ -233,6 +233,7 @@ pub struct State {
pub tray_item_ids: TrayItemIds, pub tray_item_ids: TrayItemIds,
pub data_control_device_ids: DataControlDeviceIds, pub data_control_device_ids: DataControlDeviceIds,
pub workspace_managers: WorkspaceManagerState, pub workspace_managers: WorkspaceManagerState,
pub color_management_enabled: Cell<bool>,
} }
// impl Drop for State { // impl Drop for State {
@ -1314,6 +1315,16 @@ impl State {
pub fn tray_icon_size(&self) -> i32 { pub fn tray_icon_size(&self) -> i32 {
(self.theme.sizes.title_height.get() - 2).max(0) (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)] #[derive(Debug, Error)]

View file

@ -332,7 +332,7 @@ impl ToolClient {
self_id: s.registry, self_id: s.registry,
name: s.jay_compositor.0, name: s.jay_compositor.0,
interface: JayCompositor.name(), interface: JayCompositor.name(),
version: s.jay_compositor.1.min(13), version: s.jay_compositor.1.min(14),
id: id.into(), id: id.into(),
}); });
self.jay_compositor.set(Some(id)); self.jay_compositor.set(Some(id));

View file

@ -11,7 +11,10 @@ use {
crate::{ crate::{
config::{ config::{
context::Context, context::Context,
parsers::config::{ConfigParser, ConfigParserError}, parsers::{
color_management::ColorManagement,
config::{ConfigParser, ConfigParserError},
},
}, },
toml::{self}, toml::{self},
}, },
@ -358,6 +361,7 @@ pub struct Config {
pub libei: Libei, pub libei: Libei,
pub ui_drag: UiDrag, pub ui_drag: UiDrag,
pub xwayland: Option<Xwayland>, pub xwayland: Option<Xwayland>,
pub color_management: Option<ColorManagement>,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -8,6 +8,7 @@ use {
pub mod action; pub mod action;
mod color; mod color;
pub mod color_management;
pub mod config; pub mod config;
mod connector; mod connector;
mod connector_match; mod connector_match;

View file

@ -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<bool>,
}
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<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (enabled,) = ext.extract((opt(bol("enabled")),))?;
Ok(ColorManagement {
enabled: enabled.despan(),
})
}
}

View file

@ -7,6 +7,7 @@ use {
parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{ parsers::{
action::ActionParser, action::ActionParser,
color_management::ColorManagementParser,
connector::ConnectorsParser, connector::ConnectorsParser,
drm_device::DrmDevicesParser, drm_device::DrmDevicesParser,
drm_device_match::DrmDeviceMatchParser, drm_device_match::DrmDeviceMatchParser,
@ -117,6 +118,7 @@ impl Parser for ConfigParser<'_> {
ui_drag_val, ui_drag_val,
xwayland_val, xwayland_val,
), ),
(color_management_val,),
) = ext.extract(( ) = ext.extract((
( (
opt(val("keymap")), opt(val("keymap")),
@ -154,6 +156,7 @@ impl Parser for ConfigParser<'_> {
opt(val("ui-drag")), opt(val("ui-drag")),
opt(val("xwayland")), opt(val("xwayland")),
), ),
(opt(val("color-management")),),
))?; ))?;
let mut keymap = None; let mut keymap = None;
if let Some(value) = keymap_val { 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 { Ok(Config {
keymap, keymap,
repeat_rate, repeat_rate,
@ -396,6 +411,7 @@ impl Parser for ConfigParser<'_> {
libei, libei,
ui_drag, ui_drag,
xwayland, xwayland,
color_management,
}) })
} }
} }

View file

@ -23,9 +23,9 @@ use {
is_reload, is_reload,
keyboard::{Keymap, ModifiedKeySym}, keyboard::{Keymap, ModifiedKeySym},
logging::set_log_level, logging::set_log_level,
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, on_devices_enumerated, on_idle, quit, reload, set_color_management_enabled,
set_explicit_sync_enabled, set_idle, set_idle_grace_period, set_ui_drag_enabled, set_default_workspace_capture, set_explicit_sync_enabled, set_idle, set_idle_grace_period,
set_ui_drag_threshold, set_ui_drag_enabled, set_ui_drag_threshold,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command}, status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
switch_to_vt, switch_to_vt,
theme::{reset_colors, reset_font, reset_sizes, set_font}, theme::{reset_colors, reset_font, reset_sizes, set_font},
@ -1078,6 +1078,11 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
set_x_scaling_mode(mode); 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 { fn create_command(exec: &Exec) -> Command {

View file

@ -427,6 +427,17 @@
"type": "string", "type": "string",
"description": "A color.\n\nThe format should be one of the following:\n\n- `#rgb`\n- `#rrggbb`\n- `#rgba`\n- `#rrggbba`\n" "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": { "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", "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", "type": "object",
@ -597,6 +608,10 @@
"xwayland": { "xwayland": {
"description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n", "description": "Configures the Xwayland settings.\n\n- Example:\n\n ```toml\n xwayland = { scaling-mode = \"downscaled\" }\n ```\n",
"$ref": "#/$defs/Xwayland" "$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": [] "required": []

View file

@ -590,6 +590,33 @@ The format should be one of the following:
Values of this type should be strings. Values of this type should be strings.
<a name="types-ColorManagement"></a>
### `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.
<a name="types-ComplexShortcut"></a> <a name="types-ComplexShortcut"></a>
### `ComplexShortcut` ### `ComplexShortcut`
@ -1178,6 +1205,19 @@ The table has the following fields:
The value of this field should be a [Xwayland](#types-Xwayland). 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).
<a name="types-Connector"></a> <a name="types-Connector"></a>
### `Connector` ### `Connector`

View file

@ -2286,6 +2286,18 @@ Config:
```toml ```toml
xwayland = { scaling-mode = "downscaled" } xwayland = { scaling-mode = "downscaled" }
``` ```
color-management:
ref: ColorManagement
required: false
description: |
Configures the color-management settings.
- Example:
```toml
[color-management]
enabled = true
```
Idle: Idle:
@ -2727,3 +2739,26 @@ XScalingMode:
Additionally, this mode requires the X window to scale its contents itself. In the 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 example above, you might achieve this by setting the environment variable
`GDK_SCALE=2`. `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

View file

@ -0,0 +1,19 @@
request destroy {
}
request get {
}
request set_enabled {
enabled: u32,
}
event enabled {
enabled: u32,
}
event available {
available: u32,
}

View file

@ -101,6 +101,10 @@ request get_toplevel (since = 12) {
toplevel_id: str, toplevel_id: str,
} }
request get_color_management (since = 14) {
id: id(jay_color_management),
}
# events # events
event client_id { event client_id {

View file

@ -0,0 +1,9 @@
request destroy {
}
event image_description_changed {
}
request get_image_description {
image_description: id(wp_image_description_v1),
}

View file

@ -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),
}

View file

@ -0,0 +1,10 @@
request destroy {
}
request set_image_description {
image_description: id(wp_image_description_v1),
render_intent: u32,
}
request unset_image_description {
}

View file

@ -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 {
}

View file

@ -0,0 +1,9 @@
request create {
image_description: id(wp_image_description_v1),
}
request set_icc_file {
icc_profile: fd,
offset: u32,
length: u32,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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),
}