use std::ffi::c_void;
use std::sync::{Arc, Mutex};
use damascene_core::color::{
ColorFeature, CompositorColorTargets, HostColorCapabilities, Primaries as APrimaries,
RenderIntent as ARenderIntent, TransferFunction as ATransferFunction,
};
use wayland_backend::client::{Backend, ObjectId};
use wayland_client::globals::{GlobalListContents, registry_queue_init};
use wayland_client::protocol::{wl_registry::WlRegistry, wl_surface::WlSurface};
use wayland_client::{Connection, Dispatch, EventQueue, Proxy, QueueHandle};
use wayland_protocols::wp::color_management::v1::client::{
wp_color_management_surface_feedback_v1::WpColorManagementSurfaceFeedbackV1,
wp_color_manager_v1::{
self, Feature as WpFeature, Primaries as WpPrimaries, RenderIntent,
TransferFunction as WpTransferFunction, WpColorManagerV1,
},
wp_image_description_info_v1::{self, WpImageDescriptionInfoV1},
wp_image_description_v1::{self, WpImageDescriptionV1},
};
pub struct WaylandColorManager {
capabilities: HostColorCapabilities,
preferred_targets: CompositorColorTargets,
}
impl WaylandColorManager {
pub unsafe fn try_new(display_ptr: *mut c_void, surface_ptr: *mut c_void) -> Option<Self> {
if display_ptr.is_null() || surface_ptr.is_null() {
return None;
}
let backend = unsafe {
Backend::from_foreign_display(display_ptr as *mut wayland_sys::client::wl_display)
};
let connection = Connection::from_backend(backend);
let (globals, mut event_queue) = registry_queue_init::<State>(&connection).ok()?;
let qh = event_queue.handle();
if !globals.contents().with_list(|list| {
list.iter()
.any(|g| g.interface == WpColorManagerV1::interface().name)
}) {
return None;
}
let color_manager: WpColorManagerV1 = globals
.bind::<WpColorManagerV1, _, _>(&qh, 1..=2, ())
.ok()?;
let mut state = State::default();
event_queue.roundtrip(&mut state).ok()?;
let capabilities = state.collected_capabilities();
let surface_view = unsafe { view_foreign_surface(&connection, surface_ptr) }?;
let preferred_targets = read_preferred_targets(
&color_manager,
&surface_view,
&qh,
&mut event_queue,
&mut state,
capabilities.parametric_creator(),
);
Some(Self {
capabilities,
preferred_targets,
})
}
pub fn capabilities(&self) -> HostColorCapabilities {
self.capabilities.clone()
}
pub fn preferred_targets(&self) -> CompositorColorTargets {
self.preferred_targets.clone()
}
}
fn read_preferred_targets(
color_manager: &WpColorManagerV1,
surface_view: &WlSurface,
qh: &QueueHandle<State>,
event_queue: &mut EventQueue<State>,
state: &mut State,
parametric: bool,
) -> CompositorColorTargets {
let feedback: WpColorManagementSurfaceFeedbackV1 =
color_manager.get_surface_feedback(surface_view, qh, ());
let pending = Arc::new(PendingDescription::default());
state.pending = Some(Arc::clone(&pending));
let desc: WpImageDescriptionV1 = if parametric {
feedback.get_preferred_parametric(qh, ())
} else {
feedback.get_preferred(qh, ())
};
while pending.lock().is_none() {
if event_queue.roundtrip(state).is_err() {
state.pending = None;
feedback.destroy();
return CompositorColorTargets::default();
}
}
let resolution = pending.lock().take();
state.pending = None;
let targets = match resolution {
Some(DescriptionResolution::Ready) => {
state.info = CompositorColorTargets::default();
state.info_done = false;
let _info: WpImageDescriptionInfoV1 = desc.get_information(qh, ());
while !state.info_done {
if event_queue.roundtrip(state).is_err() {
break;
}
}
std::mem::take(&mut state.info)
}
_ => CompositorColorTargets::default(),
};
desc.destroy();
feedback.destroy();
targets
}
#[derive(Default)]
struct State {
primaries: Vec<APrimaries>,
transfer_functions: Vec<ATransferFunction>,
features: Vec<ColorFeature>,
render_intents: Vec<ARenderIntent>,
pending: Option<Arc<PendingDescription>>,
info: CompositorColorTargets,
info_done: bool,
}
impl State {
fn collected_capabilities(&self) -> HostColorCapabilities {
HostColorCapabilities {
primaries: self.primaries.clone(),
transfer_functions: self.transfer_functions.clone(),
features: self.features.clone(),
render_intents: self.render_intents.clone(),
}
}
}
#[derive(Default)]
struct PendingDescription(Mutex<Option<DescriptionResolution>>);
impl PendingDescription {
fn lock(&self) -> std::sync::MutexGuard<'_, Option<DescriptionResolution>> {
self.0.lock().expect("description-pending mutex poisoned")
}
}
enum DescriptionResolution {
Ready,
Failed,
}
impl Dispatch<WlRegistry, GlobalListContents> for State {
fn event(
_: &mut Self,
_: &WlRegistry,
_: <WlRegistry as Proxy>::Event,
_: &GlobalListContents,
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<WpColorManagerV1, ()> for State {
fn event(
state: &mut Self,
_: &WpColorManagerV1,
event: <WpColorManagerV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
use wayland_client::WEnum;
use wp_color_manager_v1::Event;
match event {
Event::SupportedPrimariesNamed {
primaries: WEnum::Value(p),
} => {
if let Some(a) = primaries_from_wp(p) {
state.primaries.push(a);
}
}
Event::SupportedTfNamed {
tf: WEnum::Value(tf),
} => {
if let Some(a) = transfer_from_wp(tf) {
state.transfer_functions.push(a);
}
}
Event::SupportedFeature {
feature: WEnum::Value(f),
} => {
if let Some(cf) = feature_from_wp(f) {
state.features.push(cf);
}
}
Event::SupportedIntent {
render_intent: WEnum::Value(i),
} => {
if let Some(ai) = intent_from_wp(i) {
state.render_intents.push(ai);
}
}
Event::Done => {
}
_ => {}
}
}
}
impl Dispatch<WpImageDescriptionV1, ()> for State {
fn event(
state: &mut Self,
_: &WpImageDescriptionV1,
event: <WpImageDescriptionV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
use wp_image_description_v1::Event;
let Some(slot) = state.pending.as_ref() else {
return;
};
match event {
Event::Ready { .. } | Event::Ready2 { .. } => {
let mut guard = slot.lock();
if guard.is_none() {
*guard = Some(DescriptionResolution::Ready);
}
}
Event::Failed { .. } => {
let mut guard = slot.lock();
if guard.is_none() {
*guard = Some(DescriptionResolution::Failed);
}
}
_ => {}
}
}
}
impl Dispatch<WpColorManagementSurfaceFeedbackV1, ()> for State {
fn event(
_: &mut Self,
_: &WpColorManagementSurfaceFeedbackV1,
_: <WpColorManagementSurfaceFeedbackV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<WpImageDescriptionInfoV1, ()> for State {
fn event(
state: &mut Self,
_: &WpImageDescriptionInfoV1,
event: <WpImageDescriptionInfoV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
use wayland_client::WEnum;
use wp_image_description_info_v1::Event;
match event {
Event::Luminances {
min_lum,
max_lum,
reference_lum,
} => {
state.info.min_luminance_nits = Some(min_lum as f32 / 10000.0);
state.info.max_luminance_nits = Some(max_lum as f32);
state.info.reference_luminance_nits = Some(reference_lum as f32);
}
Event::TargetLuminance { min_lum, max_lum } => {
state.info.target_min_luminance_nits = Some(min_lum as f32 / 10000.0);
state.info.target_max_luminance_nits = Some(max_lum as f32);
}
Event::TargetMaxCll { max_cll } => {
state.info.max_content_light_level_nits = Some(max_cll as f32);
}
Event::TargetMaxFall { max_fall } => {
state.info.max_frame_average_light_level_nits = Some(max_fall as f32);
}
Event::TfNamed {
tf: WEnum::Value(tf),
} => {
state.info.preferred_transfer = transfer_from_wp(tf);
}
Event::PrimariesNamed {
primaries: WEnum::Value(p),
} => {
state.info.preferred_primaries = primaries_from_wp(p);
}
Event::IccFile { .. } => {
state.info.preferred_is_icc = true;
}
Event::Done => {
state.info_done = true;
}
_ => {}
}
}
}
fn feature_from_wp(f: WpFeature) -> Option<ColorFeature> {
Some(match f {
WpFeature::IccV2V4 => ColorFeature::IccV2V4,
WpFeature::Parametric => ColorFeature::Parametric,
WpFeature::SetPrimaries => ColorFeature::SetPrimaries,
WpFeature::SetTfPower => ColorFeature::SetTfPower,
WpFeature::SetLuminances => ColorFeature::SetLuminances,
WpFeature::SetMasteringDisplayPrimaries => ColorFeature::SetMasteringDisplayPrimaries,
WpFeature::ExtendedTargetVolume => ColorFeature::ExtendedTargetVolume,
WpFeature::WindowsScrgb => ColorFeature::WindowsScrgb,
_ => return None,
})
}
fn intent_from_wp(i: RenderIntent) -> Option<ARenderIntent> {
Some(match i {
RenderIntent::Perceptual => ARenderIntent::Perceptual,
RenderIntent::Relative => ARenderIntent::Relative,
RenderIntent::Saturation => ARenderIntent::Saturation,
RenderIntent::Absolute => ARenderIntent::Absolute,
RenderIntent::RelativeBpc => ARenderIntent::RelativeBpc,
RenderIntent::AbsoluteNoAdaptation => ARenderIntent::AbsoluteNoAdaptation,
_ => return None,
})
}
fn primaries_from_wp(p: WpPrimaries) -> Option<APrimaries> {
Some(match p {
WpPrimaries::Srgb => APrimaries::Srgb,
WpPrimaries::Bt2020 => APrimaries::Bt2020,
WpPrimaries::DisplayP3 => APrimaries::DisplayP3,
WpPrimaries::AdobeRgb => APrimaries::AdobeRgb,
_ => return None,
})
}
fn transfer_from_wp(tf: WpTransferFunction) -> Option<ATransferFunction> {
use ATransferFunction::*;
Some(match tf {
WpTransferFunction::Bt1886 => Bt1886,
WpTransferFunction::Gamma22 => Srgb, WpTransferFunction::ExtLinear => Linear,
WpTransferFunction::St2084Pq => Pq,
WpTransferFunction::Hlg => Hlg,
WpTransferFunction::Srgb => Srgb,
_ => return None,
})
}
unsafe fn view_foreign_surface(
connection: &Connection,
surface_ptr: *mut c_void,
) -> Option<WlSurface> {
use wayland_sys::client::wl_proxy;
let object_id =
unsafe { ObjectId::from_ptr(WlSurface::interface(), surface_ptr as *mut wl_proxy) }.ok()?;
WlSurface::from_id(connection, object_id).ok()
}