diff --git a/anvil/src/libei.rs b/anvil/src/libei.rs index a12a3037b619..0890c1ff2e2f 100644 --- a/anvil/src/libei.rs +++ b/anvil/src/libei.rs @@ -32,7 +32,7 @@ pub fn listen_eis(handle: &calloop::LoopHandle<'static, AnvilState>) { 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"); + seat.add_touch("virtual touch", &[]); } EiInputEvent::Disconnected => {} EiInputEvent::Event(event) => { diff --git a/src/backend/libei/input.rs b/src/backend/libei/input.rs index 635108c807ba..adffff6d1f98 100644 --- a/src/backend/libei/input.rs +++ b/src/backend/libei/input.rs @@ -44,7 +44,7 @@ impl InputBackend for EiInput { type TouchUpEvent = request::TouchUp; type TouchMotionEvent = request::TouchMotion; type TouchCancelEvent = request::TouchCancel; - type TouchFrameEvent = input::UnusedEvent; + type TouchFrameEvent = request::Frame; type TabletToolAxisEvent = input::UnusedEvent; type TabletToolProximityEvent = input::UnusedEvent; @@ -102,6 +102,8 @@ impl input::Event for T { } } +impl input::TouchFrameEvent for request::Frame {} + impl input::KeyboardKeyEvent for request::KeyboardKey { fn key_code(&self) -> input::Keycode { input::Keycode::from(self.key + 8) diff --git a/src/backend/libei/mod.rs b/src/backend/libei/mod.rs index 05f483f1c6af..355108cee07c 100644 --- a/src/backend/libei/mod.rs +++ b/src/backend/libei/mod.rs @@ -21,7 +21,7 @@ //! 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"); +//! seat.add_touch("virtual touch", &[]); //! } //! EiInputEvent::Disconnected => {} //! EiInputEvent::Event(event) => { @@ -299,7 +299,16 @@ fn convert_request(request: EisRequest) -> Option> { EisRequest::TouchMotion(event) => Some(InputEvent::TouchMotion { event }), EisRequest::TouchCancel(event) => Some(InputEvent::TouchCancel { event }), EisRequest::DeviceClosed(event) => Some(InputEvent::DeviceRemoved { device: event.device }), - EisRequest::Frame(_) => None, + // `ei_device.frame` is not touch-specific: it commits whatever the client has sent + // for that device. The capability check is sufficient here because reis only emits + // a frame for a device that had pending events of its own, and every device we + // create has a single capability. So a frame on a touch-capable device + // necessarily contains touch events. This would need revisiting if a device ever + // advertised `Touch` alongside another capability. + EisRequest::Frame(event) => event + .device + .has_capability(reis::request::DeviceCapability::Touch) + .then_some(InputEvent::TouchFrame { event }), // `TextKeysym`/`TextUtf8` are surfaced as `EiInputEvent::TextKeysym`/`TextUtf8` directly, // so they never reach here. EisRequest::TextKeysym(_) diff --git a/src/backend/libei/seat.rs b/src/backend/libei/seat.rs index bfbd979d0860..c435a7447114 100644 --- a/src/backend/libei/seat.rs +++ b/src/backend/libei/seat.rs @@ -55,7 +55,7 @@ impl EiInputSeat { keyboard: None, pointer: None, pointer_absolute: None, - pointer_absolute_regions: Vec::new(), + regions: Vec::new(), touch: None, text: None, device_keyboard: None, @@ -162,7 +162,7 @@ impl EiInputSeat { let mut inner = self.0.lock().unwrap(); inner.device_pointer_absolute = None; inner.pointer_absolute = Some(name.to_string()); - inner.pointer_absolute_regions = regions.to_vec(); + inner.regions = regions.to_vec(); inner.refresh_devices(); } @@ -171,18 +171,38 @@ impl EiInputSeat { let mut inner = self.0.lock().unwrap(); inner.device_pointer_absolute = None; inner.pointer_absolute = None; - inner.pointer_absolute_regions.clear(); inner.flush(); } /// Add a touch device to the EI seat /// + /// `regions` describes the logical coordinate space(s) the client may send absolute touch + /// coordinates in (see [`EiRegion`]). + /// /// 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) { + pub fn add_touch(&self, name: &str, regions: &[EiRegion]) { let mut inner = self.0.lock().unwrap(); inner.device_touch = None; inner.touch = Some(name.to_string()); + inner.regions = regions.to_vec(); + inner.refresh_devices(); + } + + /// Update the coordinate regions advertised to the client, recreating whichever absolute + /// devices (absolute pointer, touch) this seat already has. + /// + /// Regions are immutable once a device is created, so the devices have to be replaced for a + /// client to see a new output layout/scale. + pub fn update_regions(&self, regions: &[EiRegion]) { + let mut inner = self.0.lock().unwrap(); + inner.regions = regions.to_vec(); + if inner.pointer_absolute.is_some() { + inner.device_pointer_absolute = None; + } + if inner.touch.is_some() { + inner.device_touch = None; + } inner.refresh_devices(); } @@ -239,7 +259,7 @@ struct EiInputSeatInner { pointer_absolute: Option, // Regions advertised on the absolute pointer device, describing the logical // coordinate space(s) the client may address (see `EiRegion`). - pointer_absolute_regions: Vec, + regions: Vec, touch: Option, text: Option, // Devices created in response to client bind @@ -309,31 +329,12 @@ impl EiInputSeatInner { .contains(DeviceCapability::PointerAbsolute) { if let Some(name) = self.pointer_absolute.as_ref() { - let regions = self.pointer_absolute_regions.clone(); + let regions = self.regions.clone(); let device = self.seat.add_device( Some(name), DeviceType::Virtual, DeviceCapability::PointerAbsolute | DeviceCapability::Button | DeviceCapability::Scroll, - |device| { - // Advertise the coordinate space(s) the client may point within. - // These `ei_device.region` events must be sent after the device is - // created but before `done`, which is exactly when this closure runs. - for region in ®ions { - // `region_mapping_id` applies to the region created by the - // next `region` request, so send it first when present. - if let Some(mapping_id) = ®ion.mapping_id { - device.device().region_mapping_id(mapping_id); - } - // `ei_device.region` coordinates are unsigned - device.device().region( - region.rect.loc.x.max(0) as u32, - region.rect.loc.y.max(0) as u32, - region.rect.size.w.max(0) as u32, - region.rect.size.h.max(0) as u32, - region.scale, - ); - } - }, + |device| advertise_regions(device, ®ions), ); device.resumed(); let _ = self.event_sender.send(InputEvent::DeviceAdded { @@ -345,11 +346,12 @@ impl EiInputSeatInner { if self.device_touch.is_none() && self.bound_capabilities.contains(DeviceCapability::Touch) { if let Some(name) = self.touch.as_ref() { + let regions = self.regions.clone(); let device = self.seat.add_device( Some(name), DeviceType::Virtual, DeviceCapability::Touch.into(), - |_| {}, + |device| advertise_regions(device, ®ions), ); device.resumed(); let _ = self.event_sender.send(InputEvent::DeviceAdded { @@ -380,6 +382,28 @@ impl EiInputSeatInner { } } +/// Advertise the coordinate space(s) a client may address on an absolute device. +/// +/// These `ei_device.region` events must be sent after the device is created but before `done`, +/// i.e. from within `add_device`'s callback. +fn advertise_regions(device: &reis::request::Device, regions: &[EiRegion]) { + for region in regions { + // `region_mapping_id` applies to the region created by the next `region` request, + // so send it first when present. + if let Some(mapping_id) = ®ion.mapping_id { + device.device().region_mapping_id(mapping_id); + } + // `ei_device.region` coordinates are unsigned + device.device().region( + region.rect.loc.x.max(0) as u32, + region.rect.loc.y.max(0) as u32, + region.rect.size.w.max(0) as u32, + region.rect.size.h.max(0) as u32, + region.scale, + ); + } +} + // Helper that remove the device on drop, and send `DeviceRemoved` #[derive(Debug)] struct DeviceDropWrapper {