Skip to content

Commit

Permalink
WIP ei socket for at-spi2-core
Browse files Browse the repository at this point in the history
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
ids1024 committed Sep 18, 2024
1 parent 5aee98f commit d01b986
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 3 deletions.
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ bytemuck = "1.12"
calloop = {version = "0.14.1", features = ["executor"]}
cosmic-comp-config = {path = "cosmic-comp-config"}
cosmic-config = {git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"]}
cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"]}
cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", branch = "atspi-ei", default-features = false, features = ["server"]}
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
edid-rs = {version = "0.1"}
egui = {version = "0.23.0", optional = true}
Expand Down Expand Up @@ -59,6 +59,7 @@ zbus = "4.4.0"
profiling = { version = "1.0" }
rustix = { version = "0.38.32", features = ["process"] }
smallvec = "1.13.2"
reis = { git = "https://github.com/ids1024/reis", features = ["calloop"] }

[dependencies.id_tree]
branch = "feature/copy_clone"
Expand Down
44 changes: 44 additions & 0 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,13 @@ impl State {
.unwrap_or(false)
});

self.common.atspi_ei.input(
modifiers,
&handle,
event.state(),
event.time() as u64 * 1000,
);

// Leave move overview mode, if any modifier was released
if let Some(Trigger::KeyboardMove(action_modifiers)) =
shell.overview_mode().0.active_trigger()
Expand Down Expand Up @@ -1611,6 +1618,32 @@ impl State {
)));
}

if event.state() == KeyState::Released {
self.common
.atspi_ei
.active_virtual_mods
.remove(&event.key_code());
} else if event.state() == KeyState::Pressed
&& self
.common
.atspi_ei
.virtual_mods
.contains(&event.key_code())
{
self.common
.atspi_ei
.active_virtual_mods
.insert(event.key_code());

tracing::error!(
"active virtual mods: {:?}",
self.common.atspi_ei.active_virtual_mods
);
seat.supressed_keys().add(&handle, None);

return FilterResult::Intercept(None);
}

// Skip released events for initially surpressed keys
if event.state() == KeyState::Released {
if let Some(tokens) = seat.supressed_keys().filter(&handle) {
Expand All @@ -1635,6 +1668,17 @@ impl State {
return FilterResult::Intercept(None);
}

// TODO modifiers queue
for grab in &self.common.atspi_ei.key_grabs {
if grab.mods == modifiers.serialized.layout_effective
&& grab.virtual_mods == self.common.atspi_ei.active_virtual_mods
&& grab.key == event.key_code()
{
tracing::error!("Grab matched: {:?}", grab);
return FilterResult::Intercept(None);
}
}

// handle the rest of the global shortcuts
let mut clear_queue = true;
if !shortcuts_inhibited {
Expand Down
10 changes: 10 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell},
utils::prelude::OutputExt,
wayland::protocols::{
atspi::AtspiState,
drm::WlDrmState,
image_source::ImageSourceState,
output_configuration::OutputConfigurationState,
Expand Down Expand Up @@ -229,6 +230,9 @@ pub struct Common {
pub xwayland_state: Option<XWaylandState>,
pub xwayland_shell_state: XWaylandShellState,
pub pointer_focus_state: Option<PointerFocusState>,

pub atspi_state: AtspiState,
pub atspi_ei: crate::wayland::handlers::atspi::AtspiEiState,
}

#[derive(Debug)]
Expand Down Expand Up @@ -571,6 +575,9 @@ impl State {
tracing::warn!(?err, "Failed to initialize dbus handlers");
}

// TODO: Restrict to only specific client?
let atspi_state = AtspiState::new::<State, _>(dh, client_is_privileged);

State {
common: Common {
config,
Expand Down Expand Up @@ -627,6 +634,9 @@ impl State {
xwayland_state: None,
xwayland_shell_state,
pointer_focus_state: None,

atspi_state,
atspi_ei: Default::default(),
},
backend: BackendData::Unset,
ready: Once::new(),
Expand Down
235 changes: 235 additions & 0 deletions src/wayland/handlers/atspi.rs
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);
Loading

0 comments on commit d01b986

Please sign in to comment.