1
0
Fork 0
forked from wry/wry
wry/src/ifs/jay_randr.rs
2026-03-07 18:31:23 +01:00

609 lines
20 KiB
Rust

use {
crate::{
backend::{self, BackendColorSpace, BackendEotfs},
client::{Client, ClientError},
compositor::MAX_EXTENTS,
format::named_formats,
gfx_api::GfxApi,
ifs::wl_output,
leaks::Tracker,
object::{Object, Version},
scale::Scale,
state::{ConnectorData, DrmDevData, OutputData, State},
tree::{OutputNode, TearingMode, Transform, VrrMode},
utils::errorfmt::ErrorFmt,
wire::{JayRandrId, jay_randr::*},
},
jay_config::video::{TearingMode as ConfigTearingMode, VrrMode as ConfigVrrMode},
linearize::LinearizeExt,
std::rc::Rc,
thiserror::Error,
};
pub struct JayRandr {
pub id: JayRandrId,
pub client: Rc<Client>,
pub state: Rc<State>,
pub tracker: Tracker<Self>,
pub version: Version,
}
const VRR_CAPABLE_SINCE: Version = Version(2);
const TEARING_SINCE: Version = Version(3);
const FORMAT_SINCE: Version = Version(8);
const FLIP_MARGIN_SINCE: Version = Version(10);
const COLORIMETRY_SINCE: Version = Version(15);
const BRIGHTNESS_SINCE: Version = Version(16);
const BLEND_SPACE_SINCE: Version = Version(21);
const NATIVE_GAMUT_SINCE: Version = Version(23);
impl JayRandr {
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
Self {
id,
client: client.clone(),
state: client.state.clone(),
tracker: Default::default(),
version,
}
}
fn send_global(&self) {
self.client.event(Global {
self_id: self.id,
default_gfx_api: self.client.state.default_gfx_api.get().to_str(),
})
}
fn send_drm_device(&self, data: &DrmDevData) {
self.client.event(DrmDevice {
self_id: self.id,
id: data.dev.id().raw() as _,
syspath: data.syspath.as_deref().unwrap_or_default(),
vendor: data.pci_id.map(|p| p.vendor).unwrap_or_default(),
vendor_name: data.vendor.as_deref().unwrap_or_default(),
model: data.pci_id.map(|p| p.model).unwrap_or_default(),
model_name: data.model.as_deref().unwrap_or_default(),
devnode: data.devnode.as_deref().unwrap_or_default(),
gfx_api: data.dev.gtx_api().to_str(),
render_device: data.dev.is_render_device() as _,
});
}
fn send_connector(&self, data: &ConnectorData) {
let state_enabled = data.state.borrow().enabled;
self.client.event(Connector {
self_id: self.id,
id: data.connector.id().raw() as _,
drm_device: data
.drm_dev
.as_ref()
.map(|d| d.dev.id().raw() as _)
.unwrap_or_default(),
enabled: state_enabled as _,
name: &data.name,
});
let Some(output) = self.client.state.outputs.get(&data.connector.id()) else {
return;
};
let node = match &output.node {
Some(n) => n,
None => {
self.client.event(NonDesktopOutput {
self_id: self.id,
manufacturer: &output.monitor_info.output_id.manufacturer,
product: &output.monitor_info.output_id.model,
serial_number: &output.monitor_info.output_id.serial_number,
width_mm: output.monitor_info.width_mm,
height_mm: output.monitor_info.height_mm,
});
return;
}
};
let global = &node.global;
let pos = global.pos.get();
self.client.event(Output {
self_id: self.id,
scale: global.persistent.scale.get().to_wl(),
width: pos.width(),
height: pos.height(),
x: pos.x1(),
y: pos.y1(),
transform: global.persistent.transform.get().to_wl(),
manufacturer: &output.monitor_info.output_id.manufacturer,
product: &output.monitor_info.output_id.model,
serial_number: &output.monitor_info.output_id.serial_number,
width_mm: global.width_mm,
height_mm: global.height_mm,
});
if self.version >= VRR_CAPABLE_SINCE {
self.client.event(VrrState {
self_id: self.id,
capable: output.monitor_info.vrr_capable as _,
enabled: node.schedule.vrr_enabled() as _,
mode: node.global.persistent.vrr_mode.get().to_config().0,
});
if let Some(hz) = node.global.persistent.vrr_cursor_hz.get() {
self.client.event(VrrCursorHz {
self_id: self.id,
hz,
});
}
}
if self.version >= TEARING_SINCE {
self.client.event(TearingState {
self_id: self.id,
mode: node.global.persistent.tearing_mode.get().to_config().0,
});
}
if self.version >= FORMAT_SINCE {
let current = node.global.format.get();
self.client.event(FbFormat {
self_id: self.id,
name: current.name,
current: 1,
});
for &format in &*node.global.formats.get() {
if format != current {
self.client.event(FbFormat {
self_id: self.id,
name: format.name,
current: 0,
});
}
}
}
if self.version >= FLIP_MARGIN_SINCE {
if let Some(margin_ns) = node.flip_margin_ns.get() {
self.client.event(FlipMargin {
self_id: self.id,
margin_ns,
});
}
}
let current_mode = global.mode.get();
for mode in &global.modes {
self.client.event(Mode {
self_id: self.id,
width: mode.width,
height: mode.height,
refresh_rate_millihz: mode.refresh_rate_millihz,
current: (mode == &current_mode) as _,
});
}
if self.version >= COLORIMETRY_SINCE {
for eotf in &node.global.eotfs {
self.client.event(SupportedEotf {
self_id: self.id,
eotf: eotf.name(),
});
}
self.client.event(CurrentEotf {
self_id: self.id,
eotf: node.global.btf.get().name(),
});
for cs in &node.global.color_spaces {
self.client.event(SupportedColorSpace {
self_id: self.id,
color_space: cs.name(),
});
}
self.client.event(CurrentColorSpace {
self_id: self.id,
color_space: node.global.bcs.get().name(),
});
}
if self.version >= BRIGHTNESS_SINCE {
if let Some(lum) = node.global.luminance {
self.client.event(BrightnessRange {
self_id: self.id,
min: lum.min,
max: lum.max,
max_fall: lum.max_fall,
});
}
if let Some(lux) = node.global.persistent.brightness.get() {
self.client.event(Brightness {
self_id: self.id,
lux,
});
}
}
if self.version >= BLEND_SPACE_SINCE {
self.client.event(BlendSpace {
self_id: self.id,
blend_space: node.global.persistent.blend_space.get().name(),
});
}
if self.version >= NATIVE_GAMUT_SINCE {
let p = &node.global.primaries;
self.client.event(NativeGamut {
self_id: self.id,
r_x: p.r.0.0,
r_y: p.r.1.0,
g_x: p.g.0.0,
g_y: p.g.1.0,
b_x: p.b.0.0,
b_y: p.b.1.0,
w_x: p.wp.0.0,
w_y: p.wp.1.0,
});
if node.global.persistent.use_native_gamut.get() {
self.client.event(UseNativeGamut { self_id: self.id });
}
}
}
fn send_error(&self, msg: &str) {
self.client.event(Error {
self_id: self.id,
msg,
});
}
fn get_device(&self, name: &str) -> Option<Rc<DrmDevData>> {
let mut candidates = vec![];
for dev in self.client.state.drm_devs.lock().values() {
if let Some(node) = &dev.devnode
&& node.ends_with(name)
{
candidates.push(dev.clone());
}
}
if candidates.len() == 1 {
return Some(candidates[0].clone());
}
if candidates.len() == 0 {
self.send_error(&format!("Found no device matching `{}`", name));
} else {
self.send_error(&format!("The device suffix `{}` is ambiguous", name));
}
None
}
fn get_connector(&self, name: &str) -> Option<Rc<ConnectorData>> {
let namelc = name.to_ascii_lowercase();
for c in self.client.state.connectors.lock().values() {
if c.name.to_ascii_lowercase() == namelc {
return Some(c.clone());
}
}
self.send_error(&format!("Found no connector matching `{}`", name));
None
}
fn get_output(&self, name: &str) -> Option<Rc<OutputData>> {
let namelc = name.to_ascii_lowercase();
for c in self.client.state.outputs.lock().values() {
if c.connector.name.to_ascii_lowercase() == namelc {
return Some(c.clone());
}
}
if let Some(c) = self.get_connector(name) {
self.send_error(&format!("Connector {} is not connected", c.name));
}
None
}
fn get_output_node(&self, name: &str) -> Option<Rc<OutputNode>> {
let output = self.get_output(name)?;
match output.node.clone() {
Some(n) => return Some(n),
_ => self.send_error(&format!(
"Display connected to {} is not a desktop display",
output.connector.name
)),
}
None
}
}
impl JayRandrRequestHandler for JayRandr {
type Error = JayRandrError;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get(&self, _req: Get, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let state = &self.client.state;
self.send_global();
for dev in state.drm_devs.lock().values() {
self.send_drm_device(dev);
}
for connector in state.connectors.lock().values() {
self.send_connector(connector);
}
Ok(())
}
fn set_api(&self, req: SetApi, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
let Some(api) = GfxApi::from_str_lossy(req.api) else {
self.send_error(&format!("Unknown API `{}`", req.api));
return Ok(());
};
dev.dev.set_gfx_api(api);
Ok(())
}
fn make_render_device(
&self,
req: MakeRenderDevice,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.make_render_device();
Ok(())
}
fn set_direct_scanout(
&self,
req: SetDirectScanout,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.set_direct_scanout_enabled(req.enabled != 0);
Ok(())
}
fn set_transform(&self, req: SetTransform, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
let Some(transform) = Transform::from_wl(req.transform) else {
self.send_error(&format!("Unknown transform {}", req.transform));
return Ok(());
};
c.update_transform(transform);
Ok(())
}
fn set_scale(&self, req: SetScale, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_preferred_scale(Scale::from_wl(req.scale));
Ok(())
}
fn set_mode(&self, req: SetMode, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
let res = c.modify_state(&self.state, |s| {
s.mode = backend::Mode {
width: req.width,
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(())
}
fn set_position(&self, req: SetPosition, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
if req.x < 0 || req.y < 0 {
self.send_error("x and y cannot be less than 0");
return Ok(());
}
if req.x > MAX_EXTENTS || req.y > MAX_EXTENTS {
self.send_error(&format!("x and y cannot be greater than {MAX_EXTENTS}"));
return Ok(());
}
c.set_position(req.x, req.y);
Ok(())
}
fn set_enabled(&self, req: SetEnabled, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
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(())
}
fn set_non_desktop(&self, req: SetNonDesktop<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
let non_desktop = match req.non_desktop {
0 => None,
1 => Some(false),
_ => Some(true),
};
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(())
}
fn set_vrr_mode(&self, req: SetVrrMode<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(mode) = VrrMode::from_config(ConfigVrrMode(req.mode)) else {
return Err(JayRandrError::UnknownVrrMode(req.mode));
};
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_vrr_mode(mode);
return Ok(());
}
fn set_vrr_cursor_hz(
&self,
req: SetVrrCursorHz<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.schedule.set_cursor_hz(req.hz);
Ok(())
}
fn set_tearing_mode(
&self,
req: SetTearingMode<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(mode) = TearingMode::from_config(ConfigTearingMode(req.mode)) else {
return Err(JayRandrError::UnknownTearingMode(req.mode));
};
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_tearing_mode(mode);
return Ok(());
}
fn set_fb_format(&self, req: SetFbFormat<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(&format) = named_formats().get(req.format) else {
return Err(JayRandrError::UnknownFormat(req.format.to_string()));
};
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
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(())
}
fn set_flip_margin(&self, req: SetFlipMargin<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.set_flip_margin(req.margin_ns);
Ok(())
}
fn set_colors(&self, req: SetColors<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let cs = 'cs: {
for cs in BackendColorSpace::variants() {
if cs.name() == req.color_space {
break 'cs cs;
}
}
return Err(JayRandrError::UnknownColorSpace(
req.color_space.to_string(),
));
};
let tf = 'tf: {
for tf in BackendEotfs::variants() {
if tf.name() == req.eotf {
break 'tf tf;
}
}
return Err(JayRandrError::UnknownEotf(req.eotf.to_string()));
};
let Some(c) = self.get_connector(req.output) else {
return Ok(());
};
let res = c.modify_state(&self.state, |s| {
s.color_space = cs;
s.eotf = tf;
});
if let Err(e) = res {
self.send_error(&format!(
"Could not modify connector colors: {}",
ErrorFmt(e),
));
}
Ok(())
}
fn set_brightness(&self, req: SetBrightness<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_brightness(Some(req.lux));
Ok(())
}
fn unset_brightness(
&self,
req: UnsetBrightness<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_brightness(None);
Ok(())
}
fn set_blend_space(&self, req: SetBlendSpace<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let space = 'space: {
for space in wl_output::BlendSpace::variants() {
if space.name() == req.blend_space {
break 'space space;
}
}
self.send_error(&format!("Unknown blend space: {}", req.blend_space));
return Ok(());
};
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_blend_space(space);
Ok(())
}
fn set_use_native_gamut(
&self,
req: SetUseNativeGamut<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
let Some(c) = self.get_output_node(req.output) else {
return Ok(());
};
c.set_use_native_gamut(req.use_native_gamut != 0);
Ok(())
}
}
object_base! {
self = JayRandr;
version = self.version;
}
impl Object for JayRandr {}
simple_add_obj!(JayRandr);
#[derive(Debug, Error)]
pub enum JayRandrError {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("Unknown VRR mode {0}")]
UnknownVrrMode(u32),
#[error("Unknown tearing mode {0}")]
UnknownTearingMode(u32),
#[error("Unknown format {0}")]
UnknownFormat(String),
#[error("Unknown color space {0}")]
UnknownColorSpace(String),
#[error("Unknown EOTF {0}")]
UnknownEotf(String),
}
efrom!(JayRandrError, ClientError);