use crate::allocator::BufferObject;
use crate::async_engine::Phase;
use crate::async_engine::SpawnedFuture;
use crate::backend::AXIS_120;
use crate::backend::AxisSource;
use crate::backend::Backend;
use crate::backend::BackendConnectorState;
use crate::backend::BackendDrmDevice;
use crate::backend::BackendEvent;
use crate::backend::ButtonState;
use crate::backend::Connector;
use crate::backend::ConnectorEvent;
use crate::backend::ConnectorId;
use crate::backend::ConnectorKernelId;
use crate::backend::DrmDeviceId;
use crate::backend::DrmEvent;
use crate::backend::InputDevice;
use crate::backend::InputDeviceAccelProfile;
use crate::backend::InputDeviceCapability;
use crate::backend::InputDeviceClickMethod;
use crate::backend::InputDeviceId;
use crate::backend::InputDeviceScrollMethod;
use crate::backend::InputEvent;
use crate::backend::KeyState;
use crate::backend::Mode;
use crate::backend::MonitorInfo;
use crate::backend::ScrollAxis;
use crate::backend::TransformMatrix;
use crate::backend::transaction::BackendAppliedConnectorTransaction;
use crate::backend::transaction::BackendConnectorTransaction;
use crate::backend::transaction::BackendConnectorTransactionError;
use crate::backend::transaction::BackendConnectorTransactionType;
use crate::backend::transaction::BackendConnectorTransactionTypeDyn;
use crate::backend::transaction::BackendPreparedConnectorTransaction;
use crate::cmm::cmm_primaries::Primaries;
use crate::evdev::input_event_codes::InputEventCode;
use crate::fixed::Fixed;
use crate::format::Format;
use crate::format::XRGB8888;
use crate::gfx_api::AcquireSync;
use crate::gfx_api::GfxApi;
use crate::gfx_api::GfxContext;
use crate::gfx_api::GfxError;
use crate::gfx_api::GfxFramebuffer;
use crate::gfx_api::GfxTexture;
use crate::gfx_api::ReleaseSync;
use crate::ifs::wl_output::OutputId;
use crate::state::State;
use crate::time::Time;
use crate::utils::bhash::BHashMap;
use crate::utils::clonecell::CloneCell;
use crate::utils::copyhashmap::CopyHashMap;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::fuse::fuse_inode::FuseInodeWithKey;
use crate::utils::liveness::Liveness;
use crate::utils::numcell::NumCell;
use crate::utils::queue::AsyncQueue;
use crate::utils::syncqueue::SyncQueue;
use crate::video::drm::ConnectorType;
use crate::video::drm::Drm;
use crate::video::drm::DrmError;
use crate::video::drm::DrmVersion;
use crate::video::gbm::GBM_BO_USE_RENDERING;
use crate::video::gbm::GbmBo;
use crate::video::gbm::GbmDevice;
use crate::video::gbm::GbmError;
use crate::wire_xcon::ChangeProperty;
use crate::wire_xcon::ChangeWindowAttributes;
use crate::wire_xcon::ConfigureNotify;
use crate::wire_xcon::CreateCursor;
use crate::wire_xcon::CreatePixmap;
use crate::wire_xcon::CreateWindow;
use crate::wire_xcon::CreateWindowValues;
use crate::wire_xcon::DestroyNotify;
use crate::wire_xcon::Dri3Open;
use crate::wire_xcon::Dri3PixmapFromBuffers;
use crate::wire_xcon::Dri3QueryVersion;
use crate::wire_xcon::Extension;
use crate::wire_xcon::FreePixmap;
use crate::wire_xcon::MapWindow;
use crate::wire_xcon::PresentCompleteNotify;
use crate::wire_xcon::PresentIdleNotify;
use crate::wire_xcon::PresentPixmap;
use crate::wire_xcon::PresentQueryVersion;
use crate::wire_xcon::PresentSelectInput;
use crate::wire_xcon::XiButtonPress;
use crate::wire_xcon::XiButtonRelease;
use crate::wire_xcon::XiDeviceInfo;
use crate::wire_xcon::XiEnter;
use crate::wire_xcon::XiEventMask;
use crate::wire_xcon::XiGetDeviceButtonMapping;
use crate::wire_xcon::XiGrabDevice;
use crate::wire_xcon::XiHierarchy;
use crate::wire_xcon::XiKeyPress;
use crate::wire_xcon::XiKeyRelease;
use crate::wire_xcon::XiMotion;
use crate::wire_xcon::XiQueryDevice;
use crate::wire_xcon::XiQueryVersion;
use crate::wire_xcon::XiSelectEvents;
use crate::wire_xcon::XiUngrabDevice;
use crate::wire_xcon::XkbPerClientFlags;
use crate::wire_xcon::XkbUseExtension;
use crate::xcon::Event;
use crate::xcon::XEvent;
use crate::xcon::Xcon;
use crate::xcon::XconError;
use crate::xcon::consts::ATOM_STRING;
use crate::xcon::consts::ATOM_WM_CLASS;
use crate::xcon::consts::EVENT_MASK_EXPOSURE;
use crate::xcon::consts::EVENT_MASK_STRUCTURE_NOTIFY;
use crate::xcon::consts::EVENT_MASK_VISIBILITY_CHANGE;
use crate::xcon::consts::GRAB_MODE_ASYNC;
use crate::xcon::consts::GRAB_STATUS_SUCCESS;
use crate::xcon::consts::INPUT_DEVICE_ALL;
use crate::xcon::consts::INPUT_DEVICE_ALL_MASTER;
use crate::xcon::consts::INPUT_DEVICE_TYPE_MASTER_KEYBOARD;
use crate::xcon::consts::INPUT_HIERARCHY_MASK_MASTER_ADDED;
use crate::xcon::consts::INPUT_HIERARCHY_MASK_MASTER_REMOVED;
use crate::xcon::consts::PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
use crate::xcon::consts::PRESENT_EVENT_MASK_IDLE_NOTIFY;
use crate::xcon::consts::PROP_MODE_REPLACE;
use crate::xcon::consts::WINDOW_CLASS_INPUT_OUTPUT;
use crate::xcon::consts::XI_EVENT_MASK_BUTTON_PRESS;
use crate::xcon::consts::XI_EVENT_MASK_BUTTON_RELEASE;
use crate::xcon::consts::XI_EVENT_MASK_ENTER;
use crate::xcon::consts::XI_EVENT_MASK_FOCUS_IN;
use crate::xcon::consts::XI_EVENT_MASK_FOCUS_OUT;
use crate::xcon::consts::XI_EVENT_MASK_HIERARCHY;
use crate::xcon::consts::XI_EVENT_MASK_KEY_PRESS;
use crate::xcon::consts::XI_EVENT_MASK_KEY_RELEASE;
use crate::xcon::consts::XI_EVENT_MASK_LEAVE;
use crate::xcon::consts::XI_EVENT_MASK_MOTION;
use crate::xcon::consts::XI_EVENT_MASK_TOUCH_BEGIN;
use crate::xcon::consts::XI_EVENT_MASK_TOUCH_END;
use crate::xcon::consts::XI_EVENT_MASK_TOUCH_UPDATE;
use crate::xcon::consts::XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT;
use jay_proc::GetLiveness;
use std::any::Any;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::error::Error;
use std::future::pending;
use std::rc::Rc;
use thiserror::Error;
use uapi::c::dev_t;
mod x_dfs_g_fuse;
#[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 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 drm_device_id = state.drm_dev_ids.next();
let ctx = match state.create_gfx_context(drm_device_id, &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,
drm_dev,
liveness: Default::default(),
});
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(())
})
}
fn debugfs(self: Rc<Self>) -> Option<FuseInodeWithKey> {
self.debugfs()
}
}
#[derive(GetLiveness)]
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,
liveness: Liveness,
}
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().values() {
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 Some(format) = formats.get(&FORMAT.drm) else {
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 fb = match self.ctx.clone().dmabuf_fb(dma) {
Ok(f) => f,
Err(e) => return Err(XBackendError::CreateFramebuffer(e)),
};
let tex = match self.ctx.clone().dmabuf_tex(dma) {
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, },
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),
liveness: Default::default(),
});
{
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: Some(vec![]),
output_id: OutputId::new(
"",
"X.Org Foundation",
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)),
liveness: Default::default(),
});
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 Some(output) = self.outputs.get(&window) else {
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 Some(output) = self.outputs.get(&event.window) else {
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;
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 (Some(win), Some(seat)) = (
self.outputs.get(&event.event),
self.mouse_seats.get(&event.deviceid),
) else {
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 Some(output) = self.outputs.remove(&event.event) else {
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 Some(output) = self.outputs.get(&event.event) else {
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()) {
#[allow(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()>) {
}
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");
}
fn set_gfx_api(&self, _api: GfxApi) {
log::warn!("set_gfx_api is not supported by the X backend");
}
fn gfx_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
}
}
#[derive(GetLiveness)]
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>,
liveness: Liveness,
}
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) {
}
fn drm_dev(&self) -> Option<DrmDeviceId> {
Some(self.backend.drm_device_id)
}
fn effectively_locked(&self) -> bool {
true
}
fn state(&self) -> BackendConnectorState {
self.state.borrow().clone()
}
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: BHashMap<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>) {
}
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
self.send_state();
Ok(())
}
}
#[derive(GetLiveness)]
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>,
liveness: Liveness,
}
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;
}
fn set_scroll_method(&self, method: InputDeviceScrollMethod) {
let _ = method;
}
fn set_scroll_button(&self, button: Option<InputEventCode>) {
let _ = button;
}
fn set_scroll_button_lock(&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;
}
fn set_scroll_method(&self, method: InputDeviceScrollMethod) {
let _ = method;
}
fn set_scroll_button(&self, button: Option<InputEventCode>) {
let _ = button;
}
fn set_scroll_button_lock(&self, enabled: bool) {
let _ = enabled;
}
}