Ei protocol support
- Use the configured keyboard layout, track ei seats to reconfigure their keyboards - DBus interface - Disambiguate input devices per backend instance - Add support for `ei_keysym` and `ei_text` - Act as input method for ei_text UTF-8 injection - Create a region per output for abosolute pointer position - Map ei region for touchscreen emulation - Release pointer buttons and keys on removal - Send modifiers to EI connections
This commit is contained in:
parent
e939a72b30
commit
f4ee52c46a
15 changed files with 1092 additions and 218 deletions
|
|
@ -211,7 +211,7 @@ fn init_libinput(
|
||||||
state.backend.kms().input_devices.remove(&*device.name());
|
state.backend.kms().input_devices.remove(&*device.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
state.process_input_event(event);
|
state.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||||
|
|
||||||
for output in state.common.shell.read().outputs() {
|
for output in state.common.shell.read().outputs() {
|
||||||
state.backend.kms().schedule_render(output);
|
state.backend.kms().schedule_render(output);
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,9 @@ impl State {
|
||||||
WinitEvent::Focus(true) => {
|
WinitEvent::Focus(true) => {
|
||||||
for seat in self.common.shell.read().seats.iter() {
|
for seat in self.common.shell.read().seats.iter() {
|
||||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||||
if devices.has_device(&WinitVirtualDevice) {
|
if devices
|
||||||
|
.has_device(&WinitVirtualDevice, &crate::input::InputBackendId::Normal)
|
||||||
|
{
|
||||||
seat.set_active_output(&self.backend.winit().output);
|
seat.set_active_output(&self.backend.winit().output);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +342,9 @@ impl State {
|
||||||
render_ping.ping();
|
render_ping.ping();
|
||||||
}
|
}
|
||||||
WinitEvent::Redraw => render_ping.ping(),
|
WinitEvent::Redraw => render_ping.ping(),
|
||||||
WinitEvent::Input(event) => self.process_input_event(event),
|
WinitEvent::Input(event) => {
|
||||||
|
self.process_input_event(event, crate::input::InputBackendId::Normal)
|
||||||
|
}
|
||||||
WinitEvent::CloseRequested => {
|
WinitEvent::CloseRequested => {
|
||||||
self.common.should_stop = true;
|
self.common.should_stop = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -534,14 +534,14 @@ impl State {
|
||||||
let device = event.device();
|
let device = event.device();
|
||||||
for seat in self.common.shell.read().seats.iter() {
|
for seat in self.common.shell.read().seats.iter() {
|
||||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||||
if devices.has_device(&device) {
|
if devices.has_device(&device, &crate::input::InputBackendId::Normal) {
|
||||||
seat.set_active_output(&output);
|
seat.set_active_output(&output);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.process_input_event(event);
|
self.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||||
// TODO actually figure out the output
|
// TODO actually figure out the output
|
||||||
for output in self.common.shell.read().outputs() {
|
for output in self.common.shell.read().outputs() {
|
||||||
self.backend.x11().schedule_render(output);
|
self.backend.x11().schedule_render(output);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
input::InputBackendId,
|
||||||
shell::Shell,
|
shell::Shell,
|
||||||
state::{BackendData, State},
|
state::{BackendData, State},
|
||||||
utils::prelude::OutputExt,
|
utils::prelude::OutputExt,
|
||||||
|
|
@ -820,6 +821,27 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let ei_connections = state
|
||||||
|
.common
|
||||||
|
.ei_keyboard_source
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for conn in &ei_connections {
|
||||||
|
state.release_ei_keyboard(conn);
|
||||||
|
state.clear_input_source_state(&InputBackendId::Ei(conn.clone()));
|
||||||
|
}
|
||||||
|
for ei_seat in state.common.ei_seats.values() {
|
||||||
|
if let Err(err) =
|
||||||
|
ei_seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&value))
|
||||||
|
{
|
||||||
|
warn!(?err, "Failed to update libei keyboard keymap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !state.common.ei_seats.is_empty() {
|
||||||
|
let seat = state.common.shell.read().seats.last_active().clone();
|
||||||
|
state.broadcast_ei_keyboard_modifiers(&seat);
|
||||||
|
}
|
||||||
state.common.config.cosmic_conf.xkb_config = value;
|
state.common.config.cosmic_conf.xkb_config = value;
|
||||||
}
|
}
|
||||||
"keyboard_config" => {
|
"keyboard_config" => {
|
||||||
|
|
|
||||||
79
src/dbus/ei.rs
Normal file
79
src/dbus/ei.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use std::{
|
||||||
|
os::unix::net::UnixStream,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use smithay::reexports::calloop;
|
||||||
|
use zbus::names::{UniqueName, WellKnownName};
|
||||||
|
|
||||||
|
use super::name_owners::NameOwners;
|
||||||
|
|
||||||
|
static ALLOWED_NAMES: &[WellKnownName] = &[WellKnownName::from_static_str_unchecked(
|
||||||
|
"org.freedesktop.impl.portal.desktop.cosmic",
|
||||||
|
)];
|
||||||
|
|
||||||
|
/// Channel for handing the EI socketpair (and requested device types)
|
||||||
|
/// It's `None` until the EI sender side has been set up
|
||||||
|
type EiSender = Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>;
|
||||||
|
|
||||||
|
struct Ei {
|
||||||
|
ei_sender: EiSender,
|
||||||
|
name_owners: NameOwners,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ei {
|
||||||
|
async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> {
|
||||||
|
if self.name_owners.check_owner(sender, ALLOWED_NAMES).await {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(zbus::fdo::Error::AccessDenied("Access denied".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus::interface(name = "com.system76.CosmicComp.Ei")]
|
||||||
|
impl Ei {
|
||||||
|
/// Create a new EI sender context
|
||||||
|
async fn get_sender_socket(
|
||||||
|
&self,
|
||||||
|
device_types: u32,
|
||||||
|
#[zbus(header)] header: zbus::message::Header<'_>,
|
||||||
|
) -> zbus::fdo::Result<zbus::zvariant::OwnedFd> {
|
||||||
|
if let Some(sender) = header.sender() {
|
||||||
|
self.check_sender_allowed(sender).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (comp_stream, client_stream) = UnixStream::pair().map_err(|err| {
|
||||||
|
zbus::fdo::Error::Failed(format!("Failed to create socket pair: {err}"))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let guard = self.ei_sender.lock().unwrap();
|
||||||
|
let sender = guard
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| zbus::fdo::Error::Failed("EI sender not available".to_string()))?;
|
||||||
|
sender.send((comp_stream, device_types)).map_err(|err| {
|
||||||
|
zbus::fdo::Error::Failed(format!("Failed to hand off EI socket: {err}"))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(std::os::fd::OwnedFd::from(client_stream).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the `com.system76.CosmicComp.Ei` interface on the shared session connection.
|
||||||
|
pub async fn init(
|
||||||
|
conn: &zbus::Connection,
|
||||||
|
name_owners: &NameOwners,
|
||||||
|
ei_sender: EiSender,
|
||||||
|
) -> zbus::Result<()> {
|
||||||
|
let ei = Ei {
|
||||||
|
ei_sender,
|
||||||
|
name_owners: name_owners.clone(),
|
||||||
|
};
|
||||||
|
conn.object_server()
|
||||||
|
.at("/com/system76/CosmicComp/Ei", ei)
|
||||||
|
.await?;
|
||||||
|
conn.request_name("com.system76.CosmicComp").await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -9,11 +9,13 @@ use std::{
|
||||||
cell::{RefCell, RefMut},
|
cell::{RefCell, RefMut},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
pub mod a11y_keyboard_monitor;
|
pub mod a11y_keyboard_monitor;
|
||||||
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
|
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
|
||||||
|
pub mod ei;
|
||||||
#[cfg(feature = "logind")]
|
#[cfg(feature = "logind")]
|
||||||
pub mod logind;
|
pub mod logind;
|
||||||
mod name_owners;
|
mod name_owners;
|
||||||
|
|
@ -29,6 +31,7 @@ struct DBusStateInner {
|
||||||
session_conn: zbus::Result<zbus::Connection>,
|
session_conn: zbus::Result<zbus::Connection>,
|
||||||
system_conn: zbus::Result<zbus::Connection>,
|
system_conn: zbus::Result<zbus::Connection>,
|
||||||
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
|
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
|
||||||
|
ei_sender: Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DBusState {
|
impl DBusState {
|
||||||
|
|
@ -42,6 +45,7 @@ impl DBusState {
|
||||||
session_conn,
|
session_conn,
|
||||||
system_conn,
|
system_conn,
|
||||||
a11y_keyboard_monitor: RefCell::new(None),
|
a11y_keyboard_monitor: RefCell::new(None),
|
||||||
|
ei_sender: Arc::new(Mutex::new(None)),
|
||||||
}));
|
}));
|
||||||
evlh.insert_source(source, |_, _, _| {}).unwrap();
|
evlh.insert_source(source, |_, _, _| {}).unwrap();
|
||||||
let state_clone = state.clone();
|
let state_clone = state.clone();
|
||||||
|
|
@ -65,6 +69,10 @@ impl DBusState {
|
||||||
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
|
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_ei_sender(&self, sender: calloop::channel::Sender<crate::libei::EiRequest>) {
|
||||||
|
*self.0.ei_sender.lock().unwrap() = Some(sender);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Lazy async init when we don't have anything blocking main thread
|
// TODO Lazy async init when we don't have anything blocking main thread
|
||||||
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
|
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
|
||||||
self.0.session_conn.as_ref().map_err(|err| err.clone())
|
self.0.session_conn.as_ref().map_err(|err| err.clone())
|
||||||
|
|
@ -85,6 +93,7 @@ async fn init_session(state: &DBusState) -> zbus::Result<()> {
|
||||||
let a11y_keyboard_monitor_state =
|
let a11y_keyboard_monitor_state =
|
||||||
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
|
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
|
||||||
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
|
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
|
||||||
|
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Action, PrivateAction},
|
config::{Action, PrivateAction},
|
||||||
|
input::InputBackendId,
|
||||||
shell::{
|
shell::{
|
||||||
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
|
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
|
||||||
focus::{FocusTarget, target::KeyboardFocusTarget},
|
focus::{FocusTarget, target::KeyboardFocusTarget},
|
||||||
|
|
@ -39,6 +40,7 @@ impl State {
|
||||||
pub fn handle_action(
|
pub fn handle_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: Action,
|
action: Action,
|
||||||
|
backend_id: &InputBackendId,
|
||||||
seat: &Seat<State>,
|
seat: &Seat<State>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: u32,
|
||||||
|
|
@ -69,7 +71,7 @@ impl State {
|
||||||
Action::Shortcut(action) => {
|
Action::Shortcut(action) => {
|
||||||
let propagate = propagate_by_default(&action);
|
let propagate = propagate_by_default(&action);
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
action, seat, serial, time, pattern, direction, propagate,
|
action, backend_id, seat, serial, time, pattern, direction, propagate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Action::Private(PrivateAction::Escape) => {
|
Action::Private(PrivateAction::Escape) => {
|
||||||
|
|
@ -143,6 +145,7 @@ impl State {
|
||||||
pub fn handle_shortcut_action(
|
pub fn handle_shortcut_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: shortcuts::Action,
|
action: shortcuts::Action,
|
||||||
|
backend_id: &InputBackendId,
|
||||||
seat: &Seat<State>,
|
seat: &Seat<State>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: u32,
|
||||||
|
|
@ -232,6 +235,7 @@ impl State {
|
||||||
{
|
{
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
Action::SwitchOutput(inferred),
|
Action::SwitchOutput(inferred),
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -271,6 +275,7 @@ impl State {
|
||||||
{
|
{
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
Action::SwitchOutput(inferred),
|
Action::SwitchOutput(inferred),
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -392,6 +397,7 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
Action::SendToOutput(inferred)
|
Action::SendToOutput(inferred)
|
||||||
},
|
},
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -417,6 +423,7 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
Action::SendToWorkspace(1)
|
Action::SendToWorkspace(1)
|
||||||
},
|
},
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -483,6 +490,7 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
Action::SendToOutput(inferred)
|
Action::SendToOutput(inferred)
|
||||||
},
|
},
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -508,6 +516,7 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
Action::SendToLastWorkspace
|
Action::SendToLastWorkspace
|
||||||
},
|
},
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -532,7 +541,9 @@ impl State {
|
||||||
if propagate
|
if propagate
|
||||||
&& let Some((serial, prev_output, prev_idx)) =
|
&& let Some((serial, prev_output, prev_idx)) =
|
||||||
shell.previous_workspace_idx.take()
|
shell.previous_workspace_idx.take()
|
||||||
&& seat.last_modifier_change().is_some_and(|s| s == serial)
|
&& seat
|
||||||
|
.last_modifier_change_for(backend_id)
|
||||||
|
.is_some_and(|s| s == serial)
|
||||||
&& prev_output == current_output
|
&& prev_output == current_output
|
||||||
{
|
{
|
||||||
let _ = shell.activate(
|
let _ = shell.activate(
|
||||||
|
|
@ -705,6 +716,7 @@ impl State {
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
Action::SwitchOutput(direction),
|
Action::SwitchOutput(direction),
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -741,7 +753,8 @@ impl State {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(direction) = dir {
|
if let Some(direction) = dir {
|
||||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id)
|
||||||
|
{
|
||||||
let mut shell = self.common.shell.write();
|
let mut shell = self.common.shell.write();
|
||||||
if !shell
|
if !shell
|
||||||
.previous_workspace_idx
|
.previous_workspace_idx
|
||||||
|
|
@ -776,6 +789,7 @@ impl State {
|
||||||
|
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
action,
|
action,
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
@ -800,7 +814,7 @@ impl State {
|
||||||
.move_current_element(direction, seat);
|
.move_current_element(direction, seat);
|
||||||
match res {
|
match res {
|
||||||
MoveResult::MoveFurther(_move_further) => {
|
MoveResult::MoveFurther(_move_further) => {
|
||||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id) {
|
||||||
let mut shell = self.common.shell.write();
|
let mut shell = self.common.shell.write();
|
||||||
if !shell
|
if !shell
|
||||||
.previous_workspace_idx
|
.previous_workspace_idx
|
||||||
|
|
@ -834,6 +848,7 @@ impl State {
|
||||||
|
|
||||||
self.handle_shortcut_action(
|
self.handle_shortcut_action(
|
||||||
action,
|
action,
|
||||||
|
backend_id,
|
||||||
seat,
|
seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
|
||||||
796
src/input/mod.rs
796
src/input/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -177,11 +177,13 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
||||||
with_xwayland,
|
with_xwayland,
|
||||||
kiosk_command,
|
kiosk_command,
|
||||||
);
|
);
|
||||||
|
// Set up the libei sender side before the backend spawns Xwayland.
|
||||||
|
let ei_sender = libei::setup_ei(&event_loop.handle());
|
||||||
|
state.common.dbus_state.set_ei_sender(ei_sender);
|
||||||
|
|
||||||
// init backend
|
// init backend
|
||||||
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
|
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
|
||||||
|
|
||||||
libei::listen_eis(&event_loop.handle());
|
|
||||||
|
|
||||||
if let Err(err) = theme::watch_theme(event_loop.handle()) {
|
if let Err(err) = theme::watch_theme(event_loop.handle()) {
|
||||||
warn!(?err, "Failed to watch theme");
|
warn!(?err, "Failed to watch theme");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
228
src/libei.rs
228
src/libei.rs
|
|
@ -1,46 +1,222 @@
|
||||||
use reis::calloop::EisListenerSource;
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
use reis::eis;
|
use reis::eis;
|
||||||
use smithay::reexports::reis;
|
use smithay::reexports::reis;
|
||||||
|
|
||||||
use smithay::backend::libei::{EiInput, EiInputEvent};
|
use smithay::backend::libei::{EiInput, EiInputEvent, EiRegion};
|
||||||
use smithay::input::keyboard::XkbConfig;
|
use smithay::input::keyboard::Keysym;
|
||||||
use smithay::reexports::calloop;
|
use smithay::reexports::calloop;
|
||||||
|
use smithay::wayland::input_method::InputMethodSeat;
|
||||||
|
use smithay::wayland::text_input::TextInputSeat;
|
||||||
|
|
||||||
|
use crate::config::xkb_config_to_wl;
|
||||||
|
use crate::input::InputBackendId;
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
|
|
||||||
pub fn listen_eis(handle: &calloop::LoopHandle<'static, State>) {
|
// Requested device types for an EI connection, mirroring the XDG RemoteDesktop portal `DeviceType` bitmask
|
||||||
let listener = match eis::Listener::bind_auto() {
|
const DEVICE_TYPE_KEYBOARD: u32 = 1;
|
||||||
Ok(listener) => listener,
|
const DEVICE_TYPE_POINTER: u32 = 2;
|
||||||
Err(err) => {
|
const DEVICE_TYPE_TOUCHSCREEN: u32 = 4;
|
||||||
tracing::error!("Failed to bind EI listener socket: {}", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe { std::env::set_var("LIBEI_SOCKET", listener.path()) };
|
// Name of the EI absolute-pointer device. Shared so the connect path and the
|
||||||
|
// re-advertise-on-output-change path recreate the same device.
|
||||||
|
const ABSOLUTE_POINTER_NAME: &str = "virtual absolute pointer";
|
||||||
|
|
||||||
let listener_source = EisListenerSource::new(listener);
|
pub type EiRequest = (UnixStream, u32);
|
||||||
|
|
||||||
|
/// Build the regions advertised on the EI absolute devices (absolute pointer and touch) for the
|
||||||
|
/// output layout: one region per output, each at its **global logical** offset with
|
||||||
|
/// its logical size and scale.
|
||||||
|
pub fn absolute_regions(state: &State) -> Vec<EiRegion> {
|
||||||
|
let shell = state.common.shell.read();
|
||||||
|
shell
|
||||||
|
.outputs()
|
||||||
|
.map(|output| {
|
||||||
|
let scale = output.current_scale().fractional_scale();
|
||||||
|
EiRegion {
|
||||||
|
// Keep the signed logical rect
|
||||||
|
// the u32 clamp happens at the `ei_device.region` in smithay.
|
||||||
|
rect: output.geometry().as_logical(),
|
||||||
|
scale: scale as f32,
|
||||||
|
// Tie the region to its output so a client can correlate it with the
|
||||||
|
// matching screencast stream (the portal advertises the same name).
|
||||||
|
mapping_id: Some(output.name()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-advertise the coordinate regions on every active EI seat's absolute devices.
|
||||||
|
pub fn refresh_absolute_pointer_regions(state: &State) {
|
||||||
|
if state.common.ei_seats.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let regions = absolute_regions(state);
|
||||||
|
for seat in state.common.ei_seats.values() {
|
||||||
|
seat.update_regions(®ions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_ei(
|
||||||
|
handle: &calloop::LoopHandle<'static, State>,
|
||||||
|
) -> calloop::channel::Sender<EiRequest> {
|
||||||
|
let (sender, channel) = calloop::channel::channel::<EiRequest>();
|
||||||
let handle_clone = handle.clone();
|
let handle_clone = handle.clone();
|
||||||
handle
|
handle
|
||||||
.insert_source(listener_source, move |context, _, _| {
|
.insert_source(channel, move |event, _, _| {
|
||||||
|
let calloop::channel::Event::Msg((stream, device_types)) = event else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let context = match eis::Context::new(stream) {
|
||||||
|
Ok(context) => context,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Failed to create EI context: {}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
let source = EiInput::new(context);
|
let source = EiInput::new(context);
|
||||||
handle_clone
|
if let Err(err) =
|
||||||
.insert_source(source, |event, connection, data| match event {
|
handle_clone.insert_source(source, move |event, connection, data| match event {
|
||||||
EiInputEvent::Connected => {
|
EiInputEvent::Connected => {
|
||||||
|
let conn = connection.eis_connection().clone();
|
||||||
let seat = connection.add_seat("default");
|
let seat = connection.add_seat("default");
|
||||||
// TODO config
|
let wants_keyboard = device_types & DEVICE_TYPE_KEYBOARD != 0;
|
||||||
let _ = seat.add_keyboard("virtual keyboard", XkbConfig::default());
|
if wants_keyboard {
|
||||||
seat.add_pointer("virtual pointer");
|
let conf = data.common.config.xkb_config();
|
||||||
seat.add_pointer_absolute("virtual absoulte pointer");
|
// The ei_keyboard device is given the compositor keymap; its key
|
||||||
seat.add_touch("virtual touch");
|
// events feed the shared seat like any other keyboard.
|
||||||
|
let _ = seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&conf));
|
||||||
|
// The text device lets clients inject keysyms/utf8 directly, delivered
|
||||||
|
seat.add_text("virtual text");
|
||||||
|
}
|
||||||
|
if device_types & DEVICE_TYPE_POINTER != 0 {
|
||||||
|
seat.add_pointer("virtual pointer");
|
||||||
|
let regions = absolute_regions(data);
|
||||||
|
seat.add_pointer_absolute(ABSOLUTE_POINTER_NAME, ®ions);
|
||||||
|
}
|
||||||
|
if device_types & DEVICE_TYPE_TOUCHSCREEN != 0 {
|
||||||
|
let regions = absolute_regions(data);
|
||||||
|
seat.add_touch("virtual touch", ®ions);
|
||||||
|
}
|
||||||
|
// Kb-capable connections get a shared-seat source so their `ei_keyboard`
|
||||||
|
// key events feed the seat keyboard tracking.
|
||||||
|
if wants_keyboard {
|
||||||
|
data.common.ei_keyboard_source.insert(
|
||||||
|
conn.clone(),
|
||||||
|
smithay::input::keyboard::KeyboardSource::new_auxiliary(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Track the seat for every connection
|
||||||
|
data.common.ei_seats.insert(conn, seat);
|
||||||
|
data.update_ei_input_method();
|
||||||
|
}
|
||||||
|
EiInputEvent::Disconnected => {
|
||||||
|
let conn = connection.eis_connection().clone();
|
||||||
|
let backend_id = InputBackendId::Ei(conn.clone());
|
||||||
|
// Release any keys/modifiers and pointer buttons this remote still holds
|
||||||
|
data.release_ei_keyboard(&conn);
|
||||||
|
data.release_ei_pointer(&conn);
|
||||||
|
data.clear_input_source_state(&backend_id);
|
||||||
|
data.common.ei_seats.remove(&conn);
|
||||||
|
data.common.ei_keyboard_source.remove(&conn);
|
||||||
|
data.common.ei_pointer_buttons.remove(&conn);
|
||||||
|
data.update_ei_input_method();
|
||||||
|
// Notify the remaining libei clients of the now-cleared modifier state
|
||||||
|
let seat = data.common.shell.read().seats.last_active().clone();
|
||||||
|
data.broadcast_ei_keyboard_modifiers(&seat);
|
||||||
}
|
}
|
||||||
EiInputEvent::Disconnected => {}
|
|
||||||
EiInputEvent::Event(event) => {
|
EiInputEvent::Event(event) => {
|
||||||
data.process_input_event(event);
|
use smithay::backend::input::{InputEvent, KeyboardKeyEvent};
|
||||||
|
match event {
|
||||||
|
InputEvent::Keyboard { event } => {
|
||||||
|
data.inject_ei_key(
|
||||||
|
connection.eis_connection(),
|
||||||
|
event.key_code(),
|
||||||
|
event.state(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
let backend_id =
|
||||||
|
InputBackendId::Ei(connection.eis_connection().clone());
|
||||||
|
data.process_input_event(other, backend_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EiInputEvent::TextKeysym { keysym, state } => {
|
||||||
|
data.inject_ei_text_keysym(connection.eis_connection(), keysym, state);
|
||||||
|
}
|
||||||
|
EiInputEvent::TextUtf8 { text } => {
|
||||||
|
data.inject_ei_text(&text);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
{
|
||||||
Ok(calloop::PostAction::Continue)
|
tracing::error!("Failed to insert EI input source: {}", err);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.expect("Failed to insert EI channel source into the event loop");
|
||||||
|
|
||||||
|
sender
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Act as the input method for text injection while any text-capable EI connection is
|
||||||
|
/// active, so `ei_text` UTF-8 can be committed into the focused app even without a real
|
||||||
|
/// IME, but only when none is bound (a real IME always wins)
|
||||||
|
pub(crate) fn update_ei_input_method(&mut self) {
|
||||||
|
let active = !self.common.ei_keyboard_source.is_empty();
|
||||||
|
let seats = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.read()
|
||||||
|
.seats
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for seat in seats {
|
||||||
|
let has_ime = seat.input_method().has_instance();
|
||||||
|
let text_input = seat.text_input();
|
||||||
|
if active {
|
||||||
|
if !has_ime {
|
||||||
|
text_input.set_compositor_input_method(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_input.set_compositor_input_method(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inject UTF-8 text (from an EI `ei_text` device) into the focused client.
|
||||||
|
pub fn inject_ei_text(&mut self, text: &str) {
|
||||||
|
let seat = self.common.shell.read().seats.last_active().clone();
|
||||||
|
// Only commit through text-input when we're the active input method (no real IME)
|
||||||
|
if !seat.input_method().has_instance() {
|
||||||
|
let text_input = seat.text_input();
|
||||||
|
let mut injected = false;
|
||||||
|
text_input.with_active_text_input(|ti, _surface| {
|
||||||
|
ti.commit_string(Some(text.to_owned()));
|
||||||
|
injected = true;
|
||||||
|
});
|
||||||
|
if injected {
|
||||||
|
text_input.done(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind the whole chunk to spare keycodes in one temporary keymap per batch (delivered
|
||||||
|
// to just the focused client), so we change the keymap ~once per chunk instead of once
|
||||||
|
// per character. Leaves the seat's own keyboard state untouched.
|
||||||
|
let keysyms: Vec<Keysym> = text
|
||||||
|
.chars()
|
||||||
|
.map(Keysym::from_char)
|
||||||
|
.filter(|keysym| keysym.raw() != 0)
|
||||||
|
.collect();
|
||||||
|
let Some(keyboard) = seat.get_keyboard() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// At most ~247 keysyms fit one spare keymap (keycodes 9..=255); leave margin.
|
||||||
|
const BATCH: usize = 240;
|
||||||
|
for batch in keysyms.chunks(BATCH) {
|
||||||
|
keyboard.inject_text_keysyms(self, batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use smithay::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::key_bindings::cosmic_modifiers_from_smithay,
|
config::key_bindings::cosmic_modifiers_from_smithay,
|
||||||
|
input::InputBackendId,
|
||||||
shell::{Trigger, layout::tiling::NodeDesc},
|
shell::{Trigger, layout::tiling::NodeDesc},
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
|
|
@ -73,6 +74,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
|
||||||
|
|
||||||
data.handle_shortcut_action(
|
data.handle_shortcut_action(
|
||||||
shortcuts::Action::Focus(direction),
|
shortcuts::Action::Focus(direction),
|
||||||
|
&InputBackendId::Normal,
|
||||||
&self.seat,
|
&self.seat,
|
||||||
serial,
|
serial,
|
||||||
time,
|
time,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex};
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::cursor::CursorState,
|
backend::render::cursor::CursorState,
|
||||||
config::{Config, xkb_config_to_wl},
|
config::{Config, xkb_config_to_wl},
|
||||||
input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
input::{InputBackendId, ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
|
@ -82,12 +82,25 @@ impl Seats {
|
||||||
self.last_active = Some(seat.clone());
|
self.last_active = Some(seat.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_device<D: Device>(&self, device: &D) -> Option<&Seat<State>> {
|
pub fn for_device<D: Device>(
|
||||||
self.iter().find(|seat| {
|
&self,
|
||||||
let userdata = seat.user_data();
|
device: &D,
|
||||||
let devices = userdata.get::<Devices>().unwrap();
|
backend_id: &InputBackendId,
|
||||||
devices.has_device(device)
|
) -> Option<&Seat<State>> {
|
||||||
})
|
self.iter()
|
||||||
|
.find(|seat| {
|
||||||
|
let userdata = seat.user_data();
|
||||||
|
let devices = userdata.get::<Devices>().unwrap();
|
||||||
|
devices.has_device(device, backend_id)
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
// EI devices can be transiently unregistered while the compositor recreates
|
||||||
|
// the absolute-pointer device (e.g. on a scale/geometry change), which would
|
||||||
|
// otherwise drop all pointer/touch input until the client re-binds it. EI is
|
||||||
|
// single-seat, so fall back to the active seat here, matching the EI keyboard
|
||||||
|
// path, which always targets the active seat.
|
||||||
|
matches!(backend_id, InputBackendId::Ei(_)).then(|| self.last_active())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,6 +109,7 @@ impl Devices {
|
||||||
&self,
|
&self,
|
||||||
device: &D,
|
device: &D,
|
||||||
led_state: LedState,
|
led_state: LedState,
|
||||||
|
backend_id: &InputBackendId,
|
||||||
) -> Vec<DeviceCapability> {
|
) -> Vec<DeviceCapability> {
|
||||||
let id = device.id();
|
let id = device.id();
|
||||||
let mut map = self.capabilities.borrow_mut();
|
let mut map = self.capabilities.borrow_mut();
|
||||||
|
|
@ -113,7 +127,7 @@ impl Devices {
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
map.insert(id, caps);
|
map.insert((backend_id.clone(), id), caps);
|
||||||
|
|
||||||
if device.has_capability(DeviceCapability::Keyboard)
|
if device.has_capability(DeviceCapability::Keyboard)
|
||||||
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
|
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
|
||||||
|
|
@ -126,11 +140,18 @@ impl Devices {
|
||||||
new_caps
|
new_caps
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_device<D: Device>(&self, device: &D) -> bool {
|
/// Whether the given backend's device with this id is registered on the seat.
|
||||||
self.capabilities.borrow().contains_key(&device.id())
|
pub fn has_device<D: Device>(&self, device: &D, backend_id: &InputBackendId) -> bool {
|
||||||
|
self.capabilities
|
||||||
|
.borrow()
|
||||||
|
.contains_key(&(backend_id.clone(), device.id()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_device<D: Device>(&self, device: &D) -> Vec<DeviceCapability> {
|
pub fn remove_device<D: Device>(
|
||||||
|
&self,
|
||||||
|
device: &D,
|
||||||
|
backend_id: &InputBackendId,
|
||||||
|
) -> Vec<DeviceCapability> {
|
||||||
let id = device.id();
|
let id = device.id();
|
||||||
|
|
||||||
let mut keyboards = self.keyboards.borrow_mut();
|
let mut keyboards = self.keyboards.borrow_mut();
|
||||||
|
|
@ -139,7 +160,7 @@ impl Devices {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map = self.capabilities.borrow_mut();
|
let mut map = self.capabilities.borrow_mut();
|
||||||
map.remove(&id)
|
map.remove(&(backend_id.clone(), id))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||||
|
|
@ -155,7 +176,8 @@ impl Devices {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Devices {
|
pub struct Devices {
|
||||||
capabilities: RefCell<HashMap<String, Vec<DeviceCapability>>>,
|
// Keyed by `(backend, device_id)`
|
||||||
|
capabilities: RefCell<HashMap<(InputBackendId, String), Vec<DeviceCapability>>>,
|
||||||
// Used for updating keyboard leds on kms backend
|
// Used for updating keyboard leds on kms backend
|
||||||
keyboards: RefCell<Vec<InputDevice>>,
|
keyboards: RefCell<Vec<InputDevice>>,
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +207,7 @@ struct FocusedOutput(pub Mutex<Option<Output>>);
|
||||||
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
|
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
|
pub struct LastModifierChange(pub Mutex<(HashMap<InputBackendId, Serial>, Option<Serial>)>);
|
||||||
|
|
||||||
pub fn create_seat(
|
pub fn create_seat(
|
||||||
dh: &DisplayHandle,
|
dh: &DisplayHandle,
|
||||||
|
|
@ -265,6 +287,9 @@ pub trait SeatExt {
|
||||||
fn supressed_buttons(&self) -> &SupressedButtons;
|
fn supressed_buttons(&self) -> &SupressedButtons;
|
||||||
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
|
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
|
||||||
fn last_modifier_change(&self) -> Option<Serial>;
|
fn last_modifier_change(&self) -> Option<Serial>;
|
||||||
|
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial>;
|
||||||
|
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial);
|
||||||
|
fn clear_last_modifier_change(&self, backend_id: &InputBackendId);
|
||||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
|
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
|
||||||
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
|
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
|
||||||
|
|
||||||
|
|
@ -343,13 +368,48 @@ impl SeatExt for Seat<State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last_modifier_change(&self) -> Option<Serial> {
|
fn last_modifier_change(&self) -> Option<Serial> {
|
||||||
*self
|
self.user_data()
|
||||||
.user_data()
|
|
||||||
.get::<LastModifierChange>()
|
.get::<LastModifierChange>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0
|
.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial> {
|
||||||
|
self.user_data()
|
||||||
|
.get::<LastModifierChange>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.get(backend_id)
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial) {
|
||||||
|
let mut guard = self
|
||||||
|
.user_data()
|
||||||
|
.get::<LastModifierChange>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
guard.0.insert(backend_id.clone(), serial);
|
||||||
|
guard.1 = Some(serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_last_modifier_change(&self, backend_id: &InputBackendId) {
|
||||||
|
self.user_data()
|
||||||
|
.get::<LastModifierChange>()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.remove(backend_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {
|
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {
|
||||||
|
|
|
||||||
25
src/state.rs
25
src/state.rs
|
|
@ -248,6 +248,28 @@ pub struct Common {
|
||||||
|
|
||||||
pub gesture_state: Option<GestureState>,
|
pub gesture_state: Option<GestureState>,
|
||||||
|
|
||||||
|
/// Active libei sender seats, keyed by their `eis` connection. Tracked so their virtual
|
||||||
|
/// keyboards can be re-created when the keyboard configuration changes at runtime.
|
||||||
|
pub ei_seats: std::collections::HashMap<
|
||||||
|
smithay::reexports::reis::eis::Connection,
|
||||||
|
smithay::backend::libei::EiInputSeat,
|
||||||
|
>,
|
||||||
|
|
||||||
|
/// The shared-seat [`KeyboardSource`] assigned to each libei connection, so its
|
||||||
|
/// `ei_keyboard` key events feed the seat keyboard with independent per-source hold
|
||||||
|
/// tracking (and can be released together on disconnect). Keyed by connection.
|
||||||
|
pub ei_keyboard_source: std::collections::HashMap<
|
||||||
|
smithay::reexports::reis::eis::Connection,
|
||||||
|
smithay::input::keyboard::KeyboardSource,
|
||||||
|
>,
|
||||||
|
|
||||||
|
/// Pointer buttons currently held by each libei connection, so they can be released when the
|
||||||
|
/// connection drops
|
||||||
|
pub ei_pointer_buttons: std::collections::HashMap<
|
||||||
|
smithay::reexports::reis::eis::Connection,
|
||||||
|
std::collections::HashSet<u32>,
|
||||||
|
>,
|
||||||
|
|
||||||
pub kiosk_child: Option<Child>,
|
pub kiosk_child: Option<Child>,
|
||||||
pub theme: cosmic::Theme,
|
pub theme: cosmic::Theme,
|
||||||
|
|
||||||
|
|
@ -757,6 +779,9 @@ impl State {
|
||||||
should_stop: false,
|
should_stop: false,
|
||||||
kiosk_exit_code: None,
|
kiosk_exit_code: None,
|
||||||
gesture_state: None,
|
gesture_state: None,
|
||||||
|
ei_seats: std::collections::HashMap::new(),
|
||||||
|
ei_keyboard_source: std::collections::HashMap::new(),
|
||||||
|
ei_pointer_buttons: std::collections::HashMap::new(),
|
||||||
|
|
||||||
kiosk_child: None,
|
kiosk_child: None,
|
||||||
theme: cosmic::theme::system_preference(),
|
theme: cosmic::theme::system_preference(),
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,16 @@ impl State {
|
||||||
state.common.output_configuration_state.update();
|
state.common.output_configuration_state.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Output scale or geometry may have changed. EI absolute-pointer regions
|
||||||
|
// are immutable per device, so any connected EI client (e.g. an RDP server)
|
||||||
|
// keeps mapping with the old scale until it reconnects. Recreate the
|
||||||
|
// device with the updated region so the mapping tracks the change live.
|
||||||
|
// (drop the backend lock first: refresh borrows `self` immutably.)
|
||||||
|
drop(backend);
|
||||||
|
if !test_only {
|
||||||
|
crate::libei::refresh_absolute_pointer_regions(self);
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ impl State {
|
||||||
&self.common.display_handle,
|
&self.common.display_handle,
|
||||||
None,
|
None,
|
||||||
std::iter::empty::<(OsString, OsString)>(),
|
std::iter::empty::<(OsString, OsString)>(),
|
||||||
std::iter::empty::<OsString>(),
|
["-enable-ei-portal"],
|
||||||
true,
|
true,
|
||||||
Stdio::null(),
|
Stdio::null(),
|
||||||
Stdio::null(),
|
Stdio::null(),
|
||||||
|
|
@ -394,6 +394,7 @@ impl Common {
|
||||||
sym: Keysym,
|
sym: Keysym,
|
||||||
code: Keycode,
|
code: Keycode,
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
|
modifiers: ModifiersState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: u32,
|
||||||
) {
|
) {
|
||||||
|
|
@ -418,7 +419,6 @@ impl Common {
|
||||||
.last_active()
|
.last_active()
|
||||||
.get_keyboard()
|
.get_keyboard()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let modifiers = keyboard.modifier_state();
|
|
||||||
let is_modifier = sym.is_modifier_key();
|
let is_modifier = sym.is_modifier_key();
|
||||||
|
|
||||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue