1
0
Fork 0
forked from wry/wry

Merge pull request #499 from mahkoh/jorth/output-transaction

backend: implement output transactions
This commit is contained in:
mahkoh 2025-07-12 12:04:54 +02:00 committed by GitHub
commit 5375dee007
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2995 additions and 1468 deletions

View file

@ -1,6 +1,10 @@
use { use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
backend::transaction::{
BackendConnectorTransaction, BackendConnectorTransactionError,
BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn,
},
cmm::cmm_primaries::Primaries, cmm::cmm_primaries::Primaries,
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
@ -30,11 +34,14 @@ use {
any::Any, any::Any,
error::Error, error::Error,
fmt::{Debug, Display, Formatter}, fmt::{Debug, Display, Formatter},
hash::Hash,
rc::Rc, rc::Rc,
}, },
uapi::{OwnedFd, c}, uapi::{OwnedFd, c},
}; };
pub mod transaction;
linear_ids!(ConnectorIds, ConnectorId); linear_ids!(ConnectorIds, ConnectorId);
linear_ids!(InputDeviceIds, InputDeviceId); linear_ids!(InputDeviceIds, InputDeviceId);
linear_ids!(DrmDeviceIds, DrmDeviceId); linear_ids!(DrmDeviceIds, DrmDeviceId);
@ -49,10 +56,6 @@ pub trait Backend: Any {
let _ = vtnr; let _ = vtnr;
} }
fn set_idle(&self, idle: bool) {
let _ = idle;
}
fn import_environment(&self) -> bool { fn import_environment(&self) -> bool {
false false
} }
@ -82,21 +85,31 @@ impl Mode {
} }
} }
impl Display for Mode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{}@{}",
self.width,
self.height,
self.refresh_rate_millihz as f64 / 1000.0,
)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MonitorInfo { pub struct MonitorInfo {
pub modes: Vec<Mode>, pub modes: Vec<Mode>,
pub output_id: Rc<OutputId>, pub output_id: Rc<OutputId>,
pub initial_mode: Mode,
pub width_mm: i32, pub width_mm: i32,
pub height_mm: i32, pub height_mm: i32,
pub non_desktop: bool, pub non_desktop: bool,
pub vrr_capable: bool, pub vrr_capable: bool,
pub transfer_functions: Vec<BackendTransferFunction>, pub transfer_functions: Vec<BackendTransferFunction>,
pub transfer_function: BackendTransferFunction,
pub color_spaces: Vec<BackendColorSpace>, pub color_spaces: Vec<BackendColorSpace>,
pub color_space: BackendColorSpace,
pub primaries: Primaries, pub primaries: Primaries,
pub luminance: Option<BackendLuminance>, pub luminance: Option<BackendLuminance>,
pub state: BackendConnectorState,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -111,41 +124,35 @@ impl Display for ConnectorKernelId {
} }
} }
pub trait Connector { pub trait Connector: Any {
fn id(&self) -> ConnectorId; fn id(&self) -> ConnectorId;
fn kernel_id(&self) -> ConnectorKernelId; fn kernel_id(&self) -> ConnectorKernelId;
fn event(&self) -> Option<ConnectorEvent>; fn event(&self) -> Option<ConnectorEvent>;
fn on_change(&self, cb: Rc<dyn Fn()>); fn on_change(&self, cb: Rc<dyn Fn()>);
fn damage(&self); fn damage(&self);
fn drm_dev(&self) -> Option<DrmDeviceId>; fn drm_dev(&self) -> Option<DrmDeviceId>;
fn enabled(&self) -> bool { fn effectively_locked(&self) -> bool;
true
}
fn set_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> { fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
None None
} }
fn set_mode(&self, mode: Mode);
fn set_non_desktop_override(&self, non_desktop: Option<bool>) {
let _ = non_desktop;
}
fn drm_object_id(&self) -> Option<DrmConnector> { fn drm_object_id(&self) -> Option<DrmConnector> {
None None
} }
fn set_vrr_enabled(&self, enabled: bool) { fn before_non_desktop_override_update(&self, overrd: Option<bool>) {
let _ = enabled; let _ = overrd;
} }
fn set_tearing_enabled(&self, enabled: bool) { fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
let _ = enabled; #[derive(Hash, Eq, PartialEq)]
struct UnimplementedConnectorTransactionType;
impl BackendConnectorTransactionType for UnimplementedConnectorTransactionType {}
Box::new(UnimplementedConnectorTransactionType)
} }
fn set_fb_format(&self, format: &'static Format) { fn create_transaction(
let _ = format; &self,
} ) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
fn set_colors(&self, bcs: BackendColorSpace, btf: BackendTransferFunction) { Err(BackendConnectorTransactionError::TransactionsNotSupported(
let _ = bcs; self.kernel_id(),
let _ = btf; ))
} }
} }
@ -155,12 +162,10 @@ pub enum ConnectorEvent {
HardwareCursor(Option<Rc<dyn HardwareCursor>>), HardwareCursor(Option<Rc<dyn HardwareCursor>>),
Disconnected, Disconnected,
Removed, Removed,
ModeChanged(Mode),
Unavailable, Unavailable,
Available, Available,
VrrChanged(bool), State(BackendConnectorState),
FormatsChanged(Rc<Vec<&'static Format>>, &'static Format), FormatsChanged(Rc<Vec<&'static Format>>),
ColorsChanged(BackendColorSpace, BackendTransferFunction),
} }
pub trait HardwareCursorUpdate { pub trait HardwareCursorUpdate {
@ -570,3 +575,23 @@ impl BackendColorSpace {
} }
} }
} }
linear_ids!(
BackendConnectorStateSerials,
BackendConnectorStateSerial,
u64
);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BackendConnectorState {
pub serial: BackendConnectorStateSerial,
pub enabled: bool,
pub active: bool,
pub mode: Mode,
pub non_desktop_override: Option<bool>,
pub vrr: bool,
pub tearing: bool,
pub format: &'static Format,
pub color_space: BackendColorSpace,
pub transfer_function: BackendTransferFunction,
}

219
src/backend/transaction.rs Normal file
View file

@ -0,0 +1,219 @@
use {
crate::{
backend::{
BackendColorSpace, BackendConnectorState, BackendTransferFunction, Connector,
ConnectorKernelId, Mode,
},
backends::metal::MetalError,
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
video::drm::DrmError,
},
ahash::AHashMap,
std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
collections::hash_map::Entry,
hash::{Hash, Hasher},
rc::Rc,
},
thiserror::Error,
};
pub trait BackendConnectorTransactionType: Hash + Eq + Any {}
pub trait BackendConnectorTransactionTypeDyn: Any {
fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool;
fn hash(&self, hasher: &mut dyn Hasher);
}
impl<T> BackendConnectorTransactionTypeDyn for T
where
T: BackendConnectorTransactionType,
{
fn eq(&self, other: &dyn BackendConnectorTransactionTypeDyn) -> bool {
let Some(other) = (other as &dyn Any).downcast_ref::<Self>() else {
return false;
};
self.eq(other)
}
fn hash(&self, hasher: &mut dyn Hasher) {
struct BufHasher<'a> {
buf: Vec<u8>,
clear: Cell<bool>,
any: Cell<bool>,
hasher: RefCell<&'a mut dyn Hasher>,
}
impl Hasher for BufHasher<'_> {
fn finish(&self) -> u64 {
let hasher = &mut *self.hasher.borrow_mut();
if self.any.take() {
self.clear.set(true);
hasher.write(&self.buf);
}
hasher.finish()
}
fn write(&mut self, bytes: &[u8]) {
if self.clear.take() {
self.buf.clear();
}
self.any.set(true);
self.buf.extend_from_slice(bytes);
}
}
let mut hasher = BufHasher {
buf: Default::default(),
clear: Cell::new(false),
any: Cell::new(false),
hasher: RefCell::new(hasher),
};
TypeId::of::<Self>().hash(&mut hasher);
self.hash(&mut hasher)
}
}
impl PartialEq for dyn BackendConnectorTransactionTypeDyn {
fn eq(&self, other: &Self) -> bool {
self.eq(other)
}
}
impl Eq for dyn BackendConnectorTransactionTypeDyn {}
impl Hash for dyn BackendConnectorTransactionTypeDyn {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash(state)
}
}
#[derive(Debug, Error)]
pub enum BackendConnectorTransactionError {
#[error("The underlying DRM device of connector {} no longer exists", .0)]
MissingDrmDevice(ConnectorKernelId),
#[error("Connector {} does not support transactions", .0)]
TransactionsNotSupported(ConnectorKernelId),
#[error("Connector {} is not supported by this transaction", .0)]
UnsupportedConnectorType(ConnectorKernelId),
#[error("Connector {} cannot be modified because it is leased", .0)]
LeasedConnector(ConnectorKernelId),
#[error("Connector {} does not exist", .0)]
UnknownConnector(ConnectorKernelId),
#[error("Cannot initialize connector {} because no CRTC is available", .0)]
NoCrtcForConnector(ConnectorKernelId),
#[error("Cannot initialize connector {} because no primary plane is available", .0)]
NoPrimaryPlaneForConnector(ConnectorKernelId),
#[error("Connector {} does not support the requested mode {}", .0, .1)]
UnsupportedMode(ConnectorKernelId, Mode),
#[error("Connector {} does not support VRR", .0)]
NotVrrCapable(ConnectorKernelId),
#[error("Connector {} does not support tearing", .0)]
TearingNotSupported(ConnectorKernelId),
#[error("Connector {} does not support color space {:?}", .0, .1)]
ColorSpaceNotSupported(ConnectorKernelId, BackendColorSpace),
#[error("Connector {} does not support transfer function {:?}", .0, .1)]
TransferFunctionNotSupported(ConnectorKernelId, BackendTransferFunction),
#[error("Could not create an hdr metadata blob")]
CreateHdrMetadataBlob(#[source] DrmError),
#[error("Could not create a mode blob")]
CreateModeBlob(#[source] DrmError),
#[error("Could not allocate buffers for connector {}", .0)]
AllocateScanoutBuffers(ConnectorKernelId, #[source] Box<MetalError>),
#[error("Test commit failed")]
AtomicTestFailed(#[source] DrmError),
#[error("Commit failed")]
AtomicCommitFailed(#[source] DrmError),
}
pub trait BackendConnectorTransaction {
fn add(
&mut self,
connector: &Rc<dyn Connector>,
change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError>;
fn prepare(
self: Box<Self>,
) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>;
}
pub trait BackendPreparedConnectorTransaction {
fn apply(
self: Box<Self>,
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError>;
}
pub trait BackendAppliedConnectorTransaction {
fn commit(self: Box<Self>);
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError>;
}
#[derive(Default)]
pub struct ConnectorTransaction {
parts:
AHashMap<Box<dyn BackendConnectorTransactionTypeDyn>, Box<dyn BackendConnectorTransaction>>,
}
#[derive(Default)]
pub struct PreparedConnectorTransaction {
parts: Vec<Box<dyn BackendPreparedConnectorTransaction>>,
}
#[derive(Default)]
pub struct AppliedConnectorTransaction {
parts: Vec<Box<dyn BackendAppliedConnectorTransaction>>,
}
impl ConnectorTransaction {
pub fn add(
&mut self,
connector: &Rc<dyn Connector>,
change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let ty = connector.transaction_type();
let tran = match self.parts.entry(ty) {
Entry::Occupied(v) => v.into_mut(),
Entry::Vacant(v) => v.insert(connector.create_transaction()?),
};
tran.add(connector, change)
}
pub fn prepare(
&mut self,
) -> Result<PreparedConnectorTransaction, BackendConnectorTransactionError> {
let mut new = vec![];
for tran in self.parts.drain_values() {
new.push(tran.prepare()?);
}
Ok(PreparedConnectorTransaction { parts: new })
}
}
impl PreparedConnectorTransaction {
pub fn apply(self) -> Result<AppliedConnectorTransaction, BackendConnectorTransactionError> {
let mut applied = AppliedConnectorTransaction::default();
for tran in self.parts {
applied.parts.push(tran.apply()?);
}
Ok(applied)
}
}
impl AppliedConnectorTransaction {
pub fn commit(mut self) {
for tran in self.parts.drain(..) {
tran.commit();
}
}
}
impl Drop for AppliedConnectorTransaction {
fn drop(&mut self) {
for tran in self.parts.drain(..).rev() {
if let Err(e) = tran.rollback() {
log::error!("Could not roll back transaction: {}", ErrorFmt(e));
}
}
}
}

View file

@ -2,7 +2,7 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
backend::{ backend::{
Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, Mode, Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId,
}, },
video::drm::ConnectorType, video::drm::ConnectorType,
}, },
@ -49,7 +49,7 @@ impl Connector for DummyOutput {
None None
} }
fn set_mode(&self, _mode: Mode) { fn effectively_locked(&self) -> bool {
// nothing true
} }
} }

View file

@ -1,6 +1,7 @@
mod input; mod input;
mod monitor; mod monitor;
mod present; mod present;
mod transaction;
mod video; mod video;
use { use {
@ -9,7 +10,7 @@ use {
backend::{ backend::{
Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, Backend, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState, InputDeviceClickMethod, InputDeviceGroupId, InputDeviceId, InputEvent, KeyState,
TransformMatrix, TransformMatrix, transaction::BackendConnectorTransactionError,
}, },
backends::metal::video::{ backends::metal::video::{
MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice, MetalDrmDeviceData, MetalLeaseData, MetalRenderContext, PendingDrmDevice,
@ -47,10 +48,7 @@ use {
smallmap::SmallMap, smallmap::SmallMap,
syncqueue::SyncQueue, syncqueue::SyncQueue,
}, },
video::{ video::{drm::DrmError, gbm::GbmError},
drm::{DRM_MODE_ATOMIC_ALLOW_MODESET, DrmError},
gbm::GbmError,
},
}, },
bstr::ByteSlice, bstr::ByteSlice,
std::{ std::{
@ -87,12 +85,6 @@ pub enum MetalError {
UpdateProperties(#[source] DrmError), UpdateProperties(#[source] DrmError),
#[error("Could not create a render context")] #[error("Could not create a render context")]
CreateRenderContex(#[source] GfxError), CreateRenderContex(#[source] GfxError),
#[error("Cannot initialize connector because no CRTC is available")]
NoCrtcForConnector,
#[error("Cannot initialize connector because no primary plane is available")]
NoPrimaryPlaneForConnector,
#[error("Cannot initialize connector because no mode is available")]
NoModeForConnector,
#[error("Could not allocate scanout buffer")] #[error("Could not allocate scanout buffer")]
ScanoutBuffer(#[source] GbmError), ScanoutBuffer(#[source] GbmError),
#[error("addfb2 failed")] #[error("addfb2 failed")]
@ -104,7 +96,7 @@ pub enum MetalError {
#[error("Could not import an image into the graphics API")] #[error("Could not import an image into the graphics API")]
ImportImage(#[source] GfxError), ImportImage(#[source] GfxError),
#[error("Could not perform modeset")] #[error("Could not perform modeset")]
Modeset(#[source] DrmError), Modeset(#[source] BackendConnectorTransactionError),
#[error("Could not enable atomic modesetting")] #[error("Could not enable atomic modesetting")]
AtomicModesetting(#[source] OsError), AtomicModesetting(#[source] OsError),
#[error("Could not inspect a plane")] #[error("Could not inspect a plane")]
@ -137,6 +129,12 @@ pub enum MetalError {
Clear(#[source] GfxError), Clear(#[source] GfxError),
#[error("The present configuration is out of date")] #[error("The present configuration is out of date")]
OutOfDate, OutOfDate,
#[error("Could not add connector to transaction")]
AddToTransaction(#[source] BackendConnectorTransactionError),
#[error("Could not calculate DRM state")]
CalculateDrmState(#[source] BackendConnectorTransactionError),
#[error("Could not calculate DRM change set")]
CalculateDrmChange(#[source] BackendConnectorTransactionError),
} }
pub struct MetalBackend { pub struct MetalBackend {
@ -204,6 +202,7 @@ impl Backend for MetalBackend {
dev.futures.clear(); dev.futures.clear();
for crtc in dev.dev.crtcs.values() { for crtc in dev.dev.crtcs.values() {
crtc.connector.take(); crtc.connector.take();
crtc.pending_flip.take();
} }
dev.dev.handle_events.handle_events.take(); dev.dev.handle_events.handle_events.take();
dev.dev.on_change.clear(); dev.dev.on_change.clear();
@ -242,36 +241,6 @@ impl Backend for MetalBackend {
}) })
} }
fn set_idle(&self, idle: bool) {
let devices = self.device_holder.drm_devices.lock();
for device in devices.values() {
let mut change = device.dev.master.change();
for connector in device.connectors.lock().values() {
if let Some(crtc) = connector.crtc.get()
&& idle == crtc.active.value.get()
{
crtc.active.value.set(!idle);
change.change_object(crtc.id, |c| {
c.change(crtc.active.id, (!idle) as _);
});
}
}
if let Err(e) = change.commit(DRM_MODE_ATOMIC_ALLOW_MODESET, 0) {
log::error!("Could not set monitors idle/not idle: {}", ErrorFmt(e));
return;
}
}
if idle {
self.state.set_backend_idle(true);
} else {
for device in devices.values() {
for connector in device.connectors.lock().values() {
connector.schedule_present();
}
}
}
}
fn import_environment(&self) -> bool { fn import_environment(&self) -> bool {
true true
} }

View file

@ -3,6 +3,7 @@ use {
backend::Connector, backend::Connector,
backends::metal::{ backends::metal::{
MetalError, MetalError,
transaction::{DrmConnectorState, DrmPlaneState},
video::{ video::{
MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer, MetalConnector, MetalCrtc, MetalHardwareCursorChange, MetalPlane, RenderBuffer,
}, },
@ -22,18 +23,20 @@ use {
dmabuf::DmaBufId, dmabuf::DmaBufId,
drm::{ drm::{
DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, DRM_MODE_PAGE_FLIP_EVENT, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_ASYNC, DRM_MODE_PAGE_FLIP_EVENT,
DrmError, DrmFramebuffer, DrmCrtc, DrmError, DrmFb, DrmFramebuffer, DrmObject,
}, },
}, },
}, },
arrayvec::ArrayVec,
std::rc::{Rc, Weak}, std::rc::{Rc, Weak},
uapi::c, uapi::{OwnedFd, c},
}; };
struct Latched { struct Latched {
pass: GfxRenderPass, pass: GfxRenderPass,
damage_count: u64, damage_count: u64,
damage: Region, damage: Region,
locked: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -68,11 +71,18 @@ pub struct PresentFb {
tex: Rc<dyn GfxTexture>, tex: Rc<dyn GfxTexture>,
direct_scanout_data: Option<DirectScanoutData>, direct_scanout_data: Option<DirectScanoutData>,
sync_file: Option<SyncFile>, sync_file: Option<SyncFile>,
pub locked: bool,
} }
enum CursorProgramming { #[derive(Debug)]
struct CursorProgramming {
plane: Rc<MetalPlane>,
ty: CursorProgrammingType,
}
#[derive(Debug)]
enum CursorProgrammingType {
Enable { Enable {
plane: Rc<MetalPlane>,
fb: Rc<DrmFramebuffer>, fb: Rc<DrmFramebuffer>,
x: i32, x: i32,
y: i32, y: i32,
@ -80,9 +90,12 @@ enum CursorProgramming {
height: i32, height: i32,
swap: bool, swap: bool,
}, },
Disable { Disable,
plane: Rc<MetalPlane>, }
},
struct ChangedPlane {
plane: Rc<MetalPlane>,
state: DrmPlaneState,
} }
pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms
@ -101,13 +114,17 @@ impl MetalConnector {
let mut max = 0; let mut max = 0;
loop { loop {
self.present_trigger.triggered().await; self.present_trigger.triggered().await;
if !self.can_present.get() { if !self.buffers_idle.get() || !self.crtc_idle.get() {
continue; continue;
} }
let Some(crtc) = self.crtc.get() else {
continue;
};
let Some(node) = self.state.root.outputs.get(&self.connector_id) else { let Some(node) = self.state.root.outputs.get(&self.connector_id) else {
continue; continue;
}; };
let mut expected_sequence = self.sequence.get() + 1; let version = self.version.get();
let mut expected_sequence = crtc.sequence.get() + 1;
let mut start = Time::now_unchecked(); let mut start = Time::now_unchecked();
let use_frame_scheduling = !self.try_async_flip(); let use_frame_scheduling = !self.try_async_flip();
if use_frame_scheduling { if use_frame_scheduling {
@ -132,7 +149,11 @@ impl MetalConnector {
}; };
node.before_latch(flip).await; node.before_latch(flip).await;
} }
if let Err(e) = self.present_once(&node).await { if version != self.version.get() {
self.present_trigger.trigger();
continue;
}
if let Err(e) = self.present_once(&node, &crtc).await {
log::error!("Could not present: {}", ErrorFmt(e)); log::error!("Could not present: {}", ErrorFmt(e));
continue; continue;
} }
@ -152,19 +173,19 @@ impl MetalConnector {
} }
} }
async fn present_once(&self, node: &Rc<OutputNode>) -> Result<(), MetalError> { async fn present_once(
self: &Rc<Self>,
node: &Rc<OutputNode>,
crtc: &Rc<MetalCrtc>,
) -> Result<(), MetalError> {
let version = self.version.get(); let version = self.version.get();
if !self.can_present.get() { if !self.buffers_idle.get() || !self.crtc_idle.get() {
return Ok(()); return Ok(());
} }
if !self.backend.check_render_context(&self.dev) { if !self.backend.check_render_context(&self.dev) {
return Ok(()); return Ok(());
} }
let crtc = match self.crtc.get() { if !crtc.drm_state.borrow().active {
Some(crtc) => crtc,
_ => return Ok(()),
};
if !crtc.active.value.get() {
return Ok(()); return Ok(());
} }
let plane = match self.primary_plane.get() { let plane = match self.primary_plane.get() {
@ -175,7 +196,9 @@ impl MetalConnector {
Some(b) => b, Some(b) => b,
_ => return Ok(()), _ => return Ok(()),
}; };
let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut connector_drm_state = self.display.borrow().drm_state.clone();
let next_buffer_idx = ((connector_drm_state.fb_idx + 1) % buffers.len() as u64) as usize;
let buffer = &buffers[next_buffer_idx];
let cd = node.global.color_description.get(); let cd = node.global.color_description.get();
let linear_cd = node.global.linear_color_description.get(); let linear_cd = node.global.linear_color_description.get();
@ -183,8 +206,8 @@ impl MetalConnector {
if self.has_damage.get() > 0 || self.cursor_damage.get() { if self.has_damage.get() > 0 || self.cursor_damage.get() {
node.schedule.commit_cursor(); node.schedule.commit_cursor();
} }
self.latch_cursor(&node, &cd)?; self.latch_cursor(&node, &connector_drm_state, &cd)?;
let cursor_programming = self.compute_cursor_programming(); let cursor_programming = self.compute_cursor_programming(&connector_drm_state);
let latched = self.latch(&node, buffer); let latched = self.latch(&node, buffer);
node.latched(self.try_async_flip()); node.latched(self.try_async_flip());
@ -209,12 +232,15 @@ impl MetalConnector {
); );
} }
self.await_present_fb(present_fb.as_mut()).await; self.await_present_fb(present_fb.as_mut()).await;
let mut changed_planes = ArrayVec::new();
let mut res = self.program_connector( let mut res = self.program_connector(
version, version,
&crtc, &crtc,
&plane, &plane,
cursor_programming.as_ref(), cursor_programming.as_ref(),
present_fb.as_ref(), present_fb.as_ref(),
&mut changed_planes,
&mut connector_drm_state,
); );
if res.is_err() if res.is_err()
&& let Some(dsd_id) = direct_scanout_id && let Some(dsd_id) = direct_scanout_id
@ -235,6 +261,8 @@ impl MetalConnector {
&plane, &plane,
cursor_programming.as_ref(), cursor_programming.as_ref(),
present_fb.as_ref(), present_fb.as_ref(),
&mut changed_planes,
&mut connector_drm_state,
); );
if res.is_ok() { if res.is_ok() {
let mut cache = self.scanout_buffers.borrow_mut(); let mut cache = self.scanout_buffers.borrow_mut();
@ -265,35 +293,30 @@ impl MetalConnector {
} }
Err(e) Err(e)
} else { } else {
macro_rules! apply_change { crtc.pending_flip.set(Some(self.clone()));
($prop:expr) => { self.crtc_idle.set(false);
if let Some(v) = $prop.pending_value.take() { self.color_description.set(cd);
$prop.value.set(v); self.display.borrow_mut().drm_state = connector_drm_state;
} for plane in changed_planes {
}; *plane.plane.drm_state.borrow_mut() = plane.state;
} }
apply_change!(plane.src_w);
apply_change!(plane.src_h);
apply_change!(plane.crtc_x);
apply_change!(plane.crtc_y);
apply_change!(plane.crtc_w);
apply_change!(plane.crtc_h);
if let Some(fb) = present_fb { if let Some(fb) = present_fb {
self.presentation_is_zero_copy self.presentation_is_zero_copy
.set(fb.direct_scanout_data.is_some()); .set(fb.direct_scanout_data.is_some());
if fb.direct_scanout_data.is_none() { if fb.direct_scanout_data.is_none() {
buffer.damage_queue.clear(); buffer.damage_queue.clear();
self.next_buffer.fetch_add(1);
} else { } else {
reset_damage(); reset_damage();
} }
buffer.locked.set(fb.locked);
self.next_framebuffer.set(Some(fb)); self.next_framebuffer.set(Some(fb));
} }
if let Some(CursorProgramming::Enable { swap: true, .. }) = cursor_programming { if let Some(programming) = cursor_programming
&& let CursorProgrammingType::Enable { swap: true, .. } = &programming.ty
{
self.cursor_swap_buffer.set(false); self.cursor_swap_buffer.set(false);
self.cursor_front_buffer.fetch_add(1);
} }
self.can_present.set(false); self.buffers_idle.set(false);
if let Some(latched) = latched { if let Some(latched) = latched {
self.has_damage.fetch_sub(latched.damage_count); self.has_damage.fetch_sub(latched.damage_count);
} }
@ -318,7 +341,7 @@ impl MetalConnector {
} }
fn try_async_flip(&self) -> bool { fn try_async_flip(&self) -> bool {
self.tearing_requested.get() && self.dev.supports_async_commit self.display.borrow().persistent.state.borrow().tearing && self.dev.supports_async_commit
} }
fn program_connector( fn program_connector(
@ -328,19 +351,15 @@ impl MetalConnector {
plane: &Rc<MetalPlane>, plane: &Rc<MetalPlane>,
cursor: Option<&CursorProgramming>, cursor: Option<&CursorProgramming>,
new_fb: Option<&PresentFb>, new_fb: Option<&PresentFb>,
changed_planes: &mut ArrayVec<ChangedPlane, 2>,
connector_drm_state: &mut DrmConnectorState,
) -> Result<(), MetalError> { ) -> Result<(), MetalError> {
zone!("program_connector"); zone!("program_connector");
let mut changes = self.master.change(); let mut changes = self.master.change();
let mut try_async_flip = self.try_async_flip(); let mut try_async_flip = self.try_async_flip();
macro_rules! change { let mut drm_state = plane.drm_state.borrow().clone();
($c:expr, $prop:expr, $new:expr) => {{ changed_planes.clear();
if $prop.value.get() != $new { let mut connector_state = connector_drm_state.clone();
$c.change($prop.id, $new as u64);
try_async_flip = false;
$prop.pending_value.set(Some($new));
}
}};
}
if let Some(fb) = new_fb { if let Some(fb) = new_fb {
let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) = let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) =
match &fb.direct_scanout_data { match &fb.direct_scanout_data {
@ -362,69 +381,90 @@ impl MetalConnector {
} }
}; };
changes.change_object(plane.id, |c| { changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _); c.change(plane.fb_id, fb.fb.id());
change!(c, plane.src_w, (src_width as u32) << 16); drm_state.fb_id = fb.fb.id();
change!(c, plane.src_h, (src_height as u32) << 16); connector_state.fb = fb.fb.id();
change!(c, plane.crtc_x, crtc_x); connector_state.locked = fb.locked;
change!(c, plane.crtc_y, crtc_y); if fb.direct_scanout_data.is_none() {
change!(c, plane.crtc_w, crtc_w); connector_state.fb_idx += 1;
change!(c, plane.crtc_h, crtc_h);
if !try_async_flip
&& !self.dev.is_nvidia
&& let Some(sf) = self.backend.signaled_sync_file.get()
{
c.change(plane.in_fence_fd, sf.0.raw() as u64);
} }
macro_rules! change {
($prop:ident, $new:expr) => {{
if drm_state.$prop != $new {
c.change(plane.$prop, $new as u64);
try_async_flip = false;
drm_state.$prop = $new;
}
connector_state.$prop = drm_state.$prop;
}};
}
change!(src_w, (src_width as u32) << 16);
change!(src_h, (src_height as u32) << 16);
change!(crtc_x, crtc_x);
change!(crtc_y, crtc_y);
change!(crtc_w, crtc_w);
change!(crtc_h, crtc_h);
});
changed_planes.push(ChangedPlane {
plane: plane.clone(),
state: drm_state,
}); });
} else {
// Work around https://gitlab.freedesktop.org/drm/amd/-/issues/2186
if self.dev.is_amd
&& crtc.vrr_enabled.value.get()
&& let Some(fb) = &*self.active_framebuffer.borrow()
{
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _);
});
}
} }
if let Some(cursor) = cursor { if let Some(cursor) = cursor {
let plane = &cursor.plane;
let mut drm_state = plane.drm_state.borrow().clone();
try_async_flip = false; try_async_flip = false;
match cursor { changes.change_object(plane.id, |c| {
CursorProgramming::Enable { macro_rules! change {
plane, ($prop:ident, $new:expr) => {{
fb, c.change(plane.$prop, $new);
x, drm_state.$prop = $new;
y, }};
width, }
height, match &cursor.ty {
.. CursorProgrammingType::Enable {
} => { fb,
changes.change_object(plane.id, |c| { x,
c.change(plane.fb_id, fb.id().0 as _); y,
c.change(plane.crtc_id.id, crtc.id.0 as _); width,
c.change(plane.crtc_x.id, *x as _); height,
c.change(plane.crtc_y.id, *y as _); swap,
c.change(plane.crtc_w.id, *width as _); } => {
c.change(plane.crtc_h.id, *height as _); connector_state.cursor_fb = fb.id();
c.change(plane.src_x.id, 0); if *swap {
c.change(plane.src_y.id, 0); connector_state.cursor_fb_idx += 1;
c.change(plane.src_w.id, (*width as u64) << 16); }
c.change(plane.src_h.id, (*height as u64) << 16); connector_state.cursor_x = *x;
connector_state.cursor_y = *y;
change!(fb_id, fb.id());
change!(crtc_id, crtc.id);
change!(crtc_x, *x);
change!(crtc_y, *y);
change!(crtc_w, *width);
change!(crtc_h, *height);
change!(src_x, 0);
change!(src_y, 0);
change!(src_w, (*width as u32) << 16);
change!(src_h, (*height as u32) << 16);
if !self.dev.is_nvidia if !self.dev.is_nvidia
&& let Some(sf) = self.backend.signaled_sync_file.get() && let Some(sf) = self.backend.signaled_sync_file.get()
{ {
c.change(plane.in_fence_fd, sf.0.raw() as u64); c.change(plane.in_fence_fd, sf.0.raw() as u64);
} }
}); }
CursorProgrammingType::Disable => {
connector_state.cursor_fb = DrmFb::NONE;
change!(fb_id, DrmFb::NONE);
change!(crtc_id, DrmCrtc::NONE);
}
} }
CursorProgramming::Disable { plane } => { });
changes.change_object(plane.id, |c| { changed_planes.push(ChangedPlane {
c.change(plane.fb_id, 0); plane: plane.clone(),
c.change(plane.crtc_id.id, 0); state: drm_state,
}); });
}
}
} }
let mut out_fd: c::c_int = -1;
if version != self.version.get() { if version != self.version.get() {
return Err(MetalError::OutOfDate); return Err(MetalError::OutOfDate);
} }
@ -439,14 +479,32 @@ impl MetalConnector {
} }
} }
self.presentation_is_sync.set(true); self.presentation_is_sync.set(true);
if !self.dev.is_nvidia {
if new_fb.is_some()
&& let Some(sf) = self.backend.signaled_sync_file.get()
{
changes.change_object(plane.id, |c| {
c.change(plane.in_fence_fd, sf.0.raw() as u64);
});
}
changes.change_object(crtc.id, |c| {
c.change(crtc.out_fence_ptr, &raw mut out_fd as u64);
});
}
res = changes.commit(FLAGS, 0); res = changes.commit(FLAGS, 0);
} }
if res.is_ok() {
connector_state.out_fd =
(out_fd != -1).then(|| SyncFile(Rc::new(OwnedFd::new(out_fd))));
*connector_drm_state = connector_state;
}
res.map_err(MetalError::Commit) res.map_err(MetalError::Commit)
} }
fn latch_cursor( fn latch_cursor(
&self, &self,
node: &Rc<OutputNode>, node: &Rc<OutputNode>,
connector_drm_state: &DrmConnectorState,
cd: &Rc<ColorDescription>, cd: &Rc<ColorDescription>,
) -> Result<(), MetalError> { ) -> Result<(), MetalError> {
if !self.cursor_damage.take() { if !self.cursor_damage.take() {
@ -456,12 +514,13 @@ impl MetalConnector {
return Ok(()); return Ok(());
} }
let buffers = self.cursor_buffers.get().unwrap(); let buffers = self.cursor_buffers.get().unwrap();
let buffer_idx = ((connector_drm_state.cursor_fb_idx + 1) % buffers.len() as u64) as usize;
let mut c = MetalHardwareCursorChange { let mut c = MetalHardwareCursorChange {
cursor_enabled: self.cursor_enabled.get(), cursor_enabled: self.cursor_enabled.get(),
cursor_swap_buffer: false, cursor_swap_buffer: false,
cursor_x: self.cursor_x.get(), cursor_x: self.cursor_x.get(),
cursor_y: self.cursor_y.get(), cursor_y: self.cursor_y.get(),
cursor_buffer: &buffers[(self.cursor_front_buffer.get() + 1) % buffers.len()], cursor_buffer: &buffers[buffer_idx],
sync_file: None, sync_file: None,
cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _), cursor_size: (self.dev.cursor_width as _, self.dev.cursor_height as _),
}; };
@ -484,22 +543,25 @@ impl MetalConnector {
Ok(()) Ok(())
} }
fn compute_cursor_programming(&self) -> Option<CursorProgramming> { fn compute_cursor_programming(
&self,
connector_drm_state: &DrmConnectorState,
) -> Option<CursorProgramming> {
if !self.cursor_changed.get() { if !self.cursor_changed.get() {
return None; return None;
} }
let plane = self.cursor_plane.get()?; let plane = self.cursor_plane.get()?;
let programming = if self.cursor_enabled.get() { let ty = if self.cursor_enabled.get() {
let swap = self.cursor_swap_buffer.get(); let swap = self.cursor_swap_buffer.get();
let mut front_buffer = self.cursor_front_buffer.get();
if swap {
front_buffer = front_buffer.wrapping_add(1);
}
let buffers = self.cursor_buffers.get().unwrap(); let buffers = self.cursor_buffers.get().unwrap();
let buffer = &buffers[front_buffer % buffers.len()]; let mut front_buffer = connector_drm_state.cursor_fb_idx;
if swap {
front_buffer += 1;
}
let buffer_idx = (front_buffer % buffers.len() as u64) as usize;
let buffer = &buffers[buffer_idx];
let (width, height) = buffer.dev_fb.physical_size(); let (width, height) = buffer.dev_fb.physical_size();
CursorProgramming::Enable { CursorProgrammingType::Enable {
plane,
fb: buffer.drm.clone(), fb: buffer.drm.clone(),
x: self.cursor_x.get(), x: self.cursor_x.get(),
y: self.cursor_y.get(), y: self.cursor_y.get(),
@ -508,9 +570,9 @@ impl MetalConnector {
swap, swap,
} }
} else { } else {
CursorProgramming::Disable { plane } CursorProgrammingType::Disable
}; };
Some(programming) Some(CursorProgramming { plane, ty })
} }
fn latch(&self, node: &Rc<OutputNode>, buffer: &RenderBuffer) -> Option<Latched> { fn latch(&self, node: &Rc<OutputNode>, buffer: &RenderBuffer) -> Option<Latched> {
@ -545,6 +607,7 @@ impl MetalConnector {
pass, pass,
damage_count, damage_count,
damage, damage,
locked: self.state.lock.locked.get(),
}) })
} }
@ -797,6 +860,7 @@ impl MetalConnector {
tex, tex,
direct_scanout_data, direct_scanout_data,
sync_file, sync_file,
locked: latched.locked,
}) })
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,15 +3,19 @@ use {
allocator::BufferObject, allocator::BufferObject,
async_engine::{Phase, SpawnedFuture}, async_engine::{Phase, SpawnedFuture},
backend::{ backend::{
AXIS_120, AxisSource, Backend, BackendColorSpace, BackendDrmDevice, BackendEvent, AXIS_120, AxisSource, Backend, BackendConnectorState, BackendDrmDevice, BackendEvent,
BackendTransferFunction, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, DrmEvent,
DrmDeviceId, DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod,
InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
ScrollAxis, TransformMatrix, transaction::{
BackendAppliedConnectorTransaction, BackendConnectorTransaction,
BackendConnectorTransactionError, BackendConnectorTransactionType,
BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction,
},
}, },
cmm::cmm_primaries::Primaries, cmm::cmm_primaries::Primaries,
fixed::Fixed, fixed::Fixed,
format::XRGB8888, format::{Format, XRGB8888},
gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync}, gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync},
ifs::wl_output::OutputId, ifs::wl_output::OutputId,
state::State, state::State,
@ -51,8 +55,10 @@ use {
}, },
}, },
}, },
ahash::AHashMap,
jay_config::video::GfxApi, jay_config::video::GfxApi,
std::{ std::{
any::Any,
borrow::Cow, borrow::Cow,
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::VecDeque, collections::VecDeque,
@ -120,6 +126,8 @@ pub enum XBackendError {
XRGB8888, XRGB8888,
} }
const FORMAT: &Format = XRGB8888;
pub async fn create(state: &Rc<State>) -> Result<Rc<XBackend>, XBackendError> { pub async fn create(state: &Rc<State>) -> Result<Rc<XBackend>, XBackendError> {
let c = match Xcon::connect(state).await { let c = match Xcon::connect(state).await {
Ok(c) => c, Ok(c) => c,
@ -376,7 +384,7 @@ impl XBackend {
) -> Result<[XImage; 2], XBackendError> { ) -> Result<[XImage; 2], XBackendError> {
let mut images = [None, None]; let mut images = [None, None];
let formats = self.ctx.formats(); let formats = self.ctx.formats();
let format = match formats.get(&XRGB8888.drm) { let format = match formats.get(&FORMAT.drm) {
Some(f) => f, Some(f) => f,
None => return Err(XBackendError::XRGB8888), None => return Err(XBackendError::XRGB8888),
}; };
@ -385,7 +393,7 @@ impl XBackend {
&self.state.dma_buf_ids, &self.state.dma_buf_ids,
width, width,
height, height,
XRGB8888, FORMAT,
format.write_modifiers.keys(), format.write_modifiers.keys(),
GBM_BO_USE_RENDERING, GBM_BO_USE_RENDERING,
)?; )?;
@ -469,6 +477,22 @@ impl XBackend {
cw.wid cw.wid
}; };
let images = self.create_images(window_id, WIDTH, HEIGHT).await?; let images = self.create_images(window_id, WIDTH, HEIGHT).await?;
let state = BackendConnectorState {
serial: self.state.backend_connector_state_serials.next(),
enabled: true,
active: true,
mode: Mode {
width: WIDTH,
height: HEIGHT,
refresh_rate_millihz: 60_000, // TODO
},
non_desktop_override: None,
vrr: false,
tearing: false,
format: FORMAT,
color_space: Default::default(),
transfer_function: Default::default(),
};
let output = Rc::new(XOutput { let output = Rc::new(XOutput {
id: self.state.connector_ids.next(), id: self.state.connector_ids.next(),
backend: self.clone(), backend: self.clone(),
@ -481,6 +505,7 @@ impl XBackend {
next_image: Default::default(), next_image: Default::default(),
cb: CloneCell::new(None), cb: CloneCell::new(None),
images, images,
state: Cell::new(state),
}); });
{ {
let class = "jay\0jay\0"; let class = "jay\0jay\0";
@ -569,21 +594,15 @@ impl XBackend {
format!("X-Window-{}", output.window), format!("X-Window-{}", output.window),
output.window.to_string(), output.window.to_string(),
)), )),
initial_mode: Mode {
width: output.width.get(),
height: output.height.get(),
refresh_rate_millihz: 60_000, // TODO
},
width_mm: output.width.get(), width_mm: output.width.get(),
height_mm: output.height.get(), height_mm: output.height.get(),
non_desktop: false, non_desktop: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], transfer_functions: vec![],
transfer_function: BackendTransferFunction::Default,
color_spaces: vec![], color_spaces: vec![],
color_space: BackendColorSpace::Default,
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
state: output.state.get(),
})); }));
output.changed(); output.changed();
self.present(output).await; self.present(output).await;
@ -962,11 +981,12 @@ impl XBackend {
old.tex.set(new.tex.get()); old.tex.set(new.tex.get());
old.pixmap.set(new.pixmap.get()); old.pixmap.set(new.pixmap.get());
} }
output.events.push(ConnectorEvent::ModeChanged(Mode { let mut state = output.state.get();
width, state.serial = self.state.backend_connector_state_serials.next();
height, state.mode.width = width;
refresh_rate_millihz: 60, // TODO state.mode.height = height;
})); output.state.set(state);
output.events.push(ConnectorEvent::State(state));
output.changed(); output.changed();
} }
Ok(()) Ok(())
@ -1035,6 +1055,7 @@ struct XOutput {
next_image: NumCell<usize>, next_image: NumCell<usize>,
images: [XImage; 2], images: [XImage; 2],
cb: CloneCell<Option<Rc<dyn Fn()>>>, cb: CloneCell<Option<Rc<dyn Fn()>>>,
state: Cell<BackendConnectorState>,
} }
struct XImage { struct XImage {
@ -1083,8 +1104,75 @@ impl Connector for XOutput {
Some(self.backend.drm_device_id) Some(self.backend.drm_device_id)
} }
fn set_mode(&self, _mode: Mode) { fn effectively_locked(&self) -> bool {
log::warn!("X backend doesn't support changing the connector mode"); // todo
true
}
fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
Box::new(XTransactionType)
}
fn create_transaction(
&self,
) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
Ok(Box::new(XTransaction::default()))
}
}
#[derive(Hash, Eq, PartialEq)]
struct XTransactionType;
impl BackendConnectorTransactionType for XTransactionType {}
#[derive(Default)]
struct XTransaction {
connectors: AHashMap<ConnectorId, Rc<XOutput>>,
}
impl XTransaction {
fn send_state(&self) {
for con in self.connectors.values() {
let mut state = con.state.get();
state.serial = con.backend.state.backend_connector_state_serials.next();
con.events.push(ConnectorEvent::State(state));
}
}
}
impl BackendConnectorTransaction for XTransaction {
fn add(
&mut self,
connector: &Rc<dyn Connector>,
_change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let con = (connector.clone() as Rc<dyn Any>)
.downcast::<XOutput>()
.map_err(|_| {
BackendConnectorTransactionError::UnsupportedConnectorType(connector.kernel_id())
})?;
self.connectors.insert(con.id, con.clone());
Ok(())
}
fn prepare(
self: Box<Self>,
) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>
{
Ok(self)
}
}
impl BackendPreparedConnectorTransaction for XTransaction {
fn apply(
self: Box<Self>,
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
self.send_state();
Ok(self)
}
}
impl BackendAppliedConnectorTransaction for XTransaction {
fn commit(self: Box<Self>) {
// nothing
}
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
self.send_state();
Ok(())
} }
} }

