Watch
1
0
Fork
You've already forked smithay
0

EI protocol support using reis

The EI protocol is needed for the xdg-desktop-portal `RemoteDesktop`
portal to emulate input devices, as well as by the `InputCapture` portal
for Synergy-like uses (input-leap supports Wayland with this portal).

This exposes a relatively simple API for a compositor to create EI seats
and devices, mirroring the `Seat` API. It also provides an
`InputBackend`
that converts emulated input to Smithay input events.

Receiver contexts for the `InputCapture` portal are also a bit more
complicated to implement. Those involve capturing input once the cursor
crosses outside the display. That isn't implemented at all here yet.

We may want to change things in the future to accommodate receiver
contexts, etc. But this API is fairly minimal to use, so future breaking
changes shouldn't be too challenging for a compositor to adapt to.
This commit is contained in:
Ian Douglas Scott 2023-10-12 17:31:55 -07:00 • committed by Victoria Brekenfeld
commit 7878e6ed11
7 changed files with 852 additions and 1 deletions

View file

@ -74,6 +74,7 @@ aliasable = { version = "0.1.3", optional = true }
atomic_float = "1.1.0" atomic_float = "1.1.0"
sha2 = "0.10.9" sha2 = "0.10.9"
tracy-client = { version = "0.18.4", default-features = false, optional = true } tracy-client = { version = "0.18.4", default-features = false, optional = true }
reis = { version = "0.6.0", features = ["calloop"] }
[dev-dependencies] [dev-dependencies]
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
@ -87,7 +88,7 @@ pkg-config = { version = "0.3.17", optional = true }
cc = { version = "1.0.79", optional = true } cc = { version = "1.0.79", optional = true }
[features] [features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_libseat", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_pixman", "renderer_multi", "xwayland", "wayland_frontend", "backend_vulkan"] default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_libseat", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_pixman", "renderer_multi", "xwayland", "wayland_frontend", "backend_vulkan", "backend_libei"]
backend_winit = ["winit", "backend_egl", "wayland-client", "wayland-cursor", "wayland-egl", "renderer_gl"] backend_winit = ["winit", "backend_egl", "wayland-client", "wayland-cursor", "wayland-egl", "renderer_gl"]
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/xinput", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"] backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/xinput", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"]
backend_drm = ["drm", "drm-ffi"] backend_drm = ["drm", "drm-ffi"]
@ -100,6 +101,7 @@ backend_session = []
backend_udev = ["udev", "input/udev"] backend_udev = ["udev", "input/udev"]
backend_vulkan = ["ash", "scopeguard"] backend_vulkan = ["ash", "scopeguard"]
backend_session_libseat = ["backend_session", "libseat"] backend_session_libseat = ["backend_session", "libseat"]
backend_libei = ["wayland_frontend"]
desktop = [] desktop = []
renderer_gl = ["gl_generator", "backend_egl"] renderer_gl = ["gl_generator", "backend_egl"]
renderer_glow = ["renderer_gl", "glow"] renderer_glow = ["renderer_gl", "glow"]

301
src/backend/libei/input.rs Normal file
View file

@ -0,0 +1,301 @@
// Implementation of `InputBackend` for reis requests
use reis::{
eis,
request::{self, DeviceCapability},
};
use std::path::PathBuf;
use crate::backend::input::{self, InputBackend};
use super::EiInput;
/// Generic ei scroll event
#[derive(Debug)]
pub enum ScrollEvent {
/// Continuous scroll event
Delta(request::ScrollDelta),
/// Scroll cancel event
Cancel(request::ScrollCancel),
/// Discrete scroll event
Discrete(request::ScrollDiscrete),
/// Scroll stop event
Stop(request::ScrollStop),
}
impl InputBackend for EiInput {
type Device = request::Device;
type KeyboardKeyEvent = request::KeyboardKey;
type PointerAxisEvent = ScrollEvent;
type PointerButtonEvent = request::Button;
type PointerMotionEvent = request::PointerMotion;
type PointerMotionAbsoluteEvent = request::PointerMotionAbsolute;
type GestureSwipeBeginEvent = input::UnusedEvent;
type GestureSwipeUpdateEvent = input::UnusedEvent;
type GestureSwipeEndEvent = input::UnusedEvent;
type GesturePinchBeginEvent = input::UnusedEvent;
type GesturePinchUpdateEvent = input::UnusedEvent;
type GesturePinchEndEvent = input::UnusedEvent;
type GestureHoldBeginEvent = input::UnusedEvent;
type GestureHoldEndEvent = input::UnusedEvent;
type TouchDownEvent = request::TouchDown;
type TouchUpEvent = request::TouchUp;
type TouchMotionEvent = request::TouchMotion;
type TouchCancelEvent = request::TouchCancel;
type TouchFrameEvent = input::UnusedEvent;
type TabletToolAxisEvent = input::UnusedEvent;
type TabletToolProximityEvent = input::UnusedEvent;
type TabletToolTipEvent = input::UnusedEvent;
type TabletToolButtonEvent = input::UnusedEvent;
type SwitchToggleEvent = input::UnusedEvent;
type SpecialEvent = input::UnusedEvent;
}
impl input::Device for request::Device {
fn id(&self) -> String {
use reis::Interface;
use rustix::fd::{AsFd, AsRawFd};
let object = self.device().as_object();
let id = object.id();
// XXX don't panic?
// don't need to be valid after calloop source is destroyed?
// - (source keeps backend open, so weak handle upgrades)
let fd = object.backend().unwrap().as_fd().as_raw_fd();
format!("ei-{fd}-{id}")
}
fn name(&self) -> String {
self.name().unwrap_or("").to_string()
}
fn has_capability(&self, capability: input::DeviceCapability) -> bool {
match capability {
input::DeviceCapability::Gesture => false,
input::DeviceCapability::Keyboard => self.has_capability(DeviceCapability::Keyboard),
input::DeviceCapability::Pointer => {
self.has_capability(DeviceCapability::Pointer)
|| self.has_capability(DeviceCapability::PointerAbsolute)
}
input::DeviceCapability::Switch => false,
input::DeviceCapability::TabletPad => false,
input::DeviceCapability::TabletTool => false,
input::DeviceCapability::Touch => self.has_capability(DeviceCapability::Touch),
}
}
fn usb_id(&self) -> Option<(u32, u32)> {
None
}
fn syspath(&self) -> Option<PathBuf> {
None
}
}
impl<T: request::DeviceEvent + request::EventTime> input::Event<EiInput> for T {
fn time(&self) -> u64 {
request::EventTime::time(self)
}
fn device(&self) -> request::Device {
request::DeviceEvent::device(self).clone()
}
}
impl input::KeyboardKeyEvent<EiInput> for request::KeyboardKey {
fn key_code(&self) -> input::Keycode {
input::Keycode::from(self.key + 8)
}
fn state(&self) -> input::KeyState {
match self.state {
eis::keyboard::KeyState::Released => input::KeyState::Released,
eis::keyboard::KeyState::Press => input::KeyState::Pressed,
}
}
fn count(&self) -> u32 {
1
}
}
impl input::Event<EiInput> for ScrollEvent {
fn time(&self) -> u64 {
match self {
Self::Delta(evt) => evt.time(),
Self::Cancel(evt) => evt.time(),
Self::Discrete(evt) => evt.time(),
Self::Stop(evt) => evt.time(),
}
}
fn device(&self) -> request::Device {
match self {
Self::Delta(evt) => evt.device(),
Self::Cancel(evt) => evt.device(),
Self::Discrete(evt) => evt.device(),
Self::Stop(evt) => evt.device(),
}
}
}
impl input::PointerAxisEvent<EiInput> for ScrollEvent {
fn amount(&self, axis: input::Axis) -> Option<f64> {
match self {
Self::Delta(evt) => match axis {
input::Axis::Horizontal if evt.dx != 0.0 => Some(evt.dx.into()),
input::Axis::Vertical if evt.dy != 0.0 => Some(evt.dy.into()),
_ => None,
},
// Same as Mutter
Self::Cancel(evt) => match axis {
input::Axis::Horizontal if evt.x => Some(0.01),
input::Axis::Vertical if evt.y => Some(0.01),
_ => None,
},
Self::Discrete(_evt) => None,
Self::Stop(evt) => match axis {
input::Axis::Horizontal if evt.x => Some(0.0),
input::Axis::Vertical if evt.y => Some(0.0),
_ => None,
},
}
}
fn amount_v120(&self, axis: input::Axis) -> Option<f64> {
match self {
Self::Discrete(evt) => match axis {
input::Axis::Horizontal if evt.discrete_dx != 0 => Some(evt.discrete_dx.into()),
input::Axis::Vertical if evt.discrete_dy != 0 => Some(evt.discrete_dy.into()),
_ => None,
},
_ => None,
}
}
fn source(&self) -> input::AxisSource {
// Mutter seems to also use wheel for all the scroll events
input::AxisSource::Wheel
}
fn relative_direction(&self, _axis: input::Axis) -> input::AxisRelativeDirection {
input::AxisRelativeDirection::Identical
}
}
impl input::PointerButtonEvent<EiInput> for request::Button {
fn button_code(&self) -> u32 {
self.button
}
fn state(&self) -> input::ButtonState {
match self.state {
eis::button::ButtonState::Press => input::ButtonState::Pressed,
eis::button::ButtonState::Released => input::ButtonState::Released,
}
}
}
impl input::PointerMotionEvent<EiInput> for request::PointerMotion {
fn delta_x(&self) -> f64 {
self.dx.into()
}
fn delta_y(&self) -> f64 {
self.dy.into()
}
fn delta_x_unaccel(&self) -> f64 {
self.dx.into()
}
fn delta_y_unaccel(&self) -> f64 {
self.dy.into()
}
}
impl input::PointerMotionAbsoluteEvent<EiInput> for request::PointerMotionAbsolute {}
impl input::AbsolutePositionEvent<EiInput> for request::PointerMotionAbsolute {
fn x(&self) -> f64 {
self.dx_absolute.into()
}
fn y(&self) -> f64 {
self.dy_absolute.into()
}
fn x_transformed(&self, _width: i32) -> f64 {
self.dx_absolute.into()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.dy_absolute.into()
}
}
impl input::TouchDownEvent<EiInput> for request::TouchDown {}
impl input::TouchEvent<EiInput> for request::TouchDown {
fn slot(&self) -> input::TouchSlot {
Some(self.touch_id).into()
}
}
impl input::AbsolutePositionEvent<EiInput> for request::TouchDown {
fn x(&self) -> f64 {
self.x.into()
}
fn y(&self) -> f64 {
self.y.into()
}
fn x_transformed(&self, _width: i32) -> f64 {
self.x.into()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.y.into()
}
}
impl input::TouchUpEvent<EiInput> for request::TouchUp {}
impl input::TouchEvent<EiInput> for request::TouchUp {
fn slot(&self) -> input::TouchSlot {
Some(self.touch_id).into()
}
}
impl input::TouchMotionEvent<EiInput> for request::TouchMotion {}
impl input::TouchEvent<EiInput> for request::TouchMotion {
fn slot(&self) -> input::TouchSlot {
Some(self.touch_id).into()
}
}
impl input::AbsolutePositionEvent<EiInput> for request::TouchMotion {
fn x(&self) -> f64 {
self.x.into()
}
fn y(&self) -> f64 {
self.y.into()
}
fn x_transformed(&self, _width: i32) -> f64 {
self.x.into()
}
fn y_transformed(&self, _height: i32) -> f64 {
self.y.into()
}
}
impl input::TouchCancelEvent<EiInput> for request::TouchCancel {}
impl input::TouchEvent<EiInput> for request::TouchCancel {
fn slot(&self) -> input::TouchSlot {
Some(self.touch_id).into()
}
}

