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 cf5490ce..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,13 +614,49 @@ 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, u64 ); -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct BackendConnectorState { pub serial: BackendConnectorStateSerial, pub enabled: bool, @@ -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 b5ac531c..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 { @@ -193,7 +195,7 @@ impl ConnectorTransaction { Entry::Occupied(v) => v.into_mut(), Entry::Vacant(v) => v.insert(connector.create_transaction()?), }; - tran.add(connector, state)?; + tran.add(connector, state.clone())?; self.common.states.insert(connector.id(), state); Ok(()) } diff --git a/src/backends/metal/transaction.rs b/src/backends/metal/transaction.rs index 78fb7237..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)] @@ -189,7 +193,7 @@ impl MetalDrmDeviceData { ConnectorConfig { obj: connector.clone(), new: dd.drm_state.clone(), - state: *dd.persistent.state.borrow(), + state: dd.persistent.state.borrow().clone(), requested: false, changed: Default::default(), }, @@ -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 20dde7b9..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, @@ -637,7 +637,7 @@ impl MetalConnector { | FrontState::Connected { non_desktop: true } => return, FrontState::Connected { non_desktop: false } => {} } - let mut state = *self.display.borrow().persistent.state.borrow(); + let mut state = self.display.borrow().persistent.state.borrow().clone(); state.serial = self.state.backend_connector_state_serials.next(); self.send_event(ConnectorEvent::State(state)); } @@ -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), @@ -1928,7 +1947,7 @@ impl MetalBackend { if dd.supports_bt2020 { color_spaces.push(BackendColorSpace::Bt2020); } - let mut state = *dd.persistent.state.borrow(); + let mut state = dd.persistent.state.borrow().clone(); state.serial = self.state.backend_connector_state_serials.next(); connector.send_event(ConnectorEvent::Connected(MonitorInfo { modes, @@ -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(()) } } @@ -2573,7 +2611,7 @@ impl MetalBackend { let mut tran = dev.create_transaction(); for c in dev.connectors.lock().values() { let dd = &*c.display.borrow(); - let mut state = *dd.persistent.state.borrow(); + let mut state = dd.persistent.state.borrow().clone(); let mut changed_any = false; if disable_non_default_format && state.format != XRGB8888 { state.format = XRGB8888; diff --git a/src/backends/x.rs b/src/backends/x.rs index dcb3ea3e..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(), @@ -506,7 +507,7 @@ impl XBackend { next_image: Default::default(), cb: CloneCell::new(None), images, - state: Cell::new(state), + state: RefCell::new(state), }); { let class = "jay\0jay\0"; @@ -604,7 +605,7 @@ impl XBackend { color_spaces: vec![], primaries: Primaries::SRGB, luminance: None, - state: output.state.get(), + state: output.state.borrow().clone(), })); output.changed(); self.present(output).await; @@ -983,11 +984,11 @@ impl XBackend { old.tex.set(new.tex.get()); old.pixmap.set(new.pixmap.get()); } - let mut state = output.state.get(); + let mut state = output.state.borrow().clone(); state.serial = self.state.backend_connector_state_serials.next(); state.mode.width = width; state.mode.height = height; - output.state.set(state); + *output.state.borrow_mut() = state.clone(); output.events.push(ConnectorEvent::State(state)); output.changed(); } @@ -1057,7 +1058,7 @@ struct XOutput { next_image: NumCell, images: [XImage; 2], cb: CloneCell>>, - state: Cell, + state: RefCell, } struct XImage { @@ -1133,7 +1134,7 @@ struct XTransaction { impl XTransaction { fn send_state(&self) { for con in self.connectors.values() { - let mut state = con.state.get(); + let mut state = con.state.borrow().clone(); state.serial = con.backend.state.backend_connector_state_serials.next(); con.events.push(ConnectorEvent::State(state)); } 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 48191a77..0d2fc425 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -86,7 +86,15 @@ use { video::{GfxApi, Transform}, workspace::WorkspaceDisplayOrder, }, - std::{cell::Cell, env, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}, + std::{ + cell::{Cell, RefCell}, + env, + future::Future, + ops::Deref, + rc::Rc, + sync::Arc, + time::Duration, + }, thiserror::Error, uapi::c, }; @@ -650,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; @@ -694,7 +703,7 @@ fn create_dummy_output(state: &Rc) { damage: Default::default(), needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), - state: Cell::new(backend_state), + state: RefCell::new(backend_state), head_managers: HeadManagers::new(head_name, head_state), wlr_output_heads: Default::default(), }); @@ -763,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/head_management/jay_head_manager_session_v1.rs b/src/ifs/head_management/jay_head_manager_session_v1.rs index 9efe6ede..462e8566 100644 --- a/src/ifs/head_management/jay_head_manager_session_v1.rs +++ b/src/ifs/head_management/jay_head_manager_session_v1.rs @@ -258,8 +258,8 @@ impl JayHeadManagerSessionV1 { let Some(connector) = self.client.state.connectors.get(&head.common.id) else { return Err(HeadTransactionError::HeadRemoved(head.common.id)); }; - let old = connector.state.get(); - let mut new = old; + let old = connector.state.borrow().clone(); + let mut new = old.clone(); new.enabled = desired.connector_enabled; new.mode = desired.mode; new.non_desktop_override = desired.override_non_desktop; diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 8ba940ac..64915be9 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -72,7 +72,7 @@ impl JayRandr { } fn send_connector(&self, data: &ConnectorData) { - let state = data.state.get(); + let state_enabled = data.state.borrow().enabled; self.client.event(Connector { self_id: self.id, id: data.connector.id().raw() as _, @@ -81,7 +81,7 @@ impl JayRandr { .as_ref() .map(|d| d.dev.id().raw() as _) .unwrap_or_default(), - enabled: state.enabled as _, + enabled: state_enabled as _, name: &data.name, }); let Some(output) = self.client.state.outputs.get(&data.connector.id()) else { diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index d3680364..f6e2bbe2 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -206,7 +206,7 @@ impl WlOutputGlobal { ) -> Self { let (x, y) = persistent_state.pos.get(); let scale = persistent_state.scale.get(); - let connector_state = connector.state.get(); + let connector_state = connector.state.borrow(); let (width, height) = calculate_logical_size( (connector_state.mode.width, connector_state.mode.height), persistent_state.transform.get(), diff --git a/src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs b/src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs index a1ad5217..bc6434ff 100644 --- a/src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs +++ b/src/ifs/wlr_output_manager/zwlr_output_configuration_v1.rs @@ -73,7 +73,7 @@ impl ZwlrOutputConfigurationV1 { } let mut tran = ConnectorTransaction::new(&self.client.state); for output in self.client.state.outputs.lock().values() { - let mut state = output.connector.state.get(); + let mut state = output.connector.state.borrow().clone(); match self.enabled_outputs.get(&output.connector.id) { None => { if self.configured_outputs.not_contains(&output.connector.id) { diff --git a/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs b/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs index 8692a63b..9f73ac7c 100644 --- a/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs +++ b/src/ifs/wlr_output_manager/zwlr_output_manager_v1.rs @@ -131,7 +131,7 @@ impl ZwlrOutputManagerV1 { } }; let mi = &output.monitor_info; - let state = output.connector.state.get(); + let state_mode = output.connector.state.borrow().mode; let head_id = self.client.state.wlr_output_managers.head_ids.next(); let mut modes_list = vec![]; let mut modes = AHashMap::new(); @@ -140,7 +140,7 @@ impl ZwlrOutputManagerV1 { if modes.contains_key(mode) { continue; } - let current = !have_current && *mode == state.mode; + let current = !have_current && *mode == state_mode; if current { have_current = true; } 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/state.rs b/src/state.rs index 790f9a9a..667d8fd9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -420,7 +420,7 @@ pub struct ConnectorData { pub damage: RefCell>, pub needs_vblank_emulation: Cell, pub damage_intersect: Cell, - pub state: Cell, + pub state: RefCell, pub head_managers: HeadManagers, pub wlr_output_heads: CopyHashMap>, } @@ -456,26 +456,26 @@ impl ConnectorData { state: &State, f: impl FnOnce(&mut BackendConnectorState), ) -> Result<(), BackendConnectorTransactionError> { - let old = self.state.get(); - let mut s = old; + let old = self.state.borrow().clone(); + let mut s = old.clone(); f(&mut s); if old == s { return Ok(()); } s.serial = state.backend_connector_state_serials.next(); let mut tran = self.connector.create_transaction()?; - tran.add(&self.connector, s)?; + tran.add(&self.connector, s.clone())?; tran.prepare()?.apply()?.commit(); self.set_state(state, s); Ok(()) } pub fn set_state(&self, state: &State, s: BackendConnectorState) { - let old = self.state.get(); + let old = self.state.borrow().clone(); if old.serial >= s.serial { return; } - self.state.set(s); + *self.state.borrow_mut() = s.clone(); if old.enabled != s.enabled { self.head_managers.handle_enabled_change(s.enabled); } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index fe02aaf0..fdb789a1 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -19,7 +19,12 @@ use { }, }, jay_config::video::Transform, - std::{cell::Cell, collections::VecDeque, fmt, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + collections::VecDeque, + fmt, + rc::Rc, + }, }; pub fn handle(state: &Rc, connector: &Rc) { @@ -41,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()); @@ -83,7 +89,7 @@ pub fn handle(state: &Rc, connector: &Rc) { damage: Default::default(), needs_vblank_emulation: Cell::new(false), damage_intersect: Default::default(), - state: Cell::new(backend_state), + state: RefCell::new(backend_state), head_managers: HeadManagers::new(state.head_names.next(), head_state), wlr_output_heads: Default::default(), }); @@ -144,7 +150,7 @@ impl ConnectorHandler { async fn handle_connected(&self, info: MonitorInfo) { log::info!("Connector {} connected", self.data.connector.kernel_id()); self.data.connected.set(true); - self.data.set_state(&self.state, info.state); + self.data.set_state(&self.state, info.state.clone()); *self.data.description.borrow_mut() = create_description(&info); let name = self.state.globals.name(); if info.non_desktop_effective { @@ -265,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(); @@ -416,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/tasks/idle.rs b/src/tasks/idle.rs index 71db33e0..5561a263 100644 --- a/src/tasks/idle.rs +++ b/src/tasks/idle.rs @@ -138,7 +138,7 @@ impl Idle { fn try_set_idle(&self, idle: bool) -> Result<(), BackendConnectorTransactionError> { let mut tran = ConnectorTransaction::new(&self.state); for connector in self.state.connectors.lock().values() { - let mut state = connector.state.get(); + let mut state = connector.state.borrow().clone(); state.active = !idle; tran.add(&connector.connector, state)?; } 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) { +}