1
0
Fork 0
forked from wry/wry
wry/src/backends/x.rs
2026-02-18 18:03:38 +01:00

1399 lines
44 KiB
Rust

use {
crate::{
allocator::BufferObject,
async_engine::{Phase, SpawnedFuture},
backend::{
AXIS_120, AxisSource, Backend, BackendConnectorState, BackendDrmDevice, BackendEvent,
ButtonState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId,
DrmEvent, InputDevice, InputDeviceAccelProfile, InputDeviceCapability,
InputDeviceClickMethod, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo,
ScrollAxis, TransformMatrix,
transaction::{
BackendAppliedConnectorTransaction, BackendConnectorTransaction,
BackendConnectorTransactionError, BackendConnectorTransactionType,
BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction,
},
},
cmm::cmm_primaries::Primaries,
fixed::Fixed,
format::{Format, XRGB8888},
gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync},
ifs::wl_output::OutputId,
state::State,
time::Time,
utils::{
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
queue::AsyncQueue, syncqueue::SyncQueue,
},
video::{
drm::{ConnectorType, Drm, DrmError, DrmVersion},
gbm::{GBM_BO_USE_RENDERING, GbmBo, GbmDevice, GbmError},
},
wire_xcon::{
ChangeProperty, ChangeWindowAttributes, ConfigureNotify, CreateCursor, CreatePixmap,
CreateWindow, CreateWindowValues, DestroyNotify, Dri3Open, Dri3PixmapFromBuffers,
Dri3QueryVersion, Extension, FreePixmap, MapWindow, PresentCompleteNotify,
PresentIdleNotify, PresentPixmap, PresentQueryVersion, PresentSelectInput,
XiButtonPress, XiButtonRelease, XiDeviceInfo, XiEnter, XiEventMask,
XiGetDeviceButtonMapping, XiGrabDevice, XiHierarchy, XiKeyPress, XiKeyRelease,
XiMotion, XiQueryDevice, XiQueryVersion, XiSelectEvents, XiUngrabDevice,
XkbPerClientFlags, XkbUseExtension,
},
xcon::{
Event, XEvent, Xcon, XconError,
consts::{
ATOM_STRING, ATOM_WM_CLASS, EVENT_MASK_EXPOSURE, EVENT_MASK_STRUCTURE_NOTIFY,
EVENT_MASK_VISIBILITY_CHANGE, GRAB_MODE_ASYNC, GRAB_STATUS_SUCCESS,
INPUT_DEVICE_ALL, INPUT_DEVICE_ALL_MASTER, INPUT_DEVICE_TYPE_MASTER_KEYBOARD,
INPUT_HIERARCHY_MASK_MASTER_ADDED, INPUT_HIERARCHY_MASK_MASTER_REMOVED,
PRESENT_EVENT_MASK_COMPLETE_NOTIFY, PRESENT_EVENT_MASK_IDLE_NOTIFY,
PROP_MODE_REPLACE, WINDOW_CLASS_INPUT_OUTPUT, XI_EVENT_MASK_BUTTON_PRESS,
XI_EVENT_MASK_BUTTON_RELEASE, XI_EVENT_MASK_ENTER, XI_EVENT_MASK_FOCUS_IN,
XI_EVENT_MASK_FOCUS_OUT, XI_EVENT_MASK_HIERARCHY, XI_EVENT_MASK_KEY_PRESS,
XI_EVENT_MASK_KEY_RELEASE, XI_EVENT_MASK_LEAVE, XI_EVENT_MASK_MOTION,
XI_EVENT_MASK_TOUCH_BEGIN, XI_EVENT_MASK_TOUCH_END, XI_EVENT_MASK_TOUCH_UPDATE,
XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
},
},
},
ahash::AHashMap,
jay_config::video::GfxApi,
std::{
any::Any,
borrow::Cow,
cell::{Cell, RefCell},
collections::VecDeque,
error::Error,
future::pending,
rc::Rc,
},
thiserror::Error,
uapi::c::dev_t,
};
#[derive(Debug, Error)]
pub enum XBackendError {
#[error("Could not connect to the X server")]
CannotConnect(#[source] XconError),
#[error("Could not enable XInput")]
EnableXinput(#[source] XconError),
#[error("Could not enable Present")]
EnablePresent(#[source] XconError),
#[error("Could not enable XKB")]
EnableXkb(#[source] XconError),
#[error("Could not enable DRI3")]
EnableDri3(#[source] XconError),
#[error("DriOpen returned an error")]
DriOpen(#[source] XconError),
#[error("Could not create a pixmap")]
CreatePixmap(#[source] XconError),
#[error("Could not create a cursor")]
CreateCursor(#[source] XconError),
#[error("Could not select XInput hierarchy events")]
SelectHierarchyEvents(#[source] XconError),
#[error("The drm subsystem returned an error")]
DrmError(#[from] DrmError),
#[error("The gbm subsystem returned an error")]
GbmError(#[from] GbmError),
#[error("Could not import a dma-buf")]
ImportBuffer(#[source] XconError),
#[error("Could not create a graphics API context")]
CreateEgl(#[source] GfxError),
#[error("Could not create an graphics API image from a dma-buf")]
CreateImage(#[source] GfxError),
#[error("Could not create a framebuffer from a graphics API image")]
CreateFramebuffer(#[source] GfxError),
#[error("Could not create a texture from an graphics API image")]
CreateTexture(#[source] GfxError),
#[error("Could not select input events")]
CannotSelectInputEvents(#[source] XconError),
#[error("Could not select present events")]
CannotSelectPresentEvents(#[source] XconError),
#[error("libloading returned an error")]
Libloading(#[from] libloading::Error),
#[error("An unspecified X error occurred")]
XconError(#[from] XconError),
#[error("Could not create a window")]
CreateWindow(#[source] XconError),
#[error("Could not set WM_CLASS")]
WmClass(#[source] XconError),
#[error("Could not select window events")]
WindowEvents(#[source] XconError),
#[error("Could not map a window")]
MapWindow(#[source] XconError),
#[error("Could not query device")]
QueryDevice(#[source] XconError),
#[error("Render device does not support XRGB8888 format")]
XRGB8888,
}
const FORMAT: &Format = XRGB8888;
pub async fn create(state: &Rc<State>) -> Result<Rc<XBackend>, XBackendError> {
let c = match Xcon::connect(state).await {
Ok(c) => c,
Err(e) => return Err(XBackendError::CannotConnect(e)),
};
if let Err(e) = c
.call(&XiQueryVersion {
major_version: 2,
minor_version: 2,
})
.await
{
return Err(XBackendError::EnableXinput(e));
}
if let Err(e) = c
.call(&Dri3QueryVersion {
major_version: 1,
minor_version: 0,
})
.await
{
return Err(XBackendError::EnableDri3(e));
}
if let Err(e) = c
.call(&PresentQueryVersion {
major_version: 1,
minor_version: 0,
})
.await
{
return Err(XBackendError::EnablePresent(e));
}
if let Err(e) = c
.call(&XkbUseExtension {
wanted_major: 1,
wanted_minor: 0,
})
.await
{
return Err(XBackendError::EnableXkb(e));
}
let root = c.root_window();
let drm = {
let res = c
.call(&Dri3Open {
drawable: root,
provider: 0,
})
.await;
match res {
Ok(r) => Drm::reopen(r.get().device_fd.raw(), false)?,
Err(e) => return Err(XBackendError::DriOpen(e)),
}
};
let drm_dev = drm.dev();
let gbm = GbmDevice::new(&drm)?;
let ctx = match state.create_gfx_context(&drm, None) {
Ok(r) => r,
Err(e) => return Err(XBackendError::CreateEgl(e)),
};
let cursor = {
let cp = CreatePixmap {
depth: 1,
pid: c.generate_id()?,
drawable: root,
width: 1,
height: 1,
};
if let Err(e) = c.call(&cp).await {
return Err(XBackendError::CreatePixmap(e));
}
let cc = CreateCursor {
cid: c.generate_id()?,
source: cp.pid,
mask: cp.pid,
fore_red: 0,
fore_green: 0,
fore_blue: 0,
back_red: 0,
back_green: 0,
back_blue: 0,
x: 0,
y: 0,
};
if let Err(e) = c.call(&cc).await {
return Err(XBackendError::CreateCursor(e));
}
c.call(&FreePixmap { pixmap: cp.pid });
cc.cid
};
{
let se = XiSelectEvents {
window: c.root_window(),
masks: Cow::Borrowed(&[XiEventMask {
deviceid: INPUT_DEVICE_ALL,
mask: &[XI_EVENT_MASK_HIERARCHY],
}]),
};
if let Err(e) = c.call(&se).await {
return Err(XBackendError::SelectHierarchyEvents(e));
}
}
let data = Rc::new(XBackend {
state: state.clone(),
c,
outputs: Default::default(),
seats: Default::default(),
mouse_seats: Default::default(),
ctx: ctx.clone(),
gbm,
cursor,
root,
scheduled_present: Default::default(),
grab_requests: Default::default(),
drm_device_id: state.drm_dev_ids.next(),
drm_dev,
});
data.add_output().await?;
Ok(data)
}
impl Backend for XBackend {
fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>> {
let slf = self.clone();
self.state.eng.spawn("x backend", async move {
slf.run().await?;
Ok(())
})
}
}
pub struct XBackend {
state: Rc<State>,
c: Rc<Xcon>,
outputs: CopyHashMap<u32, Rc<XOutput>>,
seats: CopyHashMap<u16, Rc<XSeat>>,
mouse_seats: CopyHashMap<u16, Rc<XSeat>>,
ctx: Rc<dyn GfxContext>,
gbm: GbmDevice,
cursor: u32,
root: u32,
scheduled_present: AsyncQueue<Rc<XOutput>>,
grab_requests: AsyncQueue<(Rc<XSeat>, bool)>,
drm_device_id: DrmDeviceId,
drm_dev: dev_t,
}
impl XBackend {
async fn run(self: Rc<Self>) -> Result<(), XBackendError> {
self.query_devices(INPUT_DEVICE_ALL_MASTER).await?;
let _events = self
.state
.eng
.spawn("x event handler", self.clone().event_handler());
let _grab = self
.state
.eng
.spawn("grab handler", self.clone().grab_handler());
let _present = self.state.eng.spawn2(
"present handler",
Phase::Present,
self.clone().present_handler(),
);
self.state.set_render_ctx(Some(self.ctx.clone()));
self.state
.backend_events
.push(BackendEvent::NewDrmDevice(Rc::new(XDrmDevice {
backend: self.clone(),
id: self.drm_device_id,
dev: self.drm_dev,
})));
for (_, output) in self.outputs.lock().iter() {
self.active_output(output).await;
}
self.state
.backend_events
.push(BackendEvent::DevicesEnumerated);
pending().await
}
async fn event_handler(self: Rc<Self>) {
loop {
let event = self.c.event().await;
if let Err(e) = self.handle_event(&event).await {
log::error!(
"Fatal error: Could not handle an event from the X server: {}",
ErrorFmt(e)
);
self.state.ring.stop();
return;
}
}
}
async fn present_handler(self: Rc<Self>) {
loop {
let output = self.scheduled_present.pop().await;
self.present(&output).await;
}
}
async fn grab_handler(self: Rc<Self>) {
loop {
let (dev, grab) = self.grab_requests.pop().await;
self.handle_grab_request(&dev, grab).await;
}
}
async fn handle_grab_request(&self, dev: &XSeat, grab: bool) {
if grab {
let xg = XiGrabDevice {
window: self.root,
time: 0,
cursor: 0,
deviceid: dev.kb,
mode: GRAB_MODE_ASYNC,
paired_device_mode: GRAB_MODE_ASYNC,
owner_events: 1,
mask: &[],
};
let res = match self.c.call(&xg).await {
Ok(r) => r,
Err(e) => {
log::error!("Could not grab device {}: {}", dev.kb, ErrorFmt(e));
return;
}
};
let res = res.get();
if res.status != GRAB_STATUS_SUCCESS {
log::error!("Could not grab device {}: status = {}", dev.kb, res.status);
}
} else {
let ug = XiUngrabDevice {
time: 0,
deviceid: dev.kb,
};
if let Err(e) = self.c.call(&ug).await {
log::error!("Could not ungrab device {}: {}", dev.kb, ErrorFmt(e));
}
}
}
async fn create_images(
&self,
window: u32,
width: i32,
height: i32,
) -> Result<[XImage; 2], XBackendError> {
let mut images = [None, None];
let formats = self.ctx.formats();
let format = match formats.get(&FORMAT.drm) {
Some(f) => f,
None => return Err(XBackendError::XRGB8888),
};
for image in &mut images {
let bo = self.gbm.create_bo(
&self.state.dma_buf_ids,
width,
height,
FORMAT,
format.write_modifiers.keys(),
GBM_BO_USE_RENDERING,
)?;
let dma = bo.dmabuf();
let img = match self.ctx.clone().dmabuf_img(dma) {
Ok(f) => f,
Err(e) => return Err(XBackendError::CreateImage(e)),
};
let fb = match img.clone().to_framebuffer() {
Ok(f) => f,
Err(e) => return Err(XBackendError::CreateFramebuffer(e)),
};
let tex = match img.to_texture() {
Ok(f) => f,
Err(e) => return Err(XBackendError::CreateTexture(e)),
};
macro_rules! pp {
($num:expr, $field:ident) => {
dma.planes.get($num).map(|p| p.$field).unwrap_or(0) as _
};
}
let buffers: Vec<_> = dma.planes.iter().map(|p| p.fd.clone()).collect();
let pixmap = {
let pfb = Dri3PixmapFromBuffers {
pixmap: self.c.generate_id()?,
window,
num_buffers: dma.planes.len() as _,
width: dma.width as _,
height: dma.height as _,
stride0: pp!(0, stride),
offset0: pp!(0, offset),
stride1: pp!(1, stride),
offset1: pp!(1, offset),
stride2: pp!(2, stride),
offset2: pp!(2, offset),
stride3: pp!(3, stride),
offset3: pp!(3, offset),
depth: 24,
bpp: 32,
modifier: dma.modifier,
buffers: buffers.into(),
};
if let Err(e) = self.c.call(&pfb).await {
return Err(XBackendError::ImportBuffer(e));
}
pfb.pixmap
};
*image = Some(XImage {
pixmap: Cell::new(pixmap),
_bo: bo,
fb: CloneCell::new(fb),
tex: CloneCell::new(tex),
idle: Cell::new(true),
render_on_idle: Cell::new(false),
last_serial: Cell::new(0),
});
}
Ok([images[0].take().unwrap(), images[1].take().unwrap()])
}
async fn add_output(self: &Rc<Self>) -> Result<(), XBackendError> {
const WIDTH: i32 = 800;
const HEIGHT: i32 = 600;
let window_id = {
let cw = CreateWindow {
depth: 0,
wid: self.c.generate_id()?,
parent: self.root,
x: 0,
y: 0,
width: WIDTH as _,
height: HEIGHT as _,
border_width: 0,
class: WINDOW_CLASS_INPUT_OUTPUT,
visual: 0,
values: Default::default(),
};
if let Err(e) = self.c.call(&cw).await {
return Err(XBackendError::CreateWindow(e));
}
cw.wid
};
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(),
eotf: Default::default(),
gamma_lut: Default::default(),
};
let output = Rc::new(XOutput {
id: self.state.connector_ids.next(),
backend: self.clone(),
window: window_id,
events: Default::default(),
width: Cell::new(0),
height: Cell::new(0),
serial: Default::default(),
next_msc: Cell::new(0),
next_image: Default::default(),
cb: CloneCell::new(None),
images,
state: RefCell::new(state),
});
{
let class = "jay\0jay\0";
let cp = ChangeProperty {
mode: PROP_MODE_REPLACE,
window: window_id,
property: ATOM_WM_CLASS,
ty: ATOM_STRING,
format: 8,
data: class.as_bytes(),
};
if let Err(e) = self.c.call(&cp).await {
return Err(XBackendError::WmClass(e));
};
}
{
let cwa = ChangeWindowAttributes {
window: window_id,
values: CreateWindowValues {
event_mask: Some(
EVENT_MASK_EXPOSURE
| EVENT_MASK_STRUCTURE_NOTIFY
| EVENT_MASK_VISIBILITY_CHANGE,
),
cursor: Some(self.cursor),
..Default::default()
},
};
if let Err(e) = self.c.call(&cwa).await {
return Err(XBackendError::WindowEvents(e));
}
}
if let Err(e) = self.c.call(&MapWindow { window: window_id }).await {
return Err(XBackendError::MapWindow(e));
}
{
let mask = 0
| XI_EVENT_MASK_MOTION
| XI_EVENT_MASK_BUTTON_PRESS
| XI_EVENT_MASK_BUTTON_RELEASE
| XI_EVENT_MASK_KEY_PRESS
| XI_EVENT_MASK_KEY_RELEASE
| XI_EVENT_MASK_ENTER
| XI_EVENT_MASK_LEAVE
| XI_EVENT_MASK_FOCUS_IN
| XI_EVENT_MASK_FOCUS_OUT
| XI_EVENT_MASK_TOUCH_BEGIN
| XI_EVENT_MASK_TOUCH_UPDATE
| XI_EVENT_MASK_TOUCH_END;
let mask = [XiEventMask {
deviceid: INPUT_DEVICE_ALL_MASTER,
mask: &[mask],
}];
let xs = XiSelectEvents {
window: window_id,
masks: Cow::Borrowed(&mask[..]),
};
if let Err(e) = self.c.call(&xs).await {
return Err(XBackendError::CannotSelectInputEvents(e));
}
}
{
let mask = 0 | PRESENT_EVENT_MASK_IDLE_NOTIFY | PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
let si = PresentSelectInput {
eid: self.c.generate_id()?,
window: window_id,
event_mask: mask,
};
if let Err(e) = self.c.call(&si).await {
return Err(XBackendError::CannotSelectPresentEvents(e));
}
}
self.outputs.set(window_id, output.clone());
Ok(())
}
async fn active_output(&self, output: &Rc<XOutput>) {
self.state
.backend_events
.push(BackendEvent::NewConnector(output.clone()));
output.events.push(ConnectorEvent::Connected(MonitorInfo {
modes: vec![],
output_id: Rc::new(OutputId::new(
String::new(),
"X.Org Foundation".to_string(),
format!("X-Window-{}", output.window),
output.window.to_string(),
)),
width_mm: output.width.get(),
height_mm: output.height.get(),
non_desktop: false,
non_desktop_effective: false,
vrr_capable: false,
eotfs: vec![],
color_spaces: vec![],
primaries: Primaries::SRGB,
luminance: None,
state: output.state.borrow().clone(),
}));
output.changed();
self.present(output).await;
}
async fn query_devices(self: &Rc<Self>, deviceid: u16) -> Result<(), XBackendError> {
let reply = match self.c.call(&XiQueryDevice { deviceid }).await {
Ok(r) => r,
Err(e) => return Err(XBackendError::QueryDevice(e)),
};
for dev in reply.get().infos.iter() {
self.handle_input_device(dev).await;
}
Ok(())
}
async fn handle_input_device(self: &Rc<Self>, info: &XiDeviceInfo<'_>) {
if info.ty != INPUT_DEVICE_TYPE_MASTER_KEYBOARD {
return;
}
self.mouse_seats.remove(&info.attachment);
if let Some(kb) = self.seats.remove(&info.deviceid) {
kb.removed.set(true);
kb.kb_changed();
kb.mouse_changed();
}
let pcf = XkbPerClientFlags {
device_spec: info.deviceid,
change: XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
value: XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
ctrls_to_change: 0,
auto_ctrls: 0,
auto_ctrls_values: 0,
};
if let Err(e) = self.c.call(&pcf).await {
log::warn!(
"Could not make auto repeat detectable for keyboard {}: {}",
info.deviceid,
ErrorFmt(e),
);
}
let seat = Rc::new(XSeat {
kb_id: self.state.input_device_ids.next(),
mouse_id: self.state.input_device_ids.next(),
backend: self.clone(),
kb: info.deviceid,
mouse: info.attachment,
removed: Cell::new(false),
kb_cb: Default::default(),
mouse_cb: Default::default(),
kb_events: RefCell::new(Default::default()),
mouse_events: RefCell::new(Default::default()),
button_map: Default::default(),
kb_name: Rc::new(format!("kb{}", info.deviceid)),
mouse_name: Rc::new(format!("mouse{}", info.deviceid)),
});
seat.update_button_map().await;
self.seats.set(info.deviceid, seat.clone());
self.mouse_seats.set(info.attachment, seat.clone());
self.state
.backend_events
.push(BackendEvent::NewInputDevice(Rc::new(XSeatMouse(
seat.clone(),
))));
self.state
.backend_events
.push(BackendEvent::NewInputDevice(Rc::new(XSeatKeyboard(
seat.clone(),
))));
}
async fn handle_event(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
match event.ext() {
Some(ext) => self.handle_ext_event(ext, event).await,
_ => self.handle_core_event(event).await,
}
}
async fn handle_ext_event(
self: &Rc<Self>,
ext: Extension,
event: &Event,
) -> Result<(), XBackendError> {
match ext {
Extension::Present => self.handle_present_event(event),
Extension::XInputExtension => self.handle_input_event(event).await,
_ => Ok(()),
}
}
async fn handle_core_event(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
match event.code() {
ConfigureNotify::OPCODE => self.handle_configure(event).await,
DestroyNotify::OPCODE => self.handle_destroy(event),
_ => Ok(()),
}
}
fn handle_present_event(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
match event.code() {
PresentCompleteNotify::OPCODE => self.handle_present_complete(event)?,
PresentIdleNotify::OPCODE => self.handle_present_idle(event)?,
_ => {}
}
Ok(())
}
fn handle_present_complete(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
let event: PresentCompleteNotify = event.parse()?;
let window = event.window;
let output = match self.outputs.get(&window) {
Some(o) => o,
_ => return Ok(()),
};
output.next_msc.set(event.msc + 1);
let image = &output.images[output.next_image.get() % output.images.len()];
if image.idle.get() {
self.schedule_present(&output);
} else {
image.render_on_idle.set(true);
}
self.state.vblank(output.id);
Ok(())
}
fn handle_present_idle(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
let event: PresentIdleNotify = event.parse()?;
let output = match self.outputs.get(&event.window) {
Some(o) => o,
_ => return Ok(()),
};
let mut matched_any = false;
for image in &output.images {
if image.last_serial.get() == event.serial {
matched_any = true;
image.idle.set(true);
if image.render_on_idle.replace(false) {
self.schedule_present(&output);
}
}
}
if !matched_any {
fatal!(
"idle event did not match any images {}, {}, {}, {:#?}",
output.serial.get(),
output.images[0].last_serial.get(),
output.images[1].last_serial.get(),
event
);
}
Ok(())
}
fn schedule_present(&self, output: &Rc<XOutput>) {
self.scheduled_present.push(output.clone());
}
async fn present(&self, output: &Rc<XOutput>) {
let serial = output.serial.fetch_add(1);
let image = &output.images[output.next_image.fetch_add(1) % output.images.len()];
image.idle.set(false);
image.last_serial.set(serial);
if let Some(node) = self.state.root.outputs.get(&output.id) {
let now = Time::now_unchecked().nsec();
node.before_latch(now).await;
let res = self.state.present_output(
&node,
&image.fb.get(),
self.state.color_manager.srgb_gamma22(),
AcquireSync::Implicit,
ReleaseSync::Implicit,
&image.tex.get(),
true,
None,
self.state.color_manager.srgb_linear(),
);
if let Err(e) = res {
log::error!("Could not render screen: {}", ErrorFmt(e));
return;
}
}
let pp = PresentPixmap {
window: output.window,
pixmap: image.pixmap.get(),
serial,
valid: 0,
update: 0,
x_off: 0,
y_off: 0,
target_crtc: 0,
wait_fence: 0,
idle_fence: 0,
options: 0,
target_msc: output.next_msc.get(),
divisor: 1,
remainder: 0,
notifies: Default::default(),
};
if let Err(e) = self.c.call(&pp).await {
log::error!("Could not present image: {:?}", e);
return;
}
self.state.set_backend_idle(false);
}
async fn handle_input_event(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
match event.code() {
XiMotion::OPCODE => self.handle_input_motion(event),
XiEnter::OPCODE => self.handle_input_enter(event),
XiButtonPress::OPCODE => self.handle_input_button_press(event, ButtonState::Pressed),
XiButtonRelease::OPCODE => self.handle_input_button_press(event, ButtonState::Released),
XiKeyPress::OPCODE => self.handle_input_key_press(event, KeyState::Pressed),
XiKeyRelease::OPCODE => self.handle_input_key_press(event, KeyState::Released),
XiHierarchy::OPCODE => self.handle_input_hierarchy(event).await,
_ => Ok(()),
}
}
fn handle_input_button_press(
self: &Rc<Self>,
event: &Event,
state: ButtonState,
) -> Result<(), XBackendError> {
let event: XiButtonPress = event.parse()?;
if let Some(seat) = self.mouse_seats.get(&event.deviceid) {
let button = event.detail;
// let button = seat.button_map.get(&event.detail).unwrap_or(event.detail);
if matches!(button, 4..=7) {
if state == ButtonState::Pressed {
let (axis, val) = match button {
4 => (ScrollAxis::Vertical, -1),
5 => (ScrollAxis::Vertical, 1),
6 => (ScrollAxis::Horizontal, -1),
7 => (ScrollAxis::Horizontal, 1),
_ => unreachable!(),
};
seat.mouse_event(InputEvent::AxisSource {
source: AxisSource::Wheel,
});
seat.mouse_event(InputEvent::Axis120 {
dist: val * AXIS_120,
axis,
inverted: false,
});
seat.mouse_event(InputEvent::AxisFrame {
time_usec: self.state.now_usec(),
});
}
} else {
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
const BTN_SIDE: u32 = 0x113;
let button = match button {
0 => return Ok(()),
1 => BTN_LEFT,
2 => BTN_MIDDLE,
3 => BTN_RIGHT,
n => BTN_SIDE + n - 8,
};
seat.mouse_event(InputEvent::Button {
time_usec: self.state.now_usec(),
button,
state,
});
}
}
Ok(())
}
fn handle_input_key_press(
self: &Rc<Self>,
event: &Event,
state: KeyState,
) -> Result<(), XBackendError> {
let event: XiKeyPress = event.parse()?;
if let Some(seat) = self.seats.get(&event.deviceid) {
seat.kb_event(InputEvent::Key {
time_usec: self.state.now_usec(),
key: event.detail - 8,
state,
});
}
Ok(())
}
async fn handle_input_hierarchy(self: &Rc<Self>, event: &Event) -> Result<(), XBackendError> {
let event: XiHierarchy = event.parse()?;
for info in event.infos.iter() {
if info.flags & INPUT_HIERARCHY_MASK_MASTER_ADDED != 0 {
if let Err(e) = self.query_devices(info.deviceid).await {
log::error!("Could not query device {}: {}", info.deviceid, ErrorFmt(e));
}
} else if info.flags & INPUT_HIERARCHY_MASK_MASTER_REMOVED != 0 {
self.mouse_seats.remove(&info.attachment);
if let Some(seat) = self.seats.remove(&info.deviceid) {
seat.removed.set(true);
seat.kb_changed();
seat.mouse_changed();
}
}
}
Ok(())
}
fn handle_input_enter(&self, event: &Event) -> Result<(), XBackendError> {
let event: XiEnter = event.parse()?;
if let (Some(win), Some(seat)) = (
self.outputs.get(&event.event),
self.mouse_seats.get(&event.deviceid),
) {
seat.mouse_event(InputEvent::ConnectorPosition {
time_usec: self.state.now_usec(),
connector: win.id,
x: Fixed::from_1616(event.event_x),
y: Fixed::from_1616(event.event_y),
});
}
Ok(())
}
fn handle_input_motion(&self, event: &Event) -> Result<(), XBackendError> {
let event: XiMotion = event.parse()?;
let (win, seat) = match (
self.outputs.get(&event.event),
self.mouse_seats.get(&event.deviceid),
) {
(Some(a), Some(b)) => (a, b),
_ => return Ok(()),
};
seat.mouse_event(InputEvent::ConnectorPosition {
time_usec: self.state.now_nsec(),
connector: win.id,
x: Fixed::from_1616(event.event_x),
y: Fixed::from_1616(event.event_y),
});
Ok(())
}
fn handle_destroy(&self, event: &Event) -> Result<(), XBackendError> {
self.state.ring.stop();
let event: DestroyNotify = event.parse()?;
let output = match self.outputs.remove(&event.event) {
Some(o) => o,
_ => return Ok(()),
};
output.events.push(ConnectorEvent::Disconnected);
output.events.push(ConnectorEvent::Removed);
output.changed();
Ok(())
}
async fn handle_configure(&self, event: &Event) -> Result<(), XBackendError> {
let event: ConfigureNotify = event.parse()?;
let output = match self.outputs.get(&event.event) {
Some(o) => o,
_ => return Ok(()),
};
let width = event.width as i32;
let height = event.height as i32;
let mut changed = false;
changed |= output.width.replace(width) != width;
changed |= output.height.replace(height) != height;
if changed {
let images = self.create_images(output.window, width, height).await?;
for (new, old) in images.iter().zip(output.images.iter()) {
#[expect(clippy::let_underscore_future)]
let _ = self.c.call(&FreePixmap {
pixmap: old.pixmap.get(),
});
old.fb.set(new.fb.get());
old.tex.set(new.tex.get());
old.pixmap.set(new.pixmap.get());
}
let mut state = output.state.borrow().clone();
state.serial = self.state.backend_connector_state_serials.next();
state.mode.width = width;
state.mode.height = height;
*output.state.borrow_mut() = state.clone();
output.events.push(ConnectorEvent::State(state));
output.changed();
}
Ok(())
}
}
struct XDrmDevice {
backend: Rc<XBackend>,
id: DrmDeviceId,
dev: dev_t,
}
impl BackendDrmDevice for XDrmDevice {
fn id(&self) -> DrmDeviceId {
self.id
}
fn event(&self) -> Option<DrmEvent> {
None
}
fn on_change(&self, _cb: Rc<dyn Fn()>) {
// nothing
}
fn dev_t(&self) -> dev_t {
self.dev
}
fn make_render_device(&self) {
log::warn!("make_render_device is not supported by the X backend");
// nothing
}
fn set_gfx_api(&self, _api: GfxApi) {
log::warn!("set_gfx_api is not supported by the X backend");
// nothing
}
fn gtx_api(&self) -> GfxApi {
self.backend.ctx.gfx_api()
}
fn version(&self) -> Result<DrmVersion, DrmError> {
self.backend.gbm.drm.version()
}
fn set_direct_scanout_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn is_render_device(&self) -> bool {
true
}
}
struct XOutput {
id: ConnectorId,
backend: Rc<XBackend>,
window: u32,
events: SyncQueue<ConnectorEvent>,
width: Cell<i32>,
height: Cell<i32>,
serial: NumCell<u32>,
next_msc: Cell<u64>,
next_image: NumCell<usize>,
images: [XImage; 2],
cb: CloneCell<Option<Rc<dyn Fn()>>>,
state: RefCell<BackendConnectorState>,
}
struct XImage {
pixmap: Cell<u32>,
_bo: GbmBo,
fb: CloneCell<Rc<dyn GfxFramebuffer>>,
tex: CloneCell<Rc<dyn GfxTexture>>,
idle: Cell<bool>,
render_on_idle: Cell<bool>,
last_serial: Cell<u32>,
}
impl XOutput {
fn changed(&self) {
if let Some(cb) = self.cb.get() {
cb();
}
}
}
impl Connector for XOutput {
fn id(&self) -> ConnectorId {
self.id
}
fn kernel_id(&self) -> ConnectorKernelId {
ConnectorKernelId {
ty: ConnectorType::EmbeddedWindow,
idx: self.id.raw(),
}
}
fn event(&self) -> Option<ConnectorEvent> {
self.events.pop()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.cb.set(Some(cb));
}
fn damage(&self) {
// nothing
}
fn drm_dev(&self) -> Option<DrmDeviceId> {
Some(self.backend.drm_device_id)
}
fn effectively_locked(&self) -> bool {
// 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.borrow().clone();
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(())
}
}
struct XSeat {
kb_id: InputDeviceId,
mouse_id: InputDeviceId,
backend: Rc<XBackend>,
kb: u16,
mouse: u16,
removed: Cell<bool>,
kb_cb: CloneCell<Option<Rc<dyn Fn()>>>,
mouse_cb: CloneCell<Option<Rc<dyn Fn()>>>,
kb_events: RefCell<VecDeque<InputEvent>>,
mouse_events: RefCell<VecDeque<InputEvent>>,
button_map: CopyHashMap<u32, u32>,
kb_name: Rc<String>,
mouse_name: Rc<String>,
}
struct XSeatKeyboard(Rc<XSeat>);
struct XSeatMouse(Rc<XSeat>);
impl XSeat {
fn kb_changed(&self) {
if let Some(cb) = self.kb_cb.get() {
cb();
}
}
fn mouse_changed(&self) {
if let Some(cb) = self.mouse_cb.get() {
cb();
}
}
fn mouse_event(&self, event: InputEvent) {
self.mouse_events.borrow_mut().push_back(event);
self.mouse_changed();
}
fn kb_event(&self, event: InputEvent) {
self.kb_events.borrow_mut().push_back(event);
self.kb_changed();
}
async fn update_button_map(&self) {
self.button_map.clear();
let gdbm = XiGetDeviceButtonMapping {
device_id: self.mouse as _,
};
let reply = match self.backend.c.call(&gdbm).await {
Ok(r) => r,
Err(e) => {
log::error!(
"Could not get Xinput button map of device {}: {}",
self.mouse,
ErrorFmt(e),
);
return;
}
};
for (i, map) in reply.get().map.iter().copied().enumerate().rev() {
self.button_map.set(map as u32, i as u32 + 1);
}
}
}
impl InputDevice for XSeatKeyboard {
fn id(&self) -> InputDeviceId {
self.0.kb_id
}
fn removed(&self) -> bool {
self.0.removed.get()
}
fn event(&self) -> Option<InputEvent> {
self.0.kb_events.borrow_mut().pop_front()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.0.kb_cb.set(Some(cb));
}
fn grab(&self, grab: bool) {
self.0.backend.grab_requests.push((self.0.clone(), grab));
}
fn has_capability(&self, cap: InputDeviceCapability) -> bool {
match cap {
InputDeviceCapability::Keyboard => true,
_ => false,
}
}
fn set_left_handed(&self, left_handed: bool) {
let _ = left_handed;
}
fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
let _ = profile;
}
fn set_accel_speed(&self, speed: f64) {
let _ = speed;
}
fn set_transform_matrix(&self, matrix: TransformMatrix) {
let _ = matrix;
}
fn name(&self) -> Rc<String> {
self.0.kb_name.clone()
}
fn dev_t(&self) -> Option<dev_t> {
None
}
fn set_tap_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_drag_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_drag_lock_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_natural_scrolling_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_click_method(&self, method: InputDeviceClickMethod) {
let _ = method;
}
fn set_middle_button_emulation_enabled(&self, enabled: bool) {
let _ = enabled;
}
}
impl InputDevice for XSeatMouse {
fn id(&self) -> InputDeviceId {
self.0.mouse_id
}
fn removed(&self) -> bool {
self.0.removed.get()
}
fn event(&self) -> Option<InputEvent> {
self.0.mouse_events.borrow_mut().pop_front()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.0.mouse_cb.set(Some(cb));
}
fn grab(&self, _grab: bool) {
log::error!("Cannot grab xorg mouse");
}
fn has_capability(&self, cap: InputDeviceCapability) -> bool {
match cap {
InputDeviceCapability::Pointer => true,
_ => false,
}
}
fn set_left_handed(&self, left_handed: bool) {
let _ = left_handed;
}
fn set_accel_profile(&self, profile: InputDeviceAccelProfile) {
let _ = profile;
}
fn set_accel_speed(&self, speed: f64) {
let _ = speed;
}
fn set_transform_matrix(&self, matrix: TransformMatrix) {
let _ = matrix;
}
fn name(&self) -> Rc<String> {
self.0.mouse_name.clone()
}
fn dev_t(&self) -> Option<dev_t> {
None
}
fn set_tap_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_drag_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_drag_lock_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_natural_scrolling_enabled(&self, enabled: bool) {
let _ = enabled;
}
fn set_click_method(&self, method: InputDeviceClickMethod) {
let _ = method;
}
fn set_middle_button_emulation_enabled(&self, enabled: bool) {
let _ = enabled;
}
}