269
src/backend/libei/mod.rs Normal file
View file

@ -0,0 +1,269 @@
//! Input backend for `libei` sender contexts
//!
//! ``` no_run
//! # let event_loop: calloop::EventLoop<()> = todo!();
//!
//! use reis::{calloop::EisListenerSource, eis};
//! use smithay::{
//! backend::libei::{EiInput, EiInputEvent},
//! input::keyboard::XkbConfig,
//! };
//!
//! let listener = eis::Listener::bind_auto().unwrap();
//! let listener_source = EisListenerSource::new(listener);
//! let handle = event_loop.handle();
//! event_loop.handle().insert_source(listener_source, |context, _, _| {
//! let source = EiInput::new(context);
//! handle.insert_source(source, |event, connection, _data| {
//! match event {
//! EiInputEvent::Connected => {
//! let seat = connection.add_seat("default");
//! let _ = seat.add_keyboard("virtual keyboard", XkbConfig::default());
//! seat.add_pointer("virtual pointer");
//! seat.add_pointer_absolute("virtual absolute pointer");
//! seat.add_touch("virtual touch");
//! }
//! EiInputEvent::Disconnected => {}
//! EiInputEvent::Event(event) => {
//! // Pass input event to compositor's input event handling logic
//! // ...
//! }
//! }
//! }).unwrap();
//! Ok(calloop::PostAction::Continue)
//! }).unwrap();
//! ```
// TODO: Add helper for receiver contexts
use calloop::{EventSource, PostAction, Readiness, Token, TokenFactory};
use reis::{
calloop::EisRequestSourceEvent,
eis,
request::{DeviceCapability, EisRequest},
};
use std::{
io,
sync::{Arc, Mutex},
};
use crate::backend::input::InputEvent;
mod input;
pub use input::ScrollEvent;
mod seat;
pub use seat::EiInputSeat;
/// An [`EventSource`] for receiving input from an EI sender context and
/// converting to [`InputEvent`]s.
#[derive(Debug)]
pub struct EiInput {
source: reis::calloop::EisRequestSource,
connection: Option<EiInputConnection>,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
channel_source: calloop::channel::Channel<InputEvent<EiInput>>,
}
impl EiInput {
/// Create an EI sender event source.
///
/// `context` should be a new EI socket that has not been used yet.
pub fn new(context: eis::Context) -> Self {
let (event_sender, channel_source) = calloop::channel::channel();
Self {
source: reis::calloop::EisRequestSource::new(context, 0),
event_sender,
channel_source,
connection: None,
}
}
}
/// A connection for an EI sender context that can be used to add seats and
/// devices.
#[derive(Debug)]
pub struct EiInputConnection(Arc<EiInputConnectionInner>);
#[derive(Debug)]
struct EiInputConnectionInner {
connection: reis::request::Connection,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
seats: Mutex<Vec<EiInputSeat>>,
}
impl EiInputConnection {
fn new(
connection: reis::request::Connection,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
) -> Self {
Self(Arc::new(EiInputConnectionInner {
connection,
event_sender,
seats: Mutex::new(Vec::new()),
}))
}
/// Add a seat to the EI connection
pub fn add_seat(&self, name: &str) -> EiInputSeat {
let seat = self.0.connection.add_seat(
Some(name),
// Capabilities can't be added to a seat; so advertise them
// all, but only create relevant devices on bind.
DeviceCapability::Pointer
| DeviceCapability::PointerAbsolute
| DeviceCapability::Keyboard
| DeviceCapability::Touch
| DeviceCapability::Scroll
| DeviceCapability::Button,
);
let seat = EiInputSeat::new(self, seat, self.0.event_sender.clone());
self.0.seats.lock().unwrap().push(seat.clone());
seat
}
/// Send buffered events on EI socket
pub fn flush(&self) -> rustix::io::Result<()> {
self.0.connection.flush()
}
}
/// An event produced by an [`EiInput`] event source.
#[derive(Debug)]
pub enum EiInputEvent {
/// The client has finished the EI handshake. Seats and devices can
/// then be added.
Connected,
/// The client has disconnected from the server.
Disconnected,
/// An input event has been received from the client.
Event(InputEvent<EiInput>),
}
impl EventSource for EiInput {
type Event = EiInputEvent;
type Metadata = EiInputConnection;
type Ret = ();
type Error = io::Error;
fn process_events<F>(
&mut self,
readiness: Readiness,
token: Token,
mut cb: F,
) -> Result<PostAction, <Self as EventSource>::Error>
where
F: FnMut(EiInputEvent, &mut EiInputConnection),
{
let _ = self.channel_source.process_events(readiness, token, |event, ()| {
if let calloop::channel::Event::Msg(event) = event {
// Can't create device until there's a connection, so no channel messages
let connection = self.connection.as_mut().unwrap();
cb(EiInputEvent::Event(event), connection);
}
});
self.source.process_events(readiness, token, |event, connection| {
// Wrap connection in `EiInputConnection` if not created yet
if self.connection.is_none() {
self.connection = Some(EiInputConnection::new(
connection.clone(),
self.event_sender.clone(),
));
}
let connection = self.connection.as_mut().unwrap();
match event {
Ok(EisRequestSourceEvent::Connected) => {
if connection.0.connection.context_type() == eis::handshake::ContextType::Receiver {
connection
.0
.connection
.disconnected(eis::connection::DisconnectReason::Disconnected, None);
let _ = connection.flush();
return Ok(PostAction::Remove);
}
cb(EiInputEvent::Connected, connection);
}
Ok(EisRequestSourceEvent::Request(EisRequest::Disconnect)) => {
cb(EiInputEvent::Disconnected, connection);
return Ok(PostAction::Remove);
}
Ok(EisRequestSourceEvent::Request(EisRequest::Bind(request))) => {
if let Some(seat) = connection
.0
.seats
.lock()
.unwrap()
.iter()
.find(|seat| **seat == request.seat)
{
seat.bind(request.capabilities);
}
}
Ok(EisRequestSourceEvent::Request(request)) => {
if let Some(input_event) = convert_request(request) {
cb(EiInputEvent::Event(input_event), connection);
}
}
Err(err) => {
tracing::error!("Libei client error: {}", err);
return Ok(PostAction::Remove);
}
}
let _ = connection.flush();
Ok(PostAction::Continue)
})
}
fn register(
&mut self,
poll: &mut calloop::Poll,
token_factory: &mut TokenFactory,
) -> Result<(), calloop::Error> {
self.channel_source.register(poll, token_factory)?;
self.source.register(poll, token_factory)
}
fn reregister(
&mut self,
poll: &mut calloop::Poll,
token_factory: &mut TokenFactory,
) -> Result<(), calloop::Error> {
self.channel_source.reregister(poll, token_factory)?;
self.source.reregister(poll, token_factory)
}
fn unregister(&mut self, poll: &mut calloop::Poll) -> Result<(), calloop::Error> {
self.channel_source.unregister(poll)?;
self.source.unregister(poll)
}
}
fn convert_request(request: EisRequest) -> Option<InputEvent<EiInput>> {
match request {
EisRequest::KeyboardKey(event) => Some(InputEvent::Keyboard { event }),
EisRequest::PointerMotion(event) => Some(InputEvent::PointerMotion { event }),
EisRequest::PointerMotionAbsolute(event) => Some(InputEvent::PointerMotionAbsolute { event }),
EisRequest::Button(event) => Some(InputEvent::PointerButton { event }),
EisRequest::ScrollDelta(event) => Some(InputEvent::PointerAxis {
event: ScrollEvent::Delta(event),
}),
EisRequest::ScrollStop(event) => Some(InputEvent::PointerAxis {
event: ScrollEvent::Stop(event),
}),
EisRequest::ScrollCancel(event) => Some(InputEvent::PointerAxis {
event: ScrollEvent::Cancel(event),
}),
EisRequest::ScrollDiscrete(event) => Some(InputEvent::PointerAxis {
event: ScrollEvent::Discrete(event),
}),
EisRequest::TouchDown(event) => Some(InputEvent::TouchDown { event }),
EisRequest::TouchUp(event) => Some(InputEvent::TouchUp { event }),
EisRequest::TouchMotion(event) => Some(InputEvent::TouchMotion { event }),
EisRequest::TouchCancel(event) => Some(InputEvent::TouchCancel { event }),
EisRequest::Frame(_) => None,
EisRequest::Disconnect
| EisRequest::Bind(_)
| EisRequest::DeviceStartEmulating(_)
| EisRequest::DeviceStopEmulating(_) => None,
}
}

