diff --git a/docs/features.md b/docs/features.md index a4fe2966..75cdb96a 100644 --- a/docs/features.md +++ b/docs/features.md @@ -196,6 +196,7 @@ Jay supports the following wayland protocols: | xdg_wm_dialog_v1 | 1 | | | zwlr_data_control_manager_v1 | 2 | Yes | | zwlr_foreign_toplevel_manager_v1 | 3 | Yes | +| zwlr_gamma_control_manager_v1 | 1 | Yes | | zwlr_layer_shell_v1 | 5 | No[^lsaccess] | | zwlr_output_manager_v1 | 4 | Yes | | zwlr_screencopy_manager_v1 | 3 | Yes | diff --git a/jay-config/src/client.rs b/jay-config/src/client.rs index 1bc84eb0..0048a465 100644 --- a/jay-config/src/client.rs +++ b/jay-config/src/client.rs @@ -225,5 +225,7 @@ bitflags! { /// Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` /// globals. pub const CC_HEAD_MANAGER = 1 << 13, + /// Grants access to the `zwlr_gamma_control_manager_v1` global. + pub const CC_GAMMA_CONTROL_MANAGER = 1 << 14, } } diff --git a/src/backend.rs b/src/backend.rs index 203796ad..fd733270 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -37,7 +37,7 @@ use { hash::Hash, rc::Rc, }, - uapi::{OwnedFd, c}, + uapi::{OwnedFd, Packed, Pod, c}, }; pub mod transaction; @@ -165,6 +165,9 @@ pub trait Connector: Any { self.kernel_id(), )) } + fn gamma_lut_size(&self) -> Option { + None + } } #[derive(Debug)] @@ -611,6 +614,42 @@ impl BackendColorSpace { } } +// kernel: struct drm_color_lut +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] +#[repr(C)] +pub struct BackendGammaLutElement { + pub red: u16, + pub green: u16, + pub blue: u16, + pub reserved: u16, +} + +unsafe impl Pod for BackendGammaLutElement {} +unsafe impl Packed for BackendGammaLutElement {} + +#[derive(Debug, Eq)] +pub struct BackendGammaLut { + id: [u8; 32], + pub gamma_lut: Vec, +} + +impl BackendGammaLut { + pub fn new(mut gamma_lut: Vec) -> Self { + for element in &mut gamma_lut { + element.reserved = 0; + } + let gamma_lut_bytes = uapi::as_bytes(&gamma_lut as &[_]); + let id = *blake3::hash(gamma_lut_bytes).as_bytes(); + Self { id, gamma_lut } + } +} + +impl PartialEq for BackendGammaLut { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + linear_ids!( BackendConnectorStateSerials, BackendConnectorStateSerial, @@ -629,4 +668,5 @@ pub struct BackendConnectorState { pub format: &'static Format, pub color_space: BackendColorSpace, pub eotf: BackendEotfs, + pub gamma_lut: Option>, } diff --git a/src/backend/transaction.rs b/src/backend/transaction.rs index 1020bb9a..790f6f7b 100644 --- a/src/backend/transaction.rs +++ b/src/backend/transaction.rs @@ -124,6 +124,8 @@ pub enum BackendConnectorTransactionError { AtomicTestFailed(#[source] DrmError), #[error("Commit failed")] AtomicCommitFailed(#[source] DrmError), + #[error("Could not create a gamma lut blob")] + CreateGammaLutBlob(#[source] DrmError), } pub trait BackendConnectorTransaction { diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs index f4721a36..79e6d00e 100644 --- a/src/backends/metal/transaction.rs +++ b/src/backends/metal/transaction.rs @@ -2,7 +2,8 @@ use { crate::{ allocator::BufferObject, backend::{ - BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorEvent, + BackendColorSpace, BackendConnectorState, BackendEotfs, BackendGammaLut, Connector, + ConnectorEvent, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendPreparedConnectorTransaction, @@ -57,6 +58,9 @@ pub struct DrmCrtcState { pub mode_blob: Option>, pub vrr_enabled: bool, pub assigned_connector: DrmConnector, + pub gamma_lut: Option>, + pub gamma_lut_blob_id: DrmBlob, + pub gamma_lut_blob: Option>, } #[derive(Default, Clone, Debug)] @@ -420,6 +424,22 @@ impl MetalDeviceTransaction { crtc.new.mode_blob = Some(Rc::new(blob)); mode.clone() }; + if crtc.new.gamma_lut != state.gamma_lut { + if let Some(gamma_lut) = &state.gamma_lut { + let blob = slf + .dev + .dev + .master + .create_blob(&gamma_lut.gamma_lut as &[_]) + .map_err(BackendConnectorTransactionError::CreateGammaLutBlob)?; + crtc.new.gamma_lut_blob_id = blob.id(); + crtc.new.gamma_lut_blob = Some(Rc::new(blob)); + } else { + crtc.new.gamma_lut_blob_id = DrmBlob::NONE; + crtc.new.gamma_lut_blob = None; + } + crtc.new.gamma_lut = state.gamma_lut.clone(); + } for plane_id in [&mut crtc_planes.primary, &mut crtc_planes.cursor] { if plane_id.is_none() { continue; @@ -841,6 +861,12 @@ impl MetalDeviceTransactionWithDrmState { log_change!(o, n, mode_blob_id); c.change(crtc.obj.mode_id, n.mode_blob_id); } + if let Some(gamma_lut) = crtc.obj.gamma_lut + && n.gamma_lut_blob_id != o.gamma_lut_blob_id + { + log_change!(o, n, gamma_lut_blob_id); + c.change(gamma_lut, n.gamma_lut_blob_id); + } reset_default_properties!( c, &*crtc.obj.untyped_properties.borrow(), diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index d0b0b902..8509b0bf 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -3,10 +3,10 @@ use { async_engine::{Phase, SpawnedFuture}, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLease, - BackendDrmLessee, BackendEotfs, BackendEvent, BackendLuminance, CONCAP_CONNECTOR, - CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent, - ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, - Mode, MonitorInfo, + BackendDrmLessee, BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement, + BackendLuminance, CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, + Connector, ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, + HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -905,6 +905,10 @@ impl Connector for MetalConnector { ) -> Result, BackendConnectorTransactionError> { self.create_transaction().map(|v| Box::new(v) as _) } + + fn gamma_lut_size(&self) -> Option { + self.crtc.get().and_then(|crtc| crtc.gamma_lut_size) + } } pub struct MetalCrtc { @@ -925,6 +929,8 @@ pub struct MetalCrtc { pub mode_id: DrmProperty, pub vrr_enabled: DrmProperty, pub out_fence_ptr: DrmProperty, + pub gamma_lut: Option, + pub gamma_lut_size: Option, pub drm_state: RefCell, pub sequence: Cell, @@ -1327,6 +1333,7 @@ fn create_connector_display_data( format: XRGB8888, color_space: Default::default(), eotf: Default::default(), + gamma_lut: Default::default(), }), }); dev.backend @@ -1499,7 +1506,6 @@ fn create_crtc( ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), ("CTM", DefaultValue::Fixed(0)), ("DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("GAMMA_LUT", DefaultValue::Fixed(0)), ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), ], ); @@ -1507,6 +1513,14 @@ fn create_crtc( let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); let out_fence_ptr = props.get("OUT_FENCE_PTR")?; + let gamma_lut = props + .get("GAMMA_LUT") + .ok() + .map(|v| v.map(|v| DrmBlob(v as u32))); + let mut gamma_lut_size = None; + if gamma_lut.is_some() { + gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); + } let mut mode = None; if mode_id.value.is_some() { match master.getblob::(mode_id.value) { @@ -1523,6 +1537,9 @@ fn create_crtc( mode_blob: None, vrr_enabled: vrr_enabled.value, assigned_connector: DrmConnector::NONE, + gamma_lut: None, + gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), + gamma_lut_blob: None, }; Ok(MetalCrtc { id: crtc, @@ -1539,6 +1556,8 @@ fn create_crtc( mode_id: mode_id.id, vrr_enabled: vrr_enabled.id, out_fence_ptr: out_fence_ptr.id, + gamma_lut: gamma_lut.map(|v| v.id), + gamma_lut_size, sequence: Cell::new(0), have_queued_sequence: Cell::new(false), needs_vblank_emulation: Cell::new(false), @@ -2187,6 +2206,25 @@ impl MetalCrtc { } } } + if let Some(gamma_lut) = self.gamma_lut { + let id = DrmBlob(get(props, gamma_lut)? as _); + let old = state.gamma_lut_blob_id; + state.gamma_lut_blob_id = id; + if old != id { + state.gamma_lut = None; + state.gamma_lut_blob = None; + if id.is_some() { + match master.getblob_vec::(id) { + Ok(b) => { + state.gamma_lut = Some(Rc::new(BackendGammaLut::new(b))); + } + Err(e) => { + log::error!("Could not fetch gamma_lut: {}", ErrorFmt(e)); + } + } + } + } + } Ok(()) } } diff --git a/src/backends/x.rs b/src/backends/x.rs index 382a12f1..866f8a08 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -493,6 +493,7 @@ impl XBackend { format: FORMAT, color_space: Default::default(), eotf: Default::default(), + gamma_lut: Default::default(), }; let output = Rc::new(XOutput { id: self.state.connector_ids.next(), diff --git a/src/client.rs b/src/client.rs index 75f1ea6e..afbe45b7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -65,6 +65,7 @@ bitflags! { CAP_WORKSPACE = 1 << 11, CAP_FOREIGN_TOPLEVEL_MANAGER = 1 << 12, CAP_HEAD_MANAGER = 1 << 13, + CAP_GAMMA_CONTROL_MANAGER = 1 << 14, } pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); diff --git a/src/clientmem.rs b/src/clientmem.rs index c01b26f2..8cb6a4fa 100644 --- a/src/clientmem.rs +++ b/src/clientmem.rs @@ -16,7 +16,7 @@ use { }, thiserror::Error, uapi::{ - OwnedFd, + OwnedFd, Pod, c::{self, raise}, ftruncate, }, @@ -30,6 +30,8 @@ pub enum ClientMemError { Sigbus, #[error("mmap failed")] MmapFailed(#[source] crate::utils::oserror::OsError), + #[error("Length was not a multiple of the data element size")] + InvalidLength, } pub struct ClientMem { @@ -182,13 +184,17 @@ impl ClientMemOffset { } } - pub fn read(&self, dst: &mut Vec) -> Result<(), ClientMemError> { + pub fn read(&self, dst: &mut Vec) -> Result<(), ClientMemError> { + if self.data.len().checked_rem(std::mem::size_of::()) != Some(0) { + return Err(ClientMemError::InvalidLength); + } self.access(|v| { - dst.reserve(v.len()); - let (_, unused) = dst.split_at_spare_mut_ext(); + let len_elements = v.len() / std::mem::size_of::(); + dst.reserve(len_elements); + let (_, unused) = dst.split_at_spare_mut_bytes_ext(); unused[..v.len()].copy_from_slice(uapi::as_maybe_uninit_bytes(v)); unsafe { - dst.set_len(dst.len() + v.len()); + dst.set_len(dst.len() + len_elements); } }) } diff --git a/src/compositor.rs b/src/compositor.rs index 6f05710a..0d2fc425 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -658,6 +658,7 @@ fn create_dummy_output(state: &Rc) { format: XRGB8888, color_space: Default::default(), eotf: Default::default(), + gamma_lut: Default::default(), }; let id = state.connector_ids.next(); let connector = Rc::new(DummyOutput { id }) as Rc; @@ -771,6 +772,7 @@ fn create_dummy_output(state: &Rc) { ext_workspace_groups: Default::default(), pinned: Default::default(), tearing: Default::default(), + active_zwlr_gamma_control: Default::default(), }); let dummy_workspace = WorkspaceNode::new(&dummy_output, "dummy", true); dummy_workspace.may_capture.set(false); diff --git a/src/globals.rs b/src/globals.rs index 8741895c..0a72aa48 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -63,6 +63,7 @@ use { xdg_wm_base::XdgWmBaseGlobal, xdg_wm_dialog_v1::XdgWmDialogV1Global, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1Global, + zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1Global, zwlr_layer_shell_v1::ZwlrLayerShellV1Global, zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1Global, zwp_idle_inhibit_manager_v1::ZwpIdleInhibitManagerV1Global, @@ -233,6 +234,7 @@ impl Globals { add_singleton!(JayHeadManagerV1Global); add_singleton!(WpPointerWarpV1Global); add_singleton!(JayPopupExtManagerV1Global); + add_singleton!(ZwlrGammaControlManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 5d142c48..31def4a6 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -86,6 +86,8 @@ pub mod xdg_wm_base; pub mod xdg_wm_dialog_v1; pub mod zwlr_foreign_toplevel_handle_v1; pub mod zwlr_foreign_toplevel_manager_v1; +pub mod zwlr_gamma_control_manager_v1; +pub mod zwlr_gamma_control_v1; pub mod zwlr_layer_shell_v1; pub mod zwlr_screencopy_frame_v1; pub mod zwlr_screencopy_manager_v1; diff --git a/src/ifs/zwlr_gamma_control_manager_v1.rs b/src/ifs/zwlr_gamma_control_manager_v1.rs new file mode 100644 index 00000000..966fd6ca --- /dev/null +++ b/src/ifs/zwlr_gamma_control_manager_v1.rs @@ -0,0 +1,115 @@ +use { + crate::{ + client::{CAP_GAMMA_CONTROL_MANAGER, Client, ClientCaps, ClientError}, + globals::{Global, GlobalName}, + ifs::zwlr_gamma_control_v1::*, + leaks::Tracker, + object::{Object, Version}, + wire::{ZwlrGammaControlManagerV1Id, zwlr_gamma_control_manager_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwlrGammaControlManagerV1Global { + name: GlobalName, +} + +impl ZwlrGammaControlManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwlrGammaControlManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ZwlrGammaControlManagerV1Error> { + let obj = Rc::new(ZwlrGammaControlManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + ZwlrGammaControlManagerV1Global, + ZwlrGammaControlManagerV1, + ZwlrGammaControlManagerV1Error +); + +simple_add_global!(ZwlrGammaControlManagerV1Global); + +impl Global for ZwlrGammaControlManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_GAMMA_CONTROL_MANAGER + } +} + +pub struct ZwlrGammaControlManagerV1 { + pub id: ZwlrGammaControlManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ZwlrGammaControlManagerV1RequestHandler for ZwlrGammaControlManagerV1 { + type Error = ZwlrGammaControlManagerV1Error; + + fn get_gamma_control(&self, req: GetGammaControl, slf: &Rc) -> Result<(), Self::Error> { + let output = self.client.lookup(req.output)?.global.clone(); + let p = Rc::new(ZwlrGammaControlV1::new(req.id, slf, output.clone())); + track!(self.client, p); + self.client.add_client_obj(&p)?; + let Some(size) = p.gamma_lut_size() else { + p.send_failed(); + return Ok(()); + }; + let Some(node) = output.node() else { + p.send_failed(); + return Ok(()); + }; + if node.active_zwlr_gamma_control.is_some() { + p.send_failed(); + return Ok(()); + } + p.send_gamma_size(size); + node.active_zwlr_gamma_control.set(Some(p)); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwlrGammaControlManagerV1; + version = self.version; +} + +impl Object for ZwlrGammaControlManagerV1 {} + +simple_add_obj!(ZwlrGammaControlManagerV1); + +#[derive(Debug, Error)] +pub enum ZwlrGammaControlManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwlrGammaControlManagerV1Error, ClientError); diff --git a/src/ifs/zwlr_gamma_control_v1.rs b/src/ifs/zwlr_gamma_control_v1.rs new file mode 100644 index 00000000..0e361702 --- /dev/null +++ b/src/ifs/zwlr_gamma_control_v1.rs @@ -0,0 +1,166 @@ +use { + crate::{ + backend::{BackendGammaLut, BackendGammaLutElement}, + client::{Client, ClientError, ClientId}, + clientmem::{ClientMem, ClientMemError}, + ifs::{ + wl_output::OutputGlobalOpt, zwlr_gamma_control_manager_v1::ZwlrGammaControlManagerV1, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ZwlrGammaControlV1Id, zwlr_gamma_control_v1::*}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwlrGammaControlV1 { + id: ZwlrGammaControlV1Id, + client: Rc, + version: Version, + output: Rc, + pub tracker: Tracker, +} + +impl ZwlrGammaControlV1 { + pub fn new( + id: ZwlrGammaControlV1Id, + manager: &Rc, + output: Rc, + ) -> Self { + Self { + id, + client: manager.client.clone(), + version: manager.version, + output, + tracker: Default::default(), + } + } + + pub fn id(&self) -> (ClientId, ZwlrGammaControlV1Id) { + (self.client.id, self.id) + } + + pub fn send_gamma_size(&self, size: u32) { + self.client.event(GammaSize { + self_id: self.id, + size, + }); + } + + pub fn send_failed(&self) { + self.client.event(Failed { self_id: self.id }); + } + + pub fn gamma_lut_size(&self) -> Option { + self.output + .node() + .and_then(|node| node.global.connector.connector.gamma_lut_size()) + } + + fn detach(&self) { + if let Some(node) = self.output.node() + && let Some(active_zwlr_gamma_control) = node.active_zwlr_gamma_control.get() + && active_zwlr_gamma_control.id() == self.id() + { + node.active_zwlr_gamma_control.set(None); + let _ = node.set_gamma_lut(None); + } + } +} + +// Wayland's LUT is ([red], [green], [blue]). DRM's LUT is [(red, green, blue, _)]. Both are u16. +fn wayland_gamma_lut_to_drm_gamma_lut(data: &[u16]) -> Vec { + let elem_count = data.len() / 3; + let (red, rest) = data.split_at(elem_count); + let (green, blue) = rest.split_at(elem_count); + red.iter() + .copied() + .zip(green.iter().copied()) + .zip(blue.iter().copied()) + .map(|((red, green), blue)| BackendGammaLutElement { + red, + green, + blue, + reserved: 0, + }) + .collect() +} + +impl ZwlrGammaControlV1RequestHandler for ZwlrGammaControlV1 { + type Error = ZwlrGammaControlV1Error; + + fn set_gamma(&self, req: SetGamma, _slf: &Rc) -> Result<(), Self::Error> { + let fail = || { + self.detach(); + self.send_failed(); + }; + + let Some(node) = self.output.node() else { + return Ok(()); + }; + + // if the active gamma control isn't us, that implies we are not valid, and have already + // sent a failed event + if node.active_zwlr_gamma_control.get().map(|v| v.id()) != Some(self.id()) { + return Ok(()); + } + + let Some(gamma_lut_size) = self.gamma_lut_size() else { + fail(); + return Ok(()); + }; + + // 3 color channels + let data_size = gamma_lut_size * 3; + + let mut gamma_lut = vec![]; + Rc::new(ClientMem::new_private( + &req.fd, + (2 * data_size) as _, + true, + Some(&self.client), + None, + )?) + .offset(0) + .read(&mut gamma_lut)?; + let gamma_lut = &gamma_lut[..data_size as _]; + + let gamma_lut = wayland_gamma_lut_to_drm_gamma_lut(gamma_lut); + let gamma_lut = Rc::new(BackendGammaLut::new(gamma_lut)); + if node.set_gamma_lut(Some(gamma_lut)).is_err() { + fail(); + return Ok(()); + } + + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwlrGammaControlV1; + version = self.version; +} + +impl Object for ZwlrGammaControlV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ZwlrGammaControlV1); + +#[derive(Debug, Error)] +pub enum ZwlrGammaControlV1Error { + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + CLientMemError(#[from] ClientMemError), +} +efrom!(ZwlrGammaControlV1Error, ClientError); diff --git a/src/it/test_backend.rs b/src/it/test_backend.rs index 9934fef4..a14527f9 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -153,6 +153,7 @@ impl TestBackend { format: XRGB8888, color_space: Default::default(), eotf: Default::default(), + gamma_lut: Default::default(), }, }; Self { diff --git a/src/it/tests/t0034_workspace_restoration.rs b/src/it/tests/t0034_workspace_restoration.rs index e7de4789..4203d1ee 100644 --- a/src/it/tests/t0034_workspace_restoration.rs +++ b/src/it/tests/t0034_workspace_restoration.rs @@ -66,6 +66,7 @@ async fn test(run: Rc) -> TestResult { format: XRGB8888, color_space: Default::default(), eotf: Default::default(), + gamma_lut: Default::default(), }, }; run.backend diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index e7ead248..fdb789a1 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -46,6 +46,7 @@ pub fn handle(state: &Rc, connector: &Rc) { format: XRGB8888, color_space: Default::default(), eotf: Default::default(), + gamma_lut: None, }; let id = connector.id(); let name = Rc::new(connector.kernel_id().to_string()); @@ -270,6 +271,7 @@ impl ConnectorHandler { ext_workspace_groups: Default::default(), pinned: Default::default(), tearing: Default::default(), + active_zwlr_gamma_control: Default::default(), }); on.update_visible(); on.update_rects(); @@ -421,6 +423,9 @@ impl ConnectorHandler { } self.state .remove_output_scale(on.global.persistent.scale.get()); + if let Some(zwlr_gamma_control) = on.active_zwlr_gamma_control.take() { + zwlr_gamma_control.send_failed(); + } on.clear(); let _ = self.state.remove_global(&global); let _ = self.state.remove_global(&tray); diff --git a/src/tree/output.rs b/src/tree/output.rs index ac3cc924..55707c92 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,8 +1,8 @@ use { crate::{ backend::{ - BackendColorSpace, BackendConnectorState, BackendEotfs, ButtonState, HardwareCursor, - Mode, + BackendColorSpace, BackendConnectorState, BackendEotfs, BackendGammaLut, ButtonState, + HardwareCursor, Mode, transaction::BackendConnectorTransactionError, }, client::ClientId, cmm::cmm_description::ColorDescription, @@ -33,6 +33,7 @@ use { }, wp_content_type_v1::ContentType, wp_presentation_feedback::KIND_VSYNC, + zwlr_gamma_control_v1::ZwlrGammaControlV1, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, }, @@ -127,6 +128,7 @@ pub struct OutputNode { pub ext_workspace_groups: CopyHashMap>, pub pinned: LinkedList>, pub tearing: Cell, + pub active_zwlr_gamma_control: CloneCell>>, } #[derive(Copy, Clone, Debug, PartialEq)] @@ -1517,6 +1519,18 @@ impl OutputNode { } } } + + pub fn set_gamma_lut( + &self, + gamma_lut: Option>, + ) -> Result<(), BackendConnectorTransactionError> { + self.global + .connector + .modify_state(&self.state, |s| s.gamma_lut = gamma_lut) + .inspect_err(|e| { + log::error!("Could not set gamma_lut: {}", ErrorFmt(e)); + }) + } } pub struct OutputTitle { diff --git a/src/video/drm.rs b/src/video/drm.rs index aacb6cbf..621f26af 100644 --- a/src/video/drm.rs +++ b/src/video/drm.rs @@ -380,7 +380,7 @@ impl DrmMaster { res } - pub fn create_blob(self: &Rc, t: &T) -> Result { + pub fn create_blob(self: &Rc, t: &T) -> Result { match mode_create_blob(self.raw(), t) { Ok(b) => Ok(PropBlob { master: self.clone(), diff --git a/src/video/drm/sys.rs b/src/video/drm/sys.rs index 6892b7c7..8cd29a95 100644 --- a/src/video/drm/sys.rs +++ b/src/video/drm/sys.rs @@ -917,9 +917,9 @@ struct drm_mode_create_blob { const DRM_IOCTL_MODE_CREATEPROPBLOB: u64 = drm_iowr::(0xbd); -pub fn mode_create_blob(fd: c::c_int, t: &T) -> Result { +pub fn mode_create_blob(fd: c::c_int, t: &T) -> Result { let mut res = drm_mode_create_blob { - data: t as *const T as _, + data: t as *const T as *const () as _, length: size_of_val(t) as _, blob_id: 0, }; diff --git a/toml-config/src/config/parsers/capabilities.rs b/toml-config/src/config/parsers/capabilities.rs index cb690844..b17c641c 100644 --- a/toml-config/src/config/parsers/capabilities.rs +++ b/toml-config/src/config/parsers/capabilities.rs @@ -8,9 +8,9 @@ use { }, jay_config::client::{ CC_DATA_CONTROL, CC_DRM_LEASE, CC_FOREIGN_TOPLEVEL_LIST, CC_FOREIGN_TOPLEVEL_MANAGER, - CC_HEAD_MANAGER, CC_IDLE_NOTIFIER, CC_INPUT_METHOD, CC_LAYER_SHELL, CC_SCREENCOPY, - CC_SEAT_MANAGER, CC_SESSION_LOCK, CC_VIRTUAL_KEYBOARD, CC_WORKSPACE_MANAGER, - ClientCapabilities, + CC_GAMMA_CONTROL_MANAGER, CC_HEAD_MANAGER, CC_IDLE_NOTIFIER, CC_INPUT_METHOD, + CC_LAYER_SHELL, CC_SCREENCOPY, CC_SEAT_MANAGER, CC_SESSION_LOCK, CC_VIRTUAL_KEYBOARD, + CC_WORKSPACE_MANAGER, ClientCapabilities, }, thiserror::Error, }; @@ -47,6 +47,7 @@ impl Parser for CapabilitiesParser { "workspace-manager" => CC_WORKSPACE_MANAGER, "foreign-toplevel-manager" => CC_FOREIGN_TOPLEVEL_MANAGER, "head-manager" => CC_HEAD_MANAGER, + "gamma-control-manager" => CC_GAMMA_CONTROL_MANAGER, _ => { return Err( CapabilitiesParserError::UnknownCapability(string.to_owned()).spanned(span), diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index ee5b722c..29052508 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -641,7 +641,8 @@ "input-method", "workspace-manager", "foreign-toplevel-manager", - "head-manager" + "head-manager", + "gamma-control-manager" ] }, { diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index acc2c342..58d2beb1 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1013,6 +1013,10 @@ The string should have one of the following values: Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` globals. +- `gamma-control-manager`: + + Grants access to the `zwlr_gamma_control_manager_v1` global. + #### An array diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index b386df63..de751055 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -4360,6 +4360,9 @@ ClientCapabilities: description: | Grants access to the `jay_head_manager_v1` and `zwlr_output_manager_v1` globals. + - value: gamma-control-manager + description: | + Grants access to the `zwlr_gamma_control_manager_v1` global. - kind: array description: An array of masks that are OR'd. items: diff --git a/wire/zwlr_gamma_control_manager_v1.txt b/wire/zwlr_gamma_control_manager_v1.txt new file mode 100644 index 00000000..6b32df85 --- /dev/null +++ b/wire/zwlr_gamma_control_manager_v1.txt @@ -0,0 +1,7 @@ +request get_gamma_control { + id: id(zwlr_gamma_control_v1) (new), + output: id(wl_output), +} + +request destroy (destructor) { +} diff --git a/wire/zwlr_gamma_control_v1.txt b/wire/zwlr_gamma_control_v1.txt new file mode 100644 index 00000000..44938a07 --- /dev/null +++ b/wire/zwlr_gamma_control_v1.txt @@ -0,0 +1,13 @@ +event gamma_size { + size: u32, +} + +request set_gamma { + fd: fd, +} + +event failed { +} + +request destroy (destructor) { +}