-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This works with the `receive` example of `reis` using `LIBEI_SOCKET=/tmp/atspi-ei-kb.socket`. I've also worked on changes to at-spi2-core, so I'll now be able to try testing it... If a version of this is ultimately used, it will need a secure way to pass the socket to accessibility tools. Putting it in `/tmp` is a placeholder. WIP initial modifiers start_emulating fix keycode offset WIP use cosmic-atspi-unstable-v1 protocol Now client has a way to communicate grabs, but they still need to be handled here. WIP grabs WIP keycode offset atspi grabs
- Loading branch information
Showing
8 changed files
with
441 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
|
||
use once_cell::sync::Lazy; | ||
use reis::{ | ||
calloop::{ConnectedContextState, EisRequestSource, EisRequestSourceEvent}, | ||
eis::{self, device::DeviceType}, | ||
request::{DeviceCapability, EisRequest}, | ||
}; | ||
use smithay::{backend::input::KeyState, utils::SerialCounter}; | ||
use std::{ | ||
collections::{HashMap, HashSet}, | ||
os::unix::{io::AsFd, net::UnixStream}, | ||
}; | ||
use xkbcommon::xkb; | ||
|
||
use crate::{ | ||
state::State, | ||
wayland::protocols::atspi::{delegate_atspi, AtspiHandler}, | ||
}; | ||
|
||
pub static EI_SERIAL_COUNTER: SerialCounter = SerialCounter::new(); | ||
|
||
#[derive(PartialEq, Debug)] | ||
pub struct AtspiKeyGrab { | ||
pub mods: u32, | ||
pub virtual_mods: HashSet<xkb::Keycode>, | ||
pub key: xkb::Keycode, | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
pub struct AtspiEiState { | ||
modifiers: smithay::input::keyboard::ModifiersState, | ||
// TODO: purge old instances | ||
keyboards: Vec<(eis::Context, eis::Device, eis::Keyboard)>, | ||
pub key_grabs: Vec<AtspiKeyGrab>, | ||
pub virtual_mods: HashSet<xkb::Keycode>, | ||
pub active_virtual_mods: HashSet<xkb::Keycode>, | ||
} | ||
|
||
impl AtspiEiState { | ||
pub fn input( | ||
&mut self, | ||
modifiers: &smithay::input::keyboard::ModifiersState, | ||
keysym: &smithay::input::keyboard::KeysymHandle, | ||
state: KeyState, | ||
time: u64, | ||
) { | ||
let state = match state { | ||
KeyState::Pressed => eis::keyboard::KeyState::Press, | ||
KeyState::Released => eis::keyboard::KeyState::Released, | ||
}; | ||
if &self.modifiers != modifiers { | ||
self.modifiers = *modifiers; | ||
for (_, _, keyboard) in &self.keyboards { | ||
keyboard.modifiers( | ||
EI_SERIAL_COUNTER.next_serial().into(), | ||
modifiers.serialized.depressed, | ||
modifiers.serialized.locked, | ||
modifiers.serialized.latched, | ||
modifiers.serialized.layout_effective, | ||
); | ||
} | ||
} | ||
for (context, device, keyboard) in &self.keyboards { | ||
keyboard.key(keysym.raw_code().raw() - 8, state); | ||
device.frame(EI_SERIAL_COUNTER.next_serial().into(), time); | ||
context.flush(); | ||
} | ||
} | ||
|
||
fn update_virtual_mods(&mut self) { | ||
self.virtual_mods.clear(); | ||
self.virtual_mods | ||
.extend(self.key_grabs.iter().flat_map(|grab| &grab.virtual_mods)); | ||
} | ||
} | ||
|
||
static SERVER_INTERFACES: Lazy<HashMap<&'static str, u32>> = Lazy::new(|| { | ||
let mut m = HashMap::new(); | ||
m.insert("ei_callback", 1); | ||
m.insert("ei_connection", 1); | ||
m.insert("ei_seat", 1); | ||
m.insert("ei_device", 1); | ||
m.insert("ei_pingpong", 1); | ||
m.insert("ei_keyboard", 1); | ||
m | ||
}); | ||
|
||
impl AtspiHandler for State { | ||
fn add_key_event_socket(&mut self, socket: UnixStream) { | ||
let context = eis::Context::new(socket).unwrap(); // XXX | ||
let source = EisRequestSource::new(context, &SERVER_INTERFACES, 0); | ||
self.common | ||
.event_loop_handle | ||
.insert_source(source, |event, connected_state, state| { | ||
Ok(handle_event(event, connected_state, state)) | ||
}) | ||
.unwrap(); // XXX | ||
} | ||
|
||
fn add_key_grab(&mut self, mods: u32, virtual_mods: Vec<u32>, key: u32) { | ||
tracing::error!("add_key_grab: {:?}", (mods, &virtual_mods, key)); | ||
let grab = AtspiKeyGrab { | ||
mods, | ||
virtual_mods: virtual_mods.into_iter().map(|x| (x + 8).into()).collect(), | ||
key: (key + 8).into(), | ||
}; | ||
self.common.atspi_ei.key_grabs.push(grab); | ||
self.common.atspi_ei.update_virtual_mods(); | ||
} | ||
|
||
fn remove_key_grab(&mut self, mods: u32, virtual_mods: Vec<u32>, key: u32) { | ||
tracing::error!("remove_key_grab: {:?}", (mods, &virtual_mods, key)); | ||
let grab = AtspiKeyGrab { | ||
mods, | ||
virtual_mods: virtual_mods.into_iter().map(|x| (x + 8).into()).collect(), | ||
key: (key + 8).into(), | ||
}; | ||
if let Some(idx) = self | ||
.common | ||
.atspi_ei | ||
.key_grabs | ||
.iter() | ||
.position(|x| *x == grab) | ||
{ | ||
self.common.atspi_ei.key_grabs.remove(idx); | ||
} | ||
self.common.atspi_ei.update_virtual_mods(); | ||
} | ||
|
||
fn destroy(&mut self) { | ||
let state = &mut self.common.atspi_ei; | ||
state.virtual_mods.clear(); | ||
state.key_grabs.clear(); | ||
state.keyboards.clear(); | ||
} | ||
} | ||
|
||
fn handle_event( | ||
event: Result<EisRequestSourceEvent, reis::request::Error>, | ||
connected_state: &mut ConnectedContextState, | ||
state: &mut State, | ||
) -> calloop::PostAction { | ||
match event { | ||
Ok(EisRequestSourceEvent::Connected) => { | ||
if connected_state.context_type != reis::ei::handshake::ContextType::Receiver { | ||
return calloop::PostAction::Remove; | ||
} | ||
// TODO multiple seats | ||
let _seat = connected_state | ||
.request_converter | ||
.add_seat(Some("default"), &[DeviceCapability::Keyboard]); | ||
} | ||
Ok(EisRequestSourceEvent::Request(EisRequest::Disconnect)) => { | ||
return calloop::PostAction::Remove; | ||
} | ||
Ok(EisRequestSourceEvent::Request(EisRequest::Bind(request))) => { | ||
if connected_state.has_interface("ei_keyboard") | ||
&& request.capabilities & 2 << DeviceCapability::Keyboard as u64 != 0 | ||
{ | ||
// TODO Handle keymap changes | ||
|
||
let xkb_config = state.common.config.xkb_config(); | ||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); | ||
let keymap = xkb::Keymap::new_from_names( | ||
&context, | ||
&xkb_config.rules, | ||
&xkb_config.model, | ||
&xkb_config.layout, | ||
&xkb_config.variant, | ||
xkb_config.options.clone(), | ||
xkb::KEYMAP_COMPILE_NO_FLAGS, | ||
) | ||
.unwrap(); | ||
let keymap_text = keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); | ||
// XXX make smithay SealedFile public? | ||
// Share sealed file? | ||
let fd = rustix::fs::memfd_create("eis-keymap", rustix::fs::MemfdFlags::CLOEXEC) | ||
.unwrap(); | ||
let mut file = std::fs::File::from(fd); | ||
use std::io::Write; | ||
file.write_all(keymap_text.as_bytes()).unwrap(); | ||
|
||
let device = connected_state.request_converter.add_device( | ||
&request.seat, | ||
Some("keyboard"), | ||
DeviceType::Virtual, | ||
&[DeviceCapability::Keyboard], | ||
|device| { | ||
let keyboard = device.interface::<eis::Keyboard>().unwrap(); | ||
keyboard.keymap( | ||
eis::keyboard::KeymapType::Xkb, | ||
keymap_text.len() as _, | ||
file.as_fd(), | ||
); | ||
}, | ||
); | ||
device | ||
.device() | ||
.resumed(EI_SERIAL_COUNTER.next_serial().into()); | ||
|
||
let keyboard = device.interface::<eis::Keyboard>().unwrap(); | ||
|
||
keyboard.modifiers( | ||
EI_SERIAL_COUNTER.next_serial().into(), | ||
state.common.atspi_ei.modifiers.serialized.depressed, | ||
state.common.atspi_ei.modifiers.serialized.locked, | ||
state.common.atspi_ei.modifiers.serialized.latched, | ||
state.common.atspi_ei.modifiers.serialized.layout_effective, | ||
); | ||
|
||
device | ||
.device() | ||
.start_emulating(EI_SERIAL_COUNTER.next_serial().into(), 0); | ||
|
||
state.common.atspi_ei.keyboards.push(( | ||
connected_state.context.clone(), | ||
device.device().clone(), | ||
keyboard, | ||
)); | ||
} | ||
} | ||
Ok(EisRequestSourceEvent::Request(request)) => { | ||
// seat / keyboard / device release? | ||
} | ||
Ok(EisRequestSourceEvent::InvalidObject(_)) => {} | ||
Err(_) => { | ||
// TODO | ||
} | ||
} | ||
connected_state.context.flush(); | ||
calloop::PostAction::Continue | ||
} | ||
|
||
delegate_atspi!(State); |
Oops, something went wrong.