274
src/backend/libei/seat.rs Normal file
View file

@ -0,0 +1,274 @@
use reis::{eis::device::DeviceType, enumflags2::BitFlags, request::DeviceCapability};
use std::sync::{Arc, Mutex, Weak};
use xkbcommon::xkb;
use crate::{
backend::input::InputEvent,
input::keyboard::{KeymapFile, XkbConfig},
};
use super::{EiInput, EiInputConnection, EiInputConnectionInner};
/// A seat advertised on an EI sender context
#[derive(Clone, Debug)]
pub struct EiInputSeat(Arc<Mutex<EiInputSeatInner>>);
impl PartialEq<reis::request::Seat> for EiInputSeat {
fn eq(&self, other: &reis::request::Seat) -> bool {
self.0.lock().unwrap().seat == *other
}
}
impl EiInputSeat {
pub(super) fn new(
connection: &EiInputConnection,
seat: reis::request::Seat,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
) -> Self {
Self(Arc::new(Mutex::new(EiInputSeatInner {
connection: Arc::downgrade(&connection.0),
seat,
event_sender,
keyboard: None,
pointer: None,
pointer_absolute: None,
touch: None,
device_keyboard: None,
device_pointer: None,
device_pointer_absolute: None,
device_touch: None,
bound_capabilities: BitFlags::empty(),
})))
}
pub(super) fn bind(&self, capabilities: BitFlags<DeviceCapability>) {
let mut inner = self.0.lock().unwrap();
inner.bound_capabilities = capabilities;
inner.refresh_devices();
}
/// Add a keyboard device to the EI seat
///
/// Calling on a seat that already has a keyboard device will remove
/// that device and add a new one.
pub fn add_keyboard(
&self,
name: &str,
xkb_config: XkbConfig<'_>,
) -> Result<(), crate::input::keyboard::Error> {
let mut inner = self.0.lock().unwrap();
inner.device_keyboard = None;
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = xkb_config.compile_keymap(&context).map_err(|_| {
tracing::debug!("Loading keymap from XkbConfig failed");
crate::input::keyboard::Error::BadKeymap
})?;
let keymap_file = KeymapFile::new(&keymap);
inner.keyboard = Some((name.to_string(), keymap_file));
inner.refresh_devices();
Ok(())
}
/// Remove keyboard device from the EI seat
pub fn remove_keyboard(&self) {
let mut inner = self.0.lock().unwrap();
inner.device_keyboard = None;
inner.keyboard = None;
}
/// Add a pointer device to the EI seat
///
/// The EI device will have button and scroll capabilities in addition
/// to the pointer capability.
///
/// Calling on a seat that already has a pointer device will remove
/// that device and add a new one.
pub fn add_pointer(&self, name: &str) {
let mut inner = self.0.lock().unwrap();
inner.device_pointer = None;
inner.pointer = Some(name.to_string());
inner.refresh_devices();
}
/// Remove pointer device from the EI seat
pub fn remove_pointer(&self) {
let mut inner = self.0.lock().unwrap();
inner.device_pointer = None;
inner.pointer = None;
}
/// Add an absolute pointer device to the EI seat
///
/// The EI device will have button and scroll capabilities in addition
/// to the pointer absolute capability.
///
/// Calling on a seat that already has an absolute pointer device will remove
/// that device and add a new one.
pub fn add_pointer_absolute(&self, name: &str) {
let mut inner = self.0.lock().unwrap();
inner.device_pointer_absolute = None;
inner.pointer_absolute = Some(name.to_string());
inner.refresh_devices();
}
/// Remove absolute pointer device from the EI seat
pub fn remove_pointer_absolute(&self) {
let mut inner = self.0.lock().unwrap();
inner.device_pointer_absolute = None;
inner.pointer_absolute = None;
}
/// Add a touch device to the EI seat
///
/// Calling on a seat that already has a touch device will remove
/// that device and add a new one.
pub fn add_touch(&self, name: &str) {
let mut inner = self.0.lock().unwrap();
inner.device_touch = None;
inner.touch = Some(name.to_string());
inner.refresh_devices();
}
/// Remove touch device from the EI seat
pub fn remove_touch(&self) {
let mut inner = self.0.lock().unwrap();
inner.device_touch = None;
inner.touch = None;
}
/// Remove seat from EI connection
pub fn remove(&self) {
let inner = self.0.lock().unwrap();
inner.seat.remove();
if let Some(connection) = inner.connection.upgrade() {
let mut seats = connection.seats.lock().unwrap();
if let Some(idx) = seats.iter().position(|s| Arc::ptr_eq(&s.0, &self.0)) {
seats.remove(idx);
}
}
}
}
#[derive(Debug)]
struct EiInputSeatInner {
connection: Weak<EiInputConnectionInner>,
seat: reis::request::Seat,
bound_capabilities: BitFlags<DeviceCapability>,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
// Interfaces advertised by the server
keyboard: Option<(String, KeymapFile)>,
pointer: Option<String>,
pointer_absolute: Option<String>,
touch: Option<String>,
// Devices created in response to client bind
device_keyboard: Option<DeviceDropWrapper>,
device_pointer: Option<DeviceDropWrapper>,
device_pointer_absolute: Option<DeviceDropWrapper>,
device_touch: Option<DeviceDropWrapper>,
}
impl EiInputSeatInner {
// Add any devices the server provides, for capabilities the client has bound, if no device yet
fn refresh_devices(&mut self) {
if self.device_keyboard.is_none() && self.bound_capabilities.contains(DeviceCapability::Keyboard) {
if let Some((name, keymap_file)) = self.keyboard.as_ref() {
let device = self.seat.add_device(
Some(name),
DeviceType::Virtual,
DeviceCapability::Keyboard.into(),
|device| {
let keyboard = device.interface::<reis::eis::Keyboard>().unwrap();
let _ = keymap_file.with_fd(true, |fd, len| {
keyboard.keymap(reis::eis::keyboard::KeymapType::Xkb, len as u32, fd);
});
},
);
device.resumed();
let _ = self.event_sender.send(InputEvent::DeviceAdded {
device: device.clone(),
});
self.device_keyboard = Some(DeviceDropWrapper::new(device, &self.event_sender));
}
}
if self.device_pointer.is_none() && self.bound_capabilities.contains(DeviceCapability::Pointer) {
if let Some(name) = self.pointer.as_ref() {
let device = self.seat.add_device(
Some(name),
DeviceType::Virtual,
DeviceCapability::Pointer | DeviceCapability::Button | DeviceCapability::Scroll,
|_| {},
);
device.resumed();
let _ = self.event_sender.send(InputEvent::DeviceAdded {
device: device.clone(),
});
self.device_pointer = Some(DeviceDropWrapper::new(device, &self.event_sender));
}
}
if self.device_pointer_absolute.is_none()
&& self
.bound_capabilities
.contains(DeviceCapability::PointerAbsolute)
{
if let Some(name) = self.pointer_absolute.as_ref() {
let device = self.seat.add_device(
Some(name),
DeviceType::Virtual,
DeviceCapability::PointerAbsolute | DeviceCapability::Button | DeviceCapability::Scroll,
|_| {},
);
device.resumed();
let _ = self.event_sender.send(InputEvent::DeviceAdded {
device: device.clone(),
});
self.device_pointer_absolute = Some(DeviceDropWrapper::new(device, &self.event_sender));
}
}
if self.device_touch.is_none() && self.bound_capabilities.contains(DeviceCapability::Touch) {
if let Some(name) = self.touch.as_ref() {
let device = self.seat.add_device(
Some(name),
DeviceType::Virtual,
DeviceCapability::Touch.into(),
|_| {},
);
device.resumed();
let _ = self.event_sender.send(InputEvent::DeviceAdded {
device: device.clone(),
});
self.device_touch = Some(DeviceDropWrapper::new(device, &self.event_sender));
}
}
}
}
// Helper that remove the device on drop, and send `DeviceRemoved`
#[derive(Debug)]
struct DeviceDropWrapper {
device: reis::request::Device,
event_sender: calloop::channel::Sender<InputEvent<EiInput>>,
}
impl DeviceDropWrapper {
fn new(
device: reis::request::Device,
event_sender: &calloop::channel::Sender<InputEvent<EiInput>>,
) -> Self {
Self {
device,
event_sender: event_sender.clone(),
}
}
}
impl Drop for DeviceDropWrapper {
fn drop(&mut self) {
let _ = self.event_sender.send(InputEvent::DeviceRemoved {
device: self.device.clone(),
});
self.device.remove();
}
}

View file

@ -105,6 +105,9 @@ pub mod winit;
#[cfg(feature = "backend_x11")] #[cfg(feature = "backend_x11")]
pub mod x11; pub mod x11;
#[cfg(feature = "backend_libei")]
pub mod libei;
/// Error that can happen when swapping buffers. /// Error that can happen when swapping buffers.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum SwapBuffersError { pub enum SwapBuffersError {

View file

@ -13,6 +13,7 @@ pub use glow;
pub use input; pub use input;
#[cfg(feature = "renderer_pixman")] #[cfg(feature = "renderer_pixman")]
pub use pixman; pub use pixman;
pub use reis;
pub use rustix; pub use rustix;
#[cfg(feature = "tracy_gpu_profiling")] #[cfg(feature = "tracy_gpu_profiling")]
pub use tracy_client; pub use tracy_client;

View file

@ -2,3 +2,4 @@
[default.extend-words] [default.extend-words]
WRONLY = "WRONLY" WRONLY = "WRONLY"
eis = "eis"