View file

@ -853,10 +853,11 @@ impl Randr {
fn print_connector(&self, connector: &Connector, modes: bool, formats: bool) { fn print_connector(&self, connector: &Connector, modes: bool, formats: bool) {
println!(" {}:", connector.name); println!(" {}:", connector.name);
if !connector.enabled {
println!(" disabled");
}
let Some(o) = &connector.output else { let Some(o) = &connector.output else {
if !connector.enabled { if connector.enabled {
println!(" disabled");
} else {
println!(" disconnected"); println!(" disconnected");
} }
return; return;
@ -869,7 +870,9 @@ impl Randr {
o.width_mm, o.height_mm o.width_mm, o.height_mm
); );
if o.non_desktop { if o.non_desktop {
println!(" non-desktop"); if connector.enabled {
println!(" non-desktop");
}
return; return;
} }
println!(" VRR capable: {}", o.vrr_capable); println!(" VRR capable: {}", o.vrr_capable);

View file

@ -4,7 +4,7 @@ use {
crate::{ crate::{
acceptor::{Acceptor, AcceptorError}, acceptor::{Acceptor, AcceptorError},
async_engine::{AsyncEngine, Phase, SpawnedFuture}, async_engine::{AsyncEngine, Phase, SpawnedFuture},
backend::{self, Backend, BackendColorSpace, BackendTransferFunction, Connector}, backend::{self, Backend, BackendConnectorState, BackendConnectorStateSerial, Connector},
backends::{ backends::{
dummy::{DummyBackend, DummyOutput}, dummy::{DummyBackend, DummyOutput},
metal, x, metal, x,
@ -26,6 +26,7 @@ use {
dbus::Dbus, dbus::Dbus,
ei::ei_client::EiClients, ei::ei_client::EiClients,
forker, forker,
format::XRGB8888,
globals::Globals, globals::Globals,
ifs::{ ifs::{
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
@ -337,6 +338,7 @@ fn start_compositor2(
toplevel_managers: Default::default(), toplevel_managers: Default::default(),
node_at_tree: Default::default(), node_at_tree: Default::default(),
position_hint_requests: Default::default(), position_hint_requests: Default::default(),
backend_connector_state_serials: Default::default(),
}); });
state.tracker.register(ClientId::from_raw(0)); state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state); create_dummy_output(&state);
@ -396,7 +398,7 @@ async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
} }
state.update_ei_acceptor(); state.update_ei_acceptor();
let _geh = start_global_event_handlers(&state, &backend); let _geh = start_global_event_handlers(&state);
state.start_xwayland(); state.start_xwayland();
match backend.run().await { match backend.run().await {
@ -424,10 +426,7 @@ fn load_config(
} }
} }
fn start_global_event_handlers( fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
state: &Rc<State>,
backend: &Rc<dyn Backend>,
) -> Vec<SpawnedFuture<()>> {
let eng = &state.eng; let eng = &state.eng;
vec![ vec![
@ -471,11 +470,7 @@ fn start_global_event_handlers(
Phase::PostLayout, Phase::PostLayout,
float_titles(state.clone()), float_titles(state.clone()),
), ),
eng.spawn2( eng.spawn2("idle", Phase::PostLayout, idle(state.clone())),
"idle",
Phase::PostLayout,
idle(state.clone(), backend.clone()),
),
eng.spawn2( eng.spawn2(
"input, popup positioning", "input, popup positioning",
Phase::PostLayout, Phase::PostLayout,
@ -610,6 +605,23 @@ fn create_dummy_output(state: &Rc<State>) {
let connector = Rc::new(DummyOutput { let connector = Rc::new(DummyOutput {
id: state.connector_ids.next(), id: state.connector_ids.next(),
}) as Rc<dyn Connector>; }) as Rc<dyn Connector>;
let mode = backend::Mode {
width: 0,
height: 0,
refresh_rate_millihz: 40_000,
};
let backend_state = BackendConnectorState {
serial: BackendConnectorStateSerial::from_raw(0),
enabled: true,
active: false,
mode,
non_desktop_override: None,
vrr: false,
tearing: false,
format: XRGB8888,
color_space: Default::default(),
transfer_function: Default::default(),
};
let connector_data = Rc::new(ConnectorData { let connector_data = Rc::new(ConnectorData {
connector, connector,
handler: Cell::new(None), handler: Cell::new(None),
@ -621,6 +633,7 @@ fn create_dummy_output(state: &Rc<State>) {
damage: Default::default(), damage: Default::default(),
needs_vblank_emulation: Cell::new(false), needs_vblank_emulation: Cell::new(false),
damage_intersect: Default::default(), damage_intersect: Default::default(),
state: Cell::new(backend_state),
}); });
let schedule = Rc::new(OutputSchedule::new( let schedule = Rc::new(OutputSchedule::new(
&state.ring, &state.ring,
@ -635,19 +648,12 @@ fn create_dummy_output(state: &Rc<State>) {
state, state,
&connector_data, &connector_data,
Vec::new(), Vec::new(),
&backend::Mode {
width: 0,
height: 0,
refresh_rate_millihz: 40_000,
},
0, 0,
0, 0,
&output_id, &output_id,
&persistent_state, &persistent_state,
Vec::new(), Vec::new(),
BackendTransferFunction::Default,
Vec::new(), Vec::new(),
BackendColorSpace::Default,
Primaries::SRGB, Primaries::SRGB,
None, None,
)), )),

View file

@ -4,6 +4,7 @@ use {
backend::{ backend::{
self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId, self, BackendColorSpace, BackendTransferFunction, ConnectorId, DrmDeviceId,
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId,
transaction::BackendConnectorTransactionError,
}, },
client::{Client, ClientId}, client::{Client, ClientId},
cmm::cmm_transfer_function::TransferFunction, cmm::cmm_transfer_function::TransferFunction,
@ -1141,12 +1142,16 @@ impl ConfigProxyHandler {
connector: Connector, connector: Connector,
mode: WireMode, mode: WireMode,
) -> Result<(), CphError> { ) -> Result<(), CphError> {
let connector = self.get_output(connector)?; let connector = self.get_connector(connector)?;
connector.connector.connector.set_mode(backend::Mode { connector
width: mode.width, .modify_state(&self.state, |s| {
height: mode.height, s.mode = backend::Mode {
refresh_rate_millihz: mode.refresh_millihz, width: mode.width,
}); height: mode.height,
refresh_rate_millihz: mode.refresh_millihz,
};
})
.map_err(CphError::ModifyConnectorState)?;
Ok(()) Ok(())
} }
@ -1265,7 +1270,9 @@ impl ConfigProxyHandler {
return Err(CphError::UnknownFormat(format)); return Err(CphError::UnknownFormat(format));
}; };
let connector = self.get_connector(connector)?; let connector = self.get_connector(connector)?;
connector.connector.set_fb_format(format); connector
.modify_state(&self.state, |s| s.format = format)
.map_err(CphError::ModifyConnectorState)?;
Ok(()) Ok(())
} }
@ -1286,7 +1293,12 @@ impl ConfigProxyHandler {
_ => return Err(CphError::UnknownTransferFunction(transfer_function)), _ => return Err(CphError::UnknownTransferFunction(transfer_function)),
}; };
let connector = self.get_connector(connector)?; let connector = self.get_connector(connector)?;
connector.connector.set_colors(bcs, btf); connector
.modify_state(&self.state, |s| {
s.color_space = bcs;
s.transfer_function = btf;
})
.map_err(CphError::ModifyConnectorState)?;
Ok(()) Ok(())
} }
@ -1447,7 +1459,11 @@ impl ConfigProxyHandler {
enabled: bool, enabled: bool,
) -> Result<(), CphError> { ) -> Result<(), CphError> {
let connector = self.get_connector(connector)?; let connector = self.get_connector(connector)?;
connector.connector.set_enabled(enabled); connector
.modify_state(&self.state, |s| {
s.enabled = enabled;
})
.map_err(CphError::ModifyConnectorState)?;
Ok(()) Ok(())
} }
@ -3057,6 +3073,8 @@ enum CphError {
InvalidRegex(#[source] regex::Error), InvalidRegex(#[source] regex::Error),
#[error("Window matcher {0:?} does not exist")] #[error("Window matcher {0:?} does not exist")]
WindowMatcherDoesNotExist(WindowMatcher), WindowMatcherDoesNotExist(WindowMatcher),
#[error("Could not modify the connector state")]
ModifyConnectorState(#[source] BackendConnectorTransactionError),
} }
trait WithRequestName { trait WithRequestName {

View file

@ -60,6 +60,7 @@ impl ExtSessionLockManagerV1RequestHandler for ExtSessionLockManagerV1 {
client: self.client.clone(), client: self.client.clone(),
tracker: Default::default(), tracker: Default::default(),
did_lock, did_lock,
awaiting_locked: Cell::new(true),
finished: Cell::new(false), finished: Cell::new(false),
version: self.version, version: self.version,
}); });
@ -75,7 +76,7 @@ impl ExtSessionLockManagerV1RequestHandler for ExtSessionLockManagerV1 {
state.lock.lock.set(Some(new.clone())); state.lock.lock.set(Some(new.clone()));
state.tree_changed(); state.tree_changed();
state.damage(state.root.extents.get()); state.damage(state.root.extents.get());
new.send_locked(); new.check_locked();
} else { } else {
new.finish(); new.finish();
} }

View file

@ -17,12 +17,26 @@ pub struct ExtSessionLockV1 {
pub client: Rc<Client>, pub client: Rc<Client>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub did_lock: bool, pub did_lock: bool,
pub awaiting_locked: Cell<bool>,
pub finished: Cell<bool>, pub finished: Cell<bool>,
pub version: Version, pub version: Version,
} }
impl ExtSessionLockV1 { impl ExtSessionLockV1 {
pub fn send_locked(&self) { pub fn check_locked(&self) {
if !self.awaiting_locked.get() {
return;
}
for output in self.client.state.outputs.lock().values() {
if !output.connector.connector.effectively_locked() {
return;
}
}
self.send_locked();
self.awaiting_locked.set(false);
}
fn send_locked(&self) {
self.client.event(Locked { self_id: self.id }) self.client.event(Locked { self_id: self.id })
} }

View file

@ -7,9 +7,9 @@ use {
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
scale::Scale, scale::Scale,
state::{ConnectorData, DrmDevData, OutputData}, state::{ConnectorData, DrmDevData, OutputData, State},
tree::{OutputNode, TearingMode, VrrMode}, tree::{OutputNode, TearingMode, VrrMode},
utils::{gfx_api_ext::GfxApiExt, transform_ext::TransformExt}, utils::{errorfmt::ErrorFmt, gfx_api_ext::GfxApiExt, transform_ext::TransformExt},
wire::{JayRandrId, jay_randr::*}, wire::{JayRandrId, jay_randr::*},
}, },
jay_config::video::{ jay_config::video::{
@ -23,6 +23,7 @@ use {
pub struct JayRandr { pub struct JayRandr {
pub id: JayRandrId, pub id: JayRandrId,
pub client: Rc<Client>, pub client: Rc<Client>,
pub state: Rc<State>,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
pub version: Version, pub version: Version,
} }
@ -39,6 +40,7 @@ impl JayRandr {
Self { Self {
id, id,
client: client.clone(), client: client.clone(),
state: client.state.clone(),
tracker: Default::default(), tracker: Default::default(),
version, version,
} }
@ -67,6 +69,7 @@ impl JayRandr {
} }
fn send_connector(&self, data: &ConnectorData) { fn send_connector(&self, data: &ConnectorData) {
let state = data.state.get();
self.client.event(Connector { self.client.event(Connector {
self_id: self.id, self_id: self.id,
id: data.connector.id().raw() as _, id: data.connector.id().raw() as _,
@ -75,7 +78,7 @@ impl JayRandr {
.as_ref() .as_ref()
.map(|d| d.dev.id().raw() as _) .map(|d| d.dev.id().raw() as _)
.unwrap_or_default(), .unwrap_or_default(),
enabled: data.connector.enabled() as _, enabled: state.enabled as _,
name: &data.name, name: &data.name,
}); });
let Some(output) = self.client.state.outputs.get(&data.connector.id()) else { let Some(output) = self.client.state.outputs.get(&data.connector.id()) else {
@ -347,14 +350,19 @@ impl JayRandrRequestHandler for JayRandr {
} }
fn set_mode(&self, req: SetMode, _slf: &Rc<Self>) -> Result<(), Self::Error> { fn set_mode(&self, req: SetMode, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_output(req.output) else { let Some(c) = self.get_connector(req.output) else {
return Ok(()); return Ok(());
}; };
c.connector.connector.set_mode(backend::Mode { let res = c.modify_state(&self.state, |s| {
width: req.width, s.mode = backend::Mode {
height: req.height, width: req.width,
refresh_rate_millihz: req.refresh_rate_millihz, height: req.height,
refresh_rate_millihz: req.refresh_rate_millihz,
};
}); });
if let Err(e) = res {
self.send_error(&format!("Could not modify connector mode: {}", ErrorFmt(e)));
}
Ok(()) Ok(())
} }
@ -378,7 +386,10 @@ impl JayRandrRequestHandler for JayRandr {
let Some(c) = self.get_connector(req.output) else { let Some(c) = self.get_connector(req.output) else {
return Ok(()); return Ok(());
}; };
c.connector.set_enabled(req.enabled != 0); let res = c.modify_state(&self.state, |s| s.enabled = req.enabled != 0);
if let Err(e) = res {
self.send_error(&format!("Could not en/disable connector: {}", ErrorFmt(e)));
}
Ok(()) Ok(())
} }
@ -391,7 +402,13 @@ impl JayRandrRequestHandler for JayRandr {
1 => Some(false), 1 => Some(false),
_ => Some(true), _ => Some(true),
}; };
c.connector.set_non_desktop_override(non_desktop); c.connector.before_non_desktop_override_update(non_desktop);
let res = c.modify_state(&self.state, |s| {
s.non_desktop_override = non_desktop;
});
if let Err(e) = res {
self.send_error(&format!("Could not change non-desktop override: {}", e));
}
Ok(()) Ok(())
} }
@ -439,10 +456,13 @@ impl JayRandrRequestHandler for JayRandr {
let Some(&format) = named_formats().get(req.format) else { let Some(&format) = named_formats().get(req.format) else {
return Err(JayRandrError::UnknownFormat(req.format.to_string())); return Err(JayRandrError::UnknownFormat(req.format.to_string()));
}; };
let Some(c) = self.get_output_node(req.output) else { let Some(c) = self.get_connector(req.output) else {
return Ok(()); return Ok(());
}; };
c.global.connector.connector.set_fb_format(format); let res = c.modify_state(&self.state, |s| s.format = format);
if let Err(e) = res {
self.send_error(&format!("Could not modify connector format: {}", e));
}
Ok(()) Ok(())
} }
@ -478,7 +498,16 @@ impl JayRandrRequestHandler for JayRandr {
let Some(c) = self.get_connector(req.output) else { let Some(c) = self.get_connector(req.output) else {
return Ok(()); return Ok(());
}; };
c.connector.set_colors(cs, tf); let res = c.modify_state(&self.state, |s| {
s.color_space = cs;
s.transfer_function = tf;
});
if let Err(e) = res {
self.send_error(&format!(
"Could not modify connector colors: {}",
ErrorFmt(e),
));
}
Ok(()) Ok(())
} }

View file

@ -161,22 +161,20 @@ impl WlOutputGlobal {
state: &Rc<State>, state: &Rc<State>,
connector: &Rc<ConnectorData>, connector: &Rc<ConnectorData>,
modes: Vec<backend::Mode>, modes: Vec<backend::Mode>,
mode: &backend::Mode,
width_mm: i32, width_mm: i32,
height_mm: i32, height_mm: i32,
output_id: &Rc<OutputId>, output_id: &Rc<OutputId>,
persistent_state: &Rc<PersistentOutputState>, persistent_state: &Rc<PersistentOutputState>,
transfer_functions: Vec<BackendTransferFunction>, transfer_functions: Vec<BackendTransferFunction>,
btf: BackendTransferFunction,
color_spaces: Vec<BackendColorSpace>, color_spaces: Vec<BackendColorSpace>,
bcs: BackendColorSpace,
primaries: Primaries, primaries: Primaries,
luminance: Option<BackendLuminance>, luminance: Option<BackendLuminance>,
) -> Self { ) -> Self {
let (x, y) = persistent_state.pos.get(); let (x, y) = persistent_state.pos.get();
let scale = persistent_state.scale.get(); let scale = persistent_state.scale.get();
let connector_state = connector.state.get();
let (width, height) = calculate_logical_size( let (width, height) = calculate_logical_size(
(mode.width, mode.height), (connector_state.mode.width, connector_state.mode.height),
persistent_state.transform.get(), persistent_state.transform.get(),
scale, scale,
); );
@ -186,8 +184,8 @@ impl WlOutputGlobal {
connector: connector.clone(), connector: connector.clone(),
pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()), pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()),
output_id: output_id.clone(), output_id: output_id.clone(),
mode: Cell::new(*mode), mode: Cell::new(connector_state.mode),
refresh_nsec: Cell::new(mode.refresh_nsec()), refresh_nsec: Cell::new(connector_state.mode.refresh_nsec()),
modes, modes,
formats: CloneCell::new(Rc::new(vec![])), formats: CloneCell::new(Rc::new(vec![])),
format: Cell::new(XRGB8888), format: Cell::new(XRGB8888),
@ -203,8 +201,8 @@ impl WlOutputGlobal {
persistent: persistent_state.clone(), persistent: persistent_state.clone(),
opt: Default::default(), opt: Default::default(),
damage_matrix: Default::default(), damage_matrix: Default::default(),
btf: Cell::new(btf), btf: Cell::new(connector_state.transfer_function),
bcs: Cell::new(bcs), bcs: Cell::new(connector_state.color_space),
color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()), color_description: CloneCell::new(state.color_manager.srgb_srgb().clone()),
linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()), linear_color_description: CloneCell::new(state.color_manager.srgb_linear().clone()),
color_description_listeners: Default::default(), color_description_listeners: Default::default(),

View file

@ -3,15 +3,21 @@ use {
allocator::{Allocator, AllocatorError}, allocator::{Allocator, AllocatorError},
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
backend::{ backend::{
AxisSource, Backend, BackendColorSpace, BackendEvent, BackendTransferFunction, AxisSource, Backend, BackendConnectorState, BackendEvent, Connector, ConnectorEvent,
Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, ConnectorId, ConnectorKernelId, DrmDeviceId, InputDevice, InputDeviceAccelProfile,
InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState,
InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis, TransformMatrix, Mode, MonitorInfo, ScrollAxis, TransformMatrix,
transaction::{
BackendAppliedConnectorTransaction, BackendConnectorTransaction,
BackendConnectorTransactionError, BackendConnectorTransactionType,
BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction,
},
}, },
cmm::cmm_primaries::Primaries, cmm::cmm_primaries::Primaries,
compositor::TestFuture, compositor::TestFuture,
drm_feedback::DrmFeedback, drm_feedback::DrmFeedback,
fixed::Fixed, fixed::Fixed,
format::XRGB8888,
gfx_api::GfxError, gfx_api::GfxError,
gfx_apis::create_vulkan_allocator, gfx_apis::create_vulkan_allocator,
ifs::wl_output::OutputId, ifs::wl_output::OutputId,
@ -29,8 +35,9 @@ use {
gbm::{GbmDevice, GbmError}, gbm::{GbmDevice, GbmError},
}, },
}, },
ahash::AHashMap,
bstr::ByteSlice, bstr::ByteSlice,
std::{cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc}, std::{any::Any, cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc},
thiserror::Error, thiserror::Error,
uapi::c, uapi::c,
}; };
@ -63,7 +70,6 @@ pub struct TestBackend {
pub default_mouse: Rc<TestBackendMouse>, pub default_mouse: Rc<TestBackendMouse>,
pub default_kb: Rc<TestBackendKb>, pub default_kb: Rc<TestBackendKb>,
pub render_context_installed: Cell<bool>, pub render_context_installed: Cell<bool>,
pub idle: TEEH<bool>,
} }
impl TestBackend { impl TestBackend {
@ -77,6 +83,7 @@ impl TestBackend {
}, },
events: Default::default(), events: Default::default(),
feedback: Default::default(), feedback: Default::default(),
idle: Default::default(),
}); });
let default_mouse = Rc::new(TestBackendMouse { let default_mouse = Rc::new(TestBackendMouse {
common: TestInputDeviceCommon { common: TestInputDeviceCommon {
@ -125,17 +132,26 @@ impl TestBackend {
model: "TestConnector".to_string(), model: "TestConnector".to_string(),
serial_number: default_connector.id.to_string(), serial_number: default_connector.id.to_string(),
}), }),
initial_mode: mode,
width_mm: 80, width_mm: 80,
height_mm: 60, height_mm: 60,
non_desktop: false, non_desktop: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], transfer_functions: vec![],
transfer_function: BackendTransferFunction::Default,
color_spaces: vec![], color_spaces: vec![],
color_space: BackendColorSpace::Default,
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
state: BackendConnectorState {
serial: state.backend_connector_state_serials.next(),
enabled: true,
active: true,
mode,
non_desktop_override: None,
vrr: false,
tearing: false,
format: XRGB8888,
color_space: Default::default(),
transfer_function: Default::default(),
},
}; };
Self { Self {
state: state.clone(), state: state.clone(),
@ -145,7 +161,6 @@ impl TestBackend {
default_mouse, default_mouse,
default_kb, default_kb,
render_context_installed: Cell::new(false), render_context_installed: Cell::new(false),
idle: Rc::new(Default::default()),
} }
} }
@ -291,10 +306,6 @@ impl Backend for TestBackend {
let _ = vtnr; let _ = vtnr;
} }
fn set_idle(&self, idle: bool) {
self.idle.push(idle);
}
fn supports_presentation_feedback(&self) -> bool { fn supports_presentation_feedback(&self) -> bool {
true true
} }
@ -305,6 +316,7 @@ pub struct TestConnector {
pub kernel_id: ConnectorKernelId, pub kernel_id: ConnectorKernelId,
pub events: OnChange<ConnectorEvent>, pub events: OnChange<ConnectorEvent>,
pub feedback: CloneCell<Option<Rc<DrmFeedback>>>, pub feedback: CloneCell<Option<Rc<DrmFeedback>>>,
pub idle: TEEH<bool>,
} }
impl Connector for TestConnector { impl Connector for TestConnector {
@ -332,13 +344,70 @@ impl Connector for TestConnector {
None None
} }
fn set_mode(&self, _mode: Mode) { fn effectively_locked(&self) -> bool {
// todo // todo
true
} }
fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> { fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
self.feedback.get() self.feedback.get()
} }
fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
Box::new(TestBackendTransactionType)
}
fn create_transaction(
&self,
) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
Ok(Box::new(TestBackendTransaction::default()))
}
}
#[derive(Hash, Eq, PartialEq)]
struct TestBackendTransactionType;
impl BackendConnectorTransactionType for TestBackendTransactionType {}
#[derive(Default)]
struct TestBackendTransaction {
connectors: AHashMap<ConnectorId, (Rc<TestConnector>, BackendConnectorState)>,
}
impl BackendConnectorTransaction for TestBackendTransaction {
fn add(
&mut self,
connector: &Rc<dyn Connector>,
change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let c = (connector.clone() as Rc<dyn Any>)
.downcast::<TestConnector>()
.unwrap();
self.connectors.insert(c.id(), (c, change));
Ok(())
}
fn prepare(
self: Box<Self>,
) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>
{
Ok(self)
}
}
impl BackendPreparedConnectorTransaction for TestBackendTransaction {
fn apply(
self: Box<Self>,
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
for (c, s) in self.connectors.values() {
c.idle.push(!s.active);
}
Ok(self)
}
}
impl BackendAppliedConnectorTransaction for TestBackendTransaction {
fn commit(self: Box<Self>) {
// nothing
}
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
unimplemented!()
}
} }
pub struct TestMouseClick { pub struct TestMouseClick {

View file

@ -1,10 +1,10 @@
use { use {
crate::{ crate::{
backend::{ backend::{
BackendColorSpace, BackendEvent, BackendTransferFunction, ConnectorEvent, BackendConnectorState, BackendEvent, ConnectorEvent, ConnectorKernelId, MonitorInfo,
ConnectorKernelId, Mode, MonitorInfo,
}, },
cmm::cmm_primaries::Primaries, cmm::cmm_primaries::Primaries,
format::XRGB8888,
ifs::wl_output::OutputId, ifs::wl_output::OutputId,
it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun}, it::{test_backend::TestConnector, test_error::TestResult, testrun::TestRun},
video::drm::ConnectorType, video::drm::ConnectorType,
@ -34,6 +34,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
}, },
events: Default::default(), events: Default::default(),
feedback: Default::default(), feedback: Default::default(),
idle: Default::default(),
}); });
let new_monitor_info = MonitorInfo { let new_monitor_info = MonitorInfo {
modes: vec![], modes: vec![],
@ -43,21 +44,26 @@ async fn test(run: Rc<TestRun>) -> TestResult {
model: "jay second connector".to_string(), model: "jay second connector".to_string(),
serial_number: "".to_string(), serial_number: "".to_string(),
}), }),
initial_mode: Mode {
width: 400,
height: 400,
refresh_rate_millihz: 60000,
},
width_mm: 0, width_mm: 0,
height_mm: 0, height_mm: 0,
non_desktop: false, non_desktop: false,
vrr_capable: false, vrr_capable: false,
transfer_functions: vec![], transfer_functions: vec![],
transfer_function: BackendTransferFunction::Default,
color_spaces: vec![], color_spaces: vec![],
color_space: BackendColorSpace::Default,
primaries: Primaries::SRGB, primaries: Primaries::SRGB,
luminance: None, luminance: None,
state: BackendConnectorState {
serial: run.state.backend_connector_state_serials.next(),
enabled: true,
active: true,
mode: Default::default(),
non_desktop_override: None,
vrr: false,
tearing: false,
format: XRGB8888,
color_space: Default::default(),
transfer_function: Default::default(),
},
}; };
run.backend run.backend
.state .state

View file

@ -14,7 +14,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
run.cfg.set_idle(Duration::from_micros(100))?; run.cfg.set_idle(Duration::from_micros(100))?;
run.cfg.set_idle_grace_period(Duration::from_secs(0))?; run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
let idle = run.backend.idle.expect()?; let idle = ds.connector.idle.expect()?;
tassert!(idle.next().is_err()); tassert!(idle.next().is_err());
run.state.wheel.timeout(3).await?; run.state.wheel.timeout(3).await?;

View file

@ -205,7 +205,7 @@ macro_rules! linear_ids {
} }
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct $id($ty); pub struct $id($ty);
impl $id { impl $id {
@ -772,3 +772,22 @@ macro_rules! jay_allow_realtime_config_so {
"JAY_ALLOW_REALTIME_CONFIG_SO" "JAY_ALLOW_REALTIME_CONFIG_SO"
}; };
} }
#[allow(clippy::allow_attributes, unused_macros)]
macro_rules! dbg {
($val:expr) => {
match $val {
tmp => {
log::warn!(
"[{}:{}:{}] {} = {:#?}",
file!(),
line!(),
column!(),
stringify!($val),
&tmp
);
tmp
}
}
};
}

View file

@ -327,6 +327,13 @@ impl DamageQueue {
data.clear(); data.clear();
} }
pub fn clear_all(&self) {
let datas = unsafe { self.datas.get().deref_mut() };
for data in datas {
data.clear();
}
}
pub fn get(&self) -> Region { pub fn get(&self) -> Region {
let data = unsafe { &self.datas.get().deref()[self.this] }; let data = unsafe { &self.datas.get().deref()[self.this] };
Region::from_rects2(data) Region::from_rects2(data)

View file

@ -3,9 +3,10 @@ use {
acceptor::Acceptor, acceptor::Acceptor,
async_engine::{AsyncEngine, SpawnedFuture}, async_engine::{AsyncEngine, SpawnedFuture},
backend::{ backend::{
Backend, BackendDrmDevice, BackendEvent, Connector, ConnectorId, ConnectorIds, Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice,
DrmDeviceId, DrmDeviceIds, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, BackendEvent, Connector, ConnectorId, ConnectorIds, DrmDeviceId, DrmDeviceIds,
InputDeviceId, InputDeviceIds, MonitorInfo, HardwareCursorUpdate, InputDevice, InputDeviceGroupIds, InputDeviceId, InputDeviceIds,
MonitorInfo, transaction::BackendConnectorTransactionError,
}, },
backends::dummy::DummyBackend, backends::dummy::DummyBackend,
cli::RunArgs, cli::RunArgs,
@ -255,6 +256,7 @@ pub struct State {
pub caps_thread: Option<PrCapsThread>, pub caps_thread: Option<PrCapsThread>,
pub node_at_tree: RefCell<Vec<FoundNode>>, pub node_at_tree: RefCell<Vec<FoundNode>>,
pub position_hint_requests: AsyncQueue<PositionHintRequest>, pub position_hint_requests: AsyncQueue<PositionHintRequest>,
pub backend_connector_state_serials: BackendConnectorStateSerials,
} }
// impl Drop for State { // impl Drop for State {
@ -376,6 +378,7 @@ pub struct ConnectorData {
pub damage: RefCell<Vec<Rect>>, pub damage: RefCell<Vec<Rect>>,
pub needs_vblank_emulation: Cell<bool>, pub needs_vblank_emulation: Cell<bool>,
pub damage_intersect: Cell<Rect>, pub damage_intersect: Cell<Rect>,
pub state: Cell<BackendConnectorState>,
} }
pub struct OutputData { pub struct OutputData {
@ -403,6 +406,31 @@ impl ConnectorData {
self.connector.damage(); self.connector.damage();
} }
} }
pub fn modify_state(
&self,
state: &State,
f: impl FnOnce(&mut BackendConnectorState),
) -> Result<(), BackendConnectorTransactionError> {
let old = self.state.get();
let mut s = old;
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.prepare()?.apply()?.commit();
if let Some(output) = state.outputs.get(&self.connector.id())
&& let Some(node) = &output.node
{
node.update_state(s);
} else {
self.state.set(s);
}
Ok(())
}
} }
impl DrmDevData { impl DrmDevData {

View file

@ -1,6 +1,10 @@
use { use {
crate::{ crate::{
backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}, backend::{
BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent,
ConnectorId, MonitorInfo,
},
format::XRGB8888,
globals::GlobalName, globals::GlobalName,
ifs::{ ifs::{
jay_tray_v1::JayTrayV1Global, jay_tray_v1::JayTrayV1Global,
@ -22,6 +26,18 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
_ => panic!("connector's drm device does not exist"), _ => panic!("connector's drm device does not exist"),
}; };
} }
let backend_state = BackendConnectorState {
serial: BackendConnectorStateSerial::from_raw(0),
enabled: true,
active: false,
mode: Default::default(),
non_desktop_override: None,
vrr: false,
tearing: false,
format: XRGB8888,
color_space: Default::default(),
transfer_function: Default::default(),
};
let id = connector.id(); let id = connector.id();
let data = Rc::new(ConnectorData { let data = Rc::new(ConnectorData {
connector: connector.clone(), connector: connector.clone(),
@ -34,6 +50,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
damage: Default::default(), damage: Default::default(),
needs_vblank_emulation: Cell::new(false), needs_vblank_emulation: Cell::new(false),
damage_intersect: Default::default(), damage_intersect: Default::default(),
state: Cell::new(backend_state),
}); });
if let Some(dev) = drm_dev { if let Some(dev) = drm_dev {
dev.connectors.set(id, data.clone()); dev.connectors.set(id, data.clone());
@ -88,6 +105,10 @@ impl ConnectorHandler {
async fn handle_connected(&self, info: MonitorInfo) { async fn handle_connected(&self, info: MonitorInfo) {
log::info!("Connector {} connected", self.data.connector.kernel_id()); log::info!("Connector {} connected", self.data.connector.kernel_id());
self.data.connected.set(true); self.data.connected.set(true);
let old_state = self.data.state.get();
if old_state.serial < info.state.serial {
self.data.state.set(info.state);
}
let name = self.state.globals.name(); let name = self.state.globals.name();
if info.non_desktop { if info.non_desktop {
self.handle_non_desktop_connected(info).await; self.handle_non_desktop_connected(info).await;
@ -132,15 +153,12 @@ impl ConnectorHandler {
&self.state, &self.state,
&self.data, &self.data,
info.modes.clone(), info.modes.clone(),
&info.initial_mode,
info.width_mm, info.width_mm,
info.height_mm, info.height_mm,
&output_id, &output_id,
&desired_state, &desired_state,
info.transfer_functions.clone(), info.transfer_functions.clone(),
info.transfer_function,
info.color_spaces.clone(), info.color_spaces.clone(),
info.color_space,
info.primaries, info.primaries,
info.luminance, info.luminance,
)); ));
@ -268,18 +286,11 @@ impl ConnectorHandler {
on.hardware_cursor.set(hc); on.hardware_cursor.set(hc);
self.state.refresh_hardware_cursors(); self.state.refresh_hardware_cursors();
} }
ConnectorEvent::ModeChanged(mode) => { ConnectorEvent::FormatsChanged(formats) => {
on.update_mode(mode);
}
ConnectorEvent::VrrChanged(enabled) => {
on.schedule.set_vrr_enabled(enabled);
}
ConnectorEvent::FormatsChanged(formats, format) => {
on.global.formats.set(formats); on.global.formats.set(formats);
on.global.format.set(format);
} }
ConnectorEvent::ColorsChanged(bcs, btf) => { ConnectorEvent::State(state) => {
on.update_btf_and_bcs(btf, bcs); on.update_state(state);
} }
ev => unreachable!("received unexpected event {:?}", ev), ev => unreachable!("received unexpected event {:?}", ev),
} }

View file

@ -1,6 +1,6 @@
use { use {
crate::{ crate::{
backend::Backend, backend::transaction::{BackendConnectorTransactionError, ConnectorTransaction},
state::State, state::State,
utils::{ utils::{
errorfmt::ErrorFmt, errorfmt::ErrorFmt,
@ -12,7 +12,7 @@ use {
uapi::c, uapi::c,
}; };
pub async fn idle(state: Rc<State>, backend: Rc<dyn Backend>) { pub async fn idle(state: Rc<State>) {
let timer = match TimerFd::new(c::CLOCK_MONOTONIC) { let timer = match TimerFd::new(c::CLOCK_MONOTONIC) {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
@ -24,7 +24,6 @@ pub async fn idle(state: Rc<State>, backend: Rc<dyn Backend>) {
state.idle.timeout_changed.set(true); state.idle.timeout_changed.set(true);
let mut idle = Idle { let mut idle = Idle {
state, state,
backend,
timer, timer,
idle: false, idle: false,
dead: false, dead: false,
@ -36,7 +35,6 @@ pub async fn idle(state: Rc<State>, backend: Rc<dyn Backend>) {
struct Idle { struct Idle {
state: Rc<State>, state: Rc<State>,
backend: Rc<dyn Backend>,
timer: TimerFd, timer: TimerFd,
idle: bool, idle: bool,
dead: bool, dead: bool,
@ -71,7 +69,7 @@ impl Idle {
if let Some(config) = self.state.config.get() { if let Some(config) = self.state.config.get() {
config.idle(); config.idle();
} }
self.backend.set_idle(true); self.set_idle(true);
self.idle = true; self.idle = true;
} }
} else if since >= timeout { } else if since >= timeout {
@ -110,7 +108,7 @@ impl Idle {
self.last_input = now(); self.last_input = now();
self.set_in_grace_period(false); self.set_in_grace_period(false);
if self.idle { if self.idle {
self.backend.set_idle(false); self.set_idle(false);
self.idle = false; self.idle = false;
self.program_timer(); self.program_timer();
} }
@ -127,6 +125,27 @@ impl Idle {
self.dead = true; self.dead = true;
} }
} }
fn set_idle(&self, idle: bool) {
if let Err(e) = self.try_set_idle(idle) {
log::error!("Could not change idle status of backend: {}", ErrorFmt(e))
}
if let Some(lock) = self.state.lock.lock.get() {
lock.check_locked();
}
}
fn try_set_idle(&self, idle: bool) -> Result<(), BackendConnectorTransactionError> {
let mut tran = ConnectorTransaction::default();
for connector in self.state.connectors.lock().values() {
let mut state = connector.state.get();
state.active = !idle;
tran.add(&connector.connector, state)?;
}
tran.prepare()?.apply()?.commit();
self.state.set_backend_idle(idle);
Ok(())
}
} }
fn now() -> c::timespec { fn now() -> c::timespec {

View file

@ -1,6 +1,9 @@
use { use {
crate::{ crate::{
backend::{BackendColorSpace, BackendTransferFunction, HardwareCursor, KeyState, Mode}, backend::{
BackendColorSpace, BackendConnectorState, BackendTransferFunction, HardwareCursor,
KeyState, Mode,
},
client::ClientId, client::ClientId,
cmm::cmm_description::ColorDescription, cmm::cmm_description::ColorDescription,
cursor::KnownCursor, cursor::KnownCursor,
@ -207,10 +210,14 @@ impl OutputNode {
seq: u64, seq: u64,
flags: u32, flags: u32,
vrr: bool, vrr: bool,
locked: bool,
) { ) {
for listener in self.presentation_event.iter() { for listener in self.presentation_event.iter() {
listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr); listener.presented(self, tv_sec, tv_nsec, refresh, seq, flags, vrr);
} }
if locked && let Some(lock) = self.state.lock.lock.get() {
lock.check_locked()
}
} }
pub fn update_exclusive_zones(self: &Rc<Self>) { pub fn update_exclusive_zones(self: &Rc<Self>) {
@ -842,7 +849,23 @@ impl OutputNode {
self.state.tree_changed(); self.state.tree_changed();
} }
pub fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) { pub fn update_state(self: &Rc<Self>, state: BackendConnectorState) {
let old = self.global.connector.state.get();
if old.serial >= state.serial {
return;
}
self.global.connector.state.set(state);
self.update_btf_and_bcs(state.transfer_function, state.color_space);
if old.vrr != state.vrr {
self.schedule.set_vrr_enabled(state.vrr);
}
if old.mode != state.mode {
self.update_mode(state.mode);
}
self.global.format.set(state.format);
}
fn update_btf_and_bcs(&self, btf: BackendTransferFunction, bcs: BackendColorSpace) {
let old_btf = self.global.btf.replace(btf); let old_btf = self.global.btf.replace(btf);
let old_bcs = self.global.bcs.replace(bcs); let old_bcs = self.global.bcs.replace(bcs);
if (old_btf, old_bcs) == (btf, bcs) { if (old_btf, old_bcs) == (btf, bcs) {
@ -1067,7 +1090,13 @@ impl OutputNode {
true true
} }
}; };
self.global.connector.connector.set_vrr_enabled(enabled); let res = self
.global
.connector
.modify_state(&self.state, |s| s.vrr = enabled);
if let Err(e) = res {
log::error!("Could not set vrr mode: {}", e);
}
} }
fn update_tearing(&self) { fn update_tearing(&self) {
@ -1094,7 +1123,13 @@ impl OutputNode {
true true
} }
}; };
self.global.connector.connector.set_tearing_enabled(enabled); let res = self
.global
.connector
.modify_state(&self.state, |s| s.tearing = enabled);
if let Err(e) = res {
log::error!("Could not set tearing mode: {}", e);
}
} }
pub fn tile_drag_destination( pub fn tile_drag_destination(

View file

@ -2,6 +2,7 @@ pub mod activation_token;
pub mod array; pub mod array;
pub mod array_to_tuple; pub mod array_to_tuple;
pub mod asyncevent; pub mod asyncevent;
pub mod binary_search_map;
pub mod bindings; pub mod bindings;
pub mod bitfield; pub mod bitfield;
pub mod bitflags; pub mod bitflags;

View file

@ -0,0 +1,219 @@
use {
crate::utils::ptr_ext::{MutPtrExt, PtrExt},
smallvec::SmallVec,
std::{
fmt::{Debug, Formatter},
mem,
},
};
pub struct BinarySearchMap<K, V, const N: usize> {
m: SmallVec<[(K, V); N]>,
}
impl<K: Debug, V: Debug, const N: usize> Debug for BinarySearchMap<K, V, N> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.m.iter().map(|e| (&e.0, &e.1)))
.finish()
}
}
impl<K, V, const N: usize> Default for BinarySearchMap<K, V, N> {
fn default() -> Self {
Self {
m: Default::default(),
}
}
}
impl<K, V, const N: usize> BinarySearchMap<K, V, N> {
pub fn new_with(k: K, v: V) -> Self {
let mut sv = SmallVec::new();
sv.push((k, v));
Self { m: sv }
}
pub fn new() -> Self {
Self {
m: SmallVec::new_const(),
}
}
pub fn len(&self) -> usize {
self.m.len()
}
fn pos(&self, k: &K) -> Result<usize, usize>
where
K: Ord + Eq,
{
self.m.binary_search_by(|(c, _)| c.cmp(k))
}
pub fn contains(&self, k: &K) -> bool
where
K: Ord + Eq,
{
self.pos(k).is_ok()
}
pub fn not_contains(&self, k: &K) -> bool
where
K: Ord + Eq,
{
!self.contains(k)
}
pub fn insert(&mut self, k: K, v: V) -> Option<V>
where
K: Ord + Eq,
{
match self.pos(&k) {
Ok(p) => Some(mem::replace(&mut self.m[p], (k, v)).1),
Err(p) => {
self.m.insert(p, (k, v));
None
}
}
}
pub fn get(&self, k: &K) -> Option<&V>
where
K: Ord + Eq,
{
self.pos(k).ok().map(|p| &self.m[p].1)
}
pub fn get_mut(&mut self, k: &K) -> Option<&mut V>
where
K: Ord + Eq,
{
self.pos(k).ok().map(|p| &mut self.m[p].1)
}
pub fn get_or_default_mut(&mut self, k: K) -> &mut V
where
K: Ord + Eq,
V: Default,
{
self.get_or_insert_with(k, || V::default())
}
pub fn get_or_insert_with<F>(&mut self, k: K, f: F) -> &mut V
where
K: Ord + Eq,
F: FnOnce() -> V,
{
let p = match self.pos(&k) {
Ok(p) => return &mut self.m[p].1,
Err(p) => p,
};
self.m.insert(p, (k, f()));
&mut self.m[p].1
}
pub fn is_empty(&self) -> bool {
self.m.is_empty()
}
pub fn remove(&mut self, k: &K) -> Option<V>
where
K: Ord + Eq,
{
if let Ok(p) = self.pos(k) {
return Some(self.m.remove(p).1);
}
None
}
pub fn clear(&mut self) {
let _v = mem::replace(&mut self.m, SmallVec::new());
}
pub fn take(&mut self) -> SmallVec<[(K, V); N]> {
mem::take(&mut self.m)
}
pub fn iter<'a>(&'a self) -> BinarySearchMapIter<'a, K, V, N> {
BinarySearchMapIter { pos: 0, map: self }
}
pub fn values<'a>(&'a self) -> impl Iterator<Item = &'a V> + 'a {
self.iter().map(|(_, v)| v)
}
pub fn iter_mut<'a>(&'a mut self) -> BinarySearchMapMutIterMut<'a, K, V, N> {
BinarySearchMapMutIterMut { pos: 0, map: self }
}
pub fn values_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut V> + 'a {
self.iter_mut().map(|(_, v)| v)
}
pub fn remove_if<F: FnMut(&K, &V) -> bool>(&mut self, mut f: F) {
let mut i = 0;
while i < self.m.len() {
let (k, v) = &self.m[i];
if f(k, v) {
self.m.remove(i);
} else {
i += 1;
}
}
}
}
impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a BinarySearchMap<K, V, N> {
type Item = (&'a K, &'a V);
type IntoIter = BinarySearchMapIter<'a, K, V, N>;
fn into_iter(self) -> Self::IntoIter {
BinarySearchMapIter { pos: 0, map: self }
}
}
impl<'a, K: Copy, V, const N: usize> IntoIterator for &'a mut BinarySearchMap<K, V, N> {
type Item = (&'a K, &'a mut V);
type IntoIter = BinarySearchMapMutIterMut<'a, K, V, N>;
fn into_iter(self) -> Self::IntoIter {
BinarySearchMapMutIterMut { pos: 0, map: self }
}
}
pub struct BinarySearchMapIter<'a, K, V, const N: usize> {
pos: usize,
map: &'a BinarySearchMap<K, V, N>,
}
impl<'a, K, V, const N: usize> Iterator for BinarySearchMapIter<'a, K, V, N> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.map.m.len() {
return None;
}
let (k, v) = &self.map.m[self.pos];
self.pos += 1;
Some((k, v))
}
}
pub struct BinarySearchMapMutIterMut<'a, K, V, const N: usize> {
pos: usize,
map: &'a mut BinarySearchMap<K, V, N>,
}
impl<'a, K, V, const N: usize> Iterator for BinarySearchMapMutIterMut<'a, K, V, N> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.map.m.len() {
return None;
}
let (k, v) = &mut self.map.m[self.pos];
self.pos += 1;
unsafe { Some(((k as *const K).deref(), (v as *mut V).deref_mut())) }
}
}

View file

@ -710,7 +710,7 @@ pub trait DrmObject {
macro_rules! drm_obj { macro_rules! drm_obj {
($name:ident, $ty:expr) => { ($name:ident, $ty:expr) => {
#[repr(transparent)] #[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
pub struct $name(pub u32); pub struct $name(pub u32);
impl DrmObject for $name { impl DrmObject for $name {
@ -929,11 +929,6 @@ pub struct hdr_metadata_infoframe {
} }
impl DrmModeInfo { impl DrmModeInfo {
pub fn create_blob(&self, master: &Rc<DrmMaster>) -> Result<PropBlob, DrmError> {
let raw = self.to_raw();
master.create_blob(&raw)
}
pub fn to_raw(&self) -> drm_mode_modeinfo { pub fn to_raw(&self) -> drm_mode_modeinfo {
let mut name = [0u8; DRM_DISPLAY_MODE_LEN]; let mut name = [0u8; DRM_DISPLAY_MODE_LEN];
let len = name.len().min(self.name.len()); let len = name.len().min(self.name.len());
@ -1006,7 +1001,6 @@ pub struct ObjectChange<'a> {
} }
impl Change { impl Change {
#[expect(dead_code)]
pub fn test(&self, flags: u32) -> Result<(), DrmError> { pub fn test(&self, flags: u32) -> Result<(), DrmError> {
mode_atomic( mode_atomic(
self.master.raw(), self.master.raw(),
@ -1031,7 +1025,15 @@ impl Change {
) )
} }
pub fn change_object<T, F>(&mut self, obj: T, f: F) pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn is_not_empty(&self) -> bool {
!self.is_empty()
}
pub fn change_object<T, F>(&mut self, obj: T, f: F) -> bool
where where
T: DrmObject, T: DrmObject,
F: FnOnce(&mut ObjectChange), F: FnOnce(&mut ObjectChange),
@ -1047,14 +1049,17 @@ impl Change {
self.objects.push(obj.id()); self.objects.push(obj.id());
self.object_lengths.push(new); self.object_lengths.push(new);
} }
true
} else {
false
} }
} }
} }
impl<'a> ObjectChange<'a> { impl<'a> ObjectChange<'a> {
pub fn change(&mut self, property_id: DrmProperty, value: u64) { pub fn change(&mut self, property_id: DrmProperty, value: impl ObjectChangeValue) {
self.change.props.push(property_id.0); self.change.props.push(property_id.0);
self.change.values.push(value); self.change.values.push(value.into_u64());
} }
} }
@ -1069,6 +1074,36 @@ impl Drop for Change {
} }
} }
pub trait ObjectChangeValue {
fn into_u64(self) -> u64;
}
macro_rules! num {
($ty:ty) => {
impl ObjectChangeValue for $ty {
fn into_u64(self) -> u64 {
self as u64
}
}
};
}
num!(u16);
num!(i32);
num!(u32);
num!(i64);
num!(u64);
num!(bool);
impl<T> ObjectChangeValue for T
where
T: DrmObject,
{
fn into_u64(self) -> u64 {
self.id() as u64
}
}
#[expect(non_camel_case_types)] #[expect(non_camel_case_types)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum ConnectorType { pub enum ConnectorType {

View file

@ -693,6 +693,7 @@ pub fn mode_getencoder(fd: c::c_int, encoder_id: u32) -> Result<DrmEncoderInfo,
pub const DRM_DISPLAY_MODE_LEN: usize = 32; pub const DRM_DISPLAY_MODE_LEN: usize = 32;
#[repr(C)] #[repr(C)]
#[derive(Debug)]
pub struct drm_mode_modeinfo { pub struct drm_mode_modeinfo {
pub clock: u32, pub clock: u32,
pub hdisplay: u16, pub hdisplay: u16,