From 6c0e3a4fff3b47d278c9a60f8a75d71df35322d5 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 12 Apr 2024 19:58:42 +0200 Subject: [PATCH] wayland: implement virtual-keyboard --- docs/features.md | 1 + release-notes.md | 1 + src/globals.rs | 4 +- src/ifs/wl_seat.rs | 2 + src/ifs/wl_seat/event_handling.rs | 21 +- src/ifs/wl_seat/wl_keyboard.rs | 6 +- .../zwp_virtual_keyboard_manager_v1.rs | 108 ++++++++++ src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs | 131 ++++++++++++ src/it/test_ifs.rs | 2 + src/it/test_ifs/test_keyboard.rs | 15 +- src/it/test_ifs/test_registry.rs | 15 +- src/it/test_ifs/test_seat.rs | 4 + src/it/test_ifs/test_virtual_keyboard.rs | 86 ++++++++ .../test_ifs/test_virtual_keyboard_manager.rs | 49 +++++ src/it/test_transport.rs | 1 + src/it/tests.rs | 2 + src/it/tests/t0040_virtual_keyboard.rs | 188 ++++++++++++++++++ src/xkbcommon.rs | 42 +++- wire/zwp_virtual_keyboard_manager_v1.txt | 4 + wire/zwp_virtual_keyboard_v1.txt | 21 ++ 20 files changed, 689 insertions(+), 14 deletions(-) create mode 100644 src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs create mode 100644 src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs create mode 100644 src/it/test_ifs/test_virtual_keyboard.rs create mode 100644 src/it/test_ifs/test_virtual_keyboard_manager.rs create mode 100644 src/it/tests/t0040_virtual_keyboard.rs create mode 100644 wire/zwp_virtual_keyboard_manager_v1.txt create mode 100644 wire/zwp_virtual_keyboard_v1.txt diff --git a/docs/features.md b/docs/features.md index 65962d08..0c4f9993 100644 --- a/docs/features.md +++ b/docs/features.md @@ -145,6 +145,7 @@ Jay supports the following wayland protocols: | zwp_pointer_constraints_v1 | 1 | | | zwp_primary_selection_device_manager_v1 | 1 | | | zwp_relative_pointer_manager_v1 | 1 | | +| zwp_virtual_keyboard_manager_v1 | 1 | Yes | | zxdg_decoration_manager_v1 | 1 | | | zxdg_output_manager_v1 | 3 | | diff --git a/release-notes.md b/release-notes.md index 7e88133b..21fd6552 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,6 +2,7 @@ - Add support for wp-alpha-modifier. - Add support for per-device keymaps. +- Add support for virtual-keyboard-unstable-v1. # 1.0.3 (2024-04-11) diff --git a/src/globals.rs b/src/globals.rs index 4f53ff80..842aca3b 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -18,7 +18,8 @@ use { wl_registry::WlRegistry, wl_seat::{ zwp_pointer_constraints_v1::ZwpPointerConstraintsV1Global, - zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global, WlSeatGlobal, + zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global, + zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1Global, WlSeatGlobal, }, wl_shm::WlShmGlobal, wl_subcompositor::WlSubcompositorGlobal, @@ -175,6 +176,7 @@ impl Globals { add_singleton!(XdgToplevelDragManagerV1Global); add_singleton!(ZwlrDataControlManagerV1Global); add_singleton!(WpAlphaModifierV1Global); + add_singleton!(ZwpVirtualKeyboardManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 7b6dced8..d4c7448e 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -7,6 +7,8 @@ pub mod wl_touch; pub mod zwp_pointer_constraints_v1; pub mod zwp_relative_pointer_manager_v1; pub mod zwp_relative_pointer_v1; +pub mod zwp_virtual_keyboard_manager_v1; +pub mod zwp_virtual_keyboard_v1; pub use event_handling::NodeSeatState; use { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 440e706f..009c279f 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -356,7 +356,7 @@ impl WlSeatGlobal { self.pointer_owner.button(self, time_usec, button, state); } - fn key_event(&self, time_usec: u64, key: u32, key_state: KeyState) { + pub(super) fn key_event(&self, time_usec: u64, key: u32, key_state: KeyState) { let (state, xkb_dir) = { let mut pk = self.pressed_keys.borrow_mut(); match key_state { @@ -411,6 +411,25 @@ impl WlSeatGlobal { node.node_on_mods(self, mods); } } + + pub(super) fn set_modifiers( + &self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + ) { + let new_mods = + self.kb_state + .borrow_mut() + .set(mods_depressed, mods_latched, mods_locked, group); + if let Some(mods) = new_mods { + self.state.for_each_seat_tester(|t| { + t.send_modifiers(self.id, &mods); + }); + self.keyboard_node.get().node_on_mods(self, mods); + } + } } impl WlSeatGlobal { diff --git a/src/ifs/wl_seat/wl_keyboard.rs b/src/ifs/wl_seat/wl_keyboard.rs index 73f1f99c..091d9b39 100644 --- a/src/ifs/wl_seat/wl_keyboard.rs +++ b/src/ifs/wl_seat/wl_keyboard.rs @@ -15,10 +15,10 @@ pub const REPEAT_INFO_SINCE: Version = Version(4); #[allow(dead_code)] const NO_KEYMAP: u32 = 0; -pub(super) const XKB_V1: u32 = 1; +pub const XKB_V1: u32 = 1; -pub(super) const RELEASED: u32 = 0; -pub(super) const PRESSED: u32 = 1; +pub const RELEASED: u32 = 0; +pub const PRESSED: u32 = 1; pub struct WlKeyboard { id: WlKeyboardId, diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs new file mode 100644 index 00000000..c92e50f9 --- /dev/null +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_manager_v1.rs @@ -0,0 +1,108 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::wl_seat::zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1, + leaks::Tracker, + object::{Object, Version}, + wire::{zwp_virtual_keyboard_manager_v1::*, ZwpVirtualKeyboardManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwpVirtualKeyboardManagerV1Global { + pub name: GlobalName, +} + +pub struct ZwpVirtualKeyboardManagerV1 { + pub id: ZwpVirtualKeyboardManagerV1Id, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl ZwpVirtualKeyboardManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwpVirtualKeyboardManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ZwpVirtualKeyboardManagerV1Error> { + let obj = Rc::new(ZwpVirtualKeyboardManagerV1 { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + ZwpVirtualKeyboardManagerV1Global, + ZwpVirtualKeyboardManagerV1, + ZwpVirtualKeyboardManagerV1Error +); + +impl Global for ZwpVirtualKeyboardManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn secure(&self) -> bool { + true + } +} + +simple_add_global!(ZwpVirtualKeyboardManagerV1Global); + +impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 { + type Error = ZwpVirtualKeyboardManagerV1Error; + + fn create_virtual_keyboard( + &self, + req: CreateVirtualKeyboard, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let seat = self.client.lookup(req.seat)?; + let kb = Rc::new(ZwpVirtualKeyboardV1 { + id: req.id, + client: self.client.clone(), + seat: seat.global.clone(), + tracker: Default::default(), + version: self.version, + keymap_id: Default::default(), + keymap: Default::default(), + }); + track!(self.client, kb); + self.client.add_client_obj(&kb)?; + Ok(()) + } +} + +object_base! { + self = ZwpVirtualKeyboardManagerV1; + version = self.version; +} + +impl Object for ZwpVirtualKeyboardManagerV1 {} + +simple_add_obj!(ZwpVirtualKeyboardManagerV1); + +#[derive(Debug, Error)] +pub enum ZwpVirtualKeyboardManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ZwpVirtualKeyboardManagerV1Error, ClientError); diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs new file mode 100644 index 00000000..ae86cbb5 --- /dev/null +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs @@ -0,0 +1,131 @@ +use { + crate::{ + backend::KeyState, + client::{Client, ClientError}, + clientmem::{ClientMem, ClientMemError}, + ifs::wl_seat::{wl_keyboard, WlSeatGlobal}, + leaks::Tracker, + object::{Object, Version}, + utils::clonecell::CloneCell, + wire::{zwp_virtual_keyboard_v1::*, ZwpVirtualKeyboardV1Id}, + xkbcommon::{KeymapId, XkbCommonError, XkbKeymap}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ZwpVirtualKeyboardV1 { + pub id: ZwpVirtualKeyboardV1Id, + pub client: Rc, + pub seat: Rc, + pub tracker: Tracker, + pub version: Version, + pub keymap_id: Cell>, + pub keymap: CloneCell>>, +} + +impl ZwpVirtualKeyboardV1 { + fn ensure_keymap(&self) { + if let Some(id) = self.keymap_id.get() { + if id == self.seat.effective_kb_map_id.get() { + return; + } + } + let Some(keymap) = self.keymap.get() else { + return; + }; + self.seat.set_effective_keymap(&keymap); + } +} + +impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { + type Error = ZwpVirtualKeyboardV1Error; + + fn keymap(&self, req: Keymap, _slf: &Rc) -> Result<(), Self::Error> { + if req.format != wl_keyboard::XKB_V1 { + return Err(ZwpVirtualKeyboardV1Error::UnsupportedFormat(req.format)); + } + if req.size == 0 { + return Err(ZwpVirtualKeyboardV1Error::InvalidKeymap); + } + const MAX_SIZE: u32 = 1024 * 1024; + if req.size > MAX_SIZE { + return Err(ZwpVirtualKeyboardV1Error::OversizedKeymap); + } + let client_mem = ClientMem::new(req.fd.raw(), req.size as usize - 1, true) + .map(Rc::new) + .map_err(ZwpVirtualKeyboardV1Error::MapKeymap)?; + let mut map = vec![]; + client_mem + .offset(0) + .read(&mut map) + .map_err(ZwpVirtualKeyboardV1Error::ReadKeymap)?; + let map = self + .client + .state + .xkb_ctx + .keymap_from_str(&map) + .map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?; + self.keymap_id.set(Some(map.id)); + self.keymap.set(Some(map)); + Ok(()) + } + + fn key(&self, req: Key, _slf: &Rc) -> Result<(), Self::Error> { + self.ensure_keymap(); + let time_usec = (req.time as u64) * 1000; + let state = match req.state { + wl_keyboard::RELEASED => KeyState::Released, + wl_keyboard::PRESSED => KeyState::Pressed, + _ => return Err(ZwpVirtualKeyboardV1Error::UnknownState(req.state)), + }; + self.seat.key_event(time_usec, req.key, state); + Ok(()) + } + + fn modifiers(&self, req: Modifiers, _slf: &Rc) -> Result<(), Self::Error> { + self.ensure_keymap(); + self.seat.set_modifiers( + req.mods_depressed, + req.mods_latched, + req.mods_locked, + req.group, + ); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwpVirtualKeyboardV1; + version = self.version; +} + +impl Object for ZwpVirtualKeyboardV1 {} + +simple_add_obj!(ZwpVirtualKeyboardV1); + +#[derive(Debug, Error)] +pub enum ZwpVirtualKeyboardV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Unknown key state {0}")] + UnknownState(u32), + #[error("Unsupported keymap format {0}")] + UnsupportedFormat(u32), + #[error("Keymap is invalid")] + InvalidKeymap, + #[error("Keymap is too large")] + OversizedKeymap, + #[error("Could not map the keymap")] + MapKeymap(#[source] ClientMemError), + #[error("Could not read the keymap")] + ReadKeymap(#[source] ClientMemError), + #[error("Could not parse the keymap")] + ParseKeymap(#[source] XkbCommonError), +} +efrom!(ZwpVirtualKeyboardV1Error, ClientError); diff --git a/src/it/test_ifs.rs b/src/it/test_ifs.rs index b795b532..65c57bd4 100644 --- a/src/it/test_ifs.rs +++ b/src/it/test_ifs.rs @@ -41,6 +41,8 @@ pub mod test_toplevel_drag; pub mod test_toplevel_drag_manager; pub mod test_viewport; pub mod test_viewporter; +pub mod test_virtual_keyboard; +pub mod test_virtual_keyboard_manager; pub mod test_xdg_activation; pub mod test_xdg_activation_token; pub mod test_xdg_base; diff --git a/src/it/test_ifs/test_keyboard.rs b/src/it/test_ifs/test_keyboard.rs index 87858ee8..f912b6d9 100644 --- a/src/it/test_ifs/test_keyboard.rs +++ b/src/it/test_ifs/test_keyboard.rs @@ -5,7 +5,7 @@ use { test_error::TestResult, test_object::TestObject, test_transport::TestTransport, test_utils::test_expected_event::TEEH, testrun::ParseFull, }, - utils::{buffd::MsgParser, clonecell::CloneCell, once::Once}, + utils::{buffd::MsgParser, clonecell::CloneCell, numcell::NumCell, once::Once}, wire::{wl_keyboard::*, WlKeyboardId, WlSurfaceId}, }, std::rc::Rc, @@ -22,8 +22,12 @@ pub struct TestKeyboard { pub tran: Rc, pub server: CloneCell>>, pub destroyed: Once, + pub keymap: TEEH<(usize, Keymap)>, + pub key: TEEH<(usize, Key)>, + pub modifiers: TEEH<(usize, Modifiers)>, pub enter: TEEH, pub leave: TEEH, + pub event_id: NumCell, } impl TestKeyboard { @@ -35,7 +39,8 @@ impl TestKeyboard { } fn handle_keymap(&self, parser: MsgParser<'_, '_>) -> TestResult { - let _ev = Keymap::parse_full(parser)?; + let ev = Keymap::parse_full(parser)?; + self.keymap.push((self.event_id.fetch_add(1), ev)); Ok(()) } @@ -56,12 +61,14 @@ impl TestKeyboard { } fn handle_key(&self, parser: MsgParser<'_, '_>) -> TestResult { - let _ev = Key::parse_full(parser)?; + let ev = Key::parse_full(parser)?; + self.key.push((self.event_id.fetch_add(1), ev)); Ok(()) } fn handle_modifiers(&self, parser: MsgParser<'_, '_>) -> TestResult { - let _ev = Modifiers::parse_full(parser)?; + let ev = Modifiers::parse_full(parser)?; + self.modifiers.push((self.event_id.fetch_add(1), ev)); Ok(()) } diff --git a/src/it/test_ifs/test_registry.rs b/src/it/test_ifs/test_registry.rs index 6c48d770..3fb385b4 100644 --- a/src/it/test_ifs/test_registry.rs +++ b/src/it/test_ifs/test_registry.rs @@ -15,8 +15,9 @@ use { test_single_pixel_buffer_manager::TestSinglePixelBufferManager, test_subcompositor::TestSubcompositor, test_syncobj_manager::TestSyncobjManager, test_toplevel_drag_manager::TestToplevelDragManager, - test_viewporter::TestViewporter, test_xdg_activation::TestXdgActivation, - test_xdg_base::TestXdgWmBase, + test_viewporter::TestViewporter, + test_virtual_keyboard_manager::TestVirtualKeyboardManager, + test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase, }, test_object::TestObject, test_transport::TestTransport, @@ -52,6 +53,7 @@ pub struct TestRegistrySingletons { pub zwp_linux_dmabuf_v1: u32, pub xdg_toplevel_drag_manager_v1: u32, pub wp_alpha_modifier_v1: u32, + pub zwp_virtual_keyboard_manager_v1: u32, } pub struct TestRegistry { @@ -76,6 +78,7 @@ pub struct TestRegistry { pub dmabuf: CloneCell>>, pub drag_manager: CloneCell>>, pub alpha_modifier: CloneCell>>, + pub virtual_keyboard_manager: CloneCell>>, pub seats: CopyHashMap>, } @@ -144,6 +147,7 @@ impl TestRegistry { zwp_linux_dmabuf_v1, xdg_toplevel_drag_manager_v1, wp_alpha_modifier_v1, + zwp_virtual_keyboard_manager_v1, }; self.singletons.set(Some(singletons.clone())); Ok(singletons) @@ -238,6 +242,13 @@ impl TestRegistry { 1, TestAlphaModifier ); + create_singleton!( + get_virtual_keyboard_manager, + virtual_keyboard_manager, + zwp_virtual_keyboard_manager_v1, + 1, + TestVirtualKeyboardManager + ); pub fn bind( &self, diff --git a/src/it/test_ifs/test_seat.rs b/src/it/test_ifs/test_seat.rs index 6688f3a4..57fba76e 100644 --- a/src/it/test_ifs/test_seat.rs +++ b/src/it/test_ifs/test_seat.rs @@ -42,8 +42,12 @@ impl TestSeat { tran: self.tran.clone(), server: Default::default(), destroyed: Default::default(), + keymap: Default::default(), + key: Default::default(), + modifiers: Default::default(), enter: Default::default(), leave: Default::default(), + event_id: Default::default(), }); self.tran.add_obj(kb.clone())?; self.tran.sync().await; diff --git a/src/it/test_ifs/test_virtual_keyboard.rs b/src/it/test_ifs/test_virtual_keyboard.rs new file mode 100644 index 00000000..05496e78 --- /dev/null +++ b/src/it/test_ifs/test_virtual_keyboard.rs @@ -0,0 +1,86 @@ +use { + crate::{ + backend::KeyState, + ifs::wl_seat::wl_keyboard, + it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport}, + time::now_usec, + wire::{zwp_virtual_keyboard_v1::*, ZwpVirtualKeyboardV1Id}, + }, + std::{cell::Cell, io::Write, rc::Rc}, + uapi::c, +}; + +pub struct TestVirtualKeyboard { + pub id: ZwpVirtualKeyboardV1Id, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestVirtualKeyboard { + pub fn destroy(&self) -> Result<(), TestError> { + if !self.destroyed.replace(true) { + self.tran.send(Destroy { self_id: self.id })?; + } + Ok(()) + } + + pub fn set_keymap(&self, map: &str) -> Result<(), TestError> { + let mut memfd = + uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).unwrap(); + memfd.write_all(map.as_bytes()).unwrap(); + memfd.write_all(&[0]).unwrap(); + uapi::lseek(memfd.raw(), 0, c::SEEK_SET).unwrap(); + uapi::fcntl_add_seals( + memfd.raw(), + c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, + ) + .unwrap(); + self.tran.send(Keymap { + self_id: self.id, + format: wl_keyboard::XKB_V1, + fd: Rc::new(memfd), + size: map.len() as _, + }) + } + + pub fn key(&self, key: u32, state: KeyState) -> Result<(), TestError> { + let state = match state { + KeyState::Released => wl_keyboard::RELEASED, + KeyState::Pressed => wl_keyboard::PRESSED, + }; + self.tran.send(Key { + self_id: self.id, + time: (now_usec() / 1000) as u32, + key, + state, + }) + } + + pub fn modifiers( + &self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + ) -> Result<(), TestError> { + self.tran.send(Modifiers { + self_id: self.id, + mods_depressed, + mods_latched, + mods_locked, + group, + }) + } +} + +impl Drop for TestVirtualKeyboard { + fn drop(&mut self) { + let _ = self.destroy(); + } +} + +test_object! { + TestVirtualKeyboard, ZwpVirtualKeyboardV1; +} + +impl TestObject for TestVirtualKeyboard {} diff --git a/src/it/test_ifs/test_virtual_keyboard_manager.rs b/src/it/test_ifs/test_virtual_keyboard_manager.rs new file mode 100644 index 00000000..79e19e33 --- /dev/null +++ b/src/it/test_ifs/test_virtual_keyboard_manager.rs @@ -0,0 +1,49 @@ +use { + crate::{ + it::{ + test_error::TestResult, + test_ifs::{test_seat::TestSeat, test_virtual_keyboard::TestVirtualKeyboard}, + test_object::TestObject, + test_transport::TestTransport, + }, + wire::{zwp_virtual_keyboard_manager_v1::*, ZwpVirtualKeyboardManagerV1Id}, + }, + std::{cell::Cell, rc::Rc}, +}; + +pub struct TestVirtualKeyboardManager { + pub id: ZwpVirtualKeyboardManagerV1Id, + pub tran: Rc, + pub destroyed: Cell, +} + +impl TestVirtualKeyboardManager { + pub fn new(tran: &Rc) -> Self { + Self { + id: tran.id(), + tran: tran.clone(), + destroyed: Cell::new(false), + } + } + + pub fn create_virtual_keyboard(&self, seat: &TestSeat) -> TestResult> { + let obj = Rc::new(TestVirtualKeyboard { + id: self.tran.id(), + tran: self.tran.clone(), + destroyed: Cell::new(false), + }); + self.tran.add_obj(obj.clone())?; + self.tran.send(CreateVirtualKeyboard { + self_id: self.id, + seat: seat.id, + id: obj.id, + })?; + Ok(obj) + } +} + +test_object! { + TestVirtualKeyboardManager, ZwpVirtualKeyboardManagerV1; +} + +impl TestObject for TestVirtualKeyboardManager {} diff --git a/src/it/test_transport.rs b/src/it/test_transport.rs index 2c846d46..d64b7a19 100644 --- a/src/it/test_transport.rs +++ b/src/it/test_transport.rs @@ -69,6 +69,7 @@ impl TestTransport { dmabuf: Default::default(), drag_manager: Default::default(), alpha_modifier: Default::default(), + virtual_keyboard_manager: Default::default(), seats: Default::default(), }); self.send(wl_display::GetRegistry { diff --git a/src/it/tests.rs b/src/it/tests.rs index 6756d8ea..e669a77a 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -71,6 +71,7 @@ mod t0036_idle; mod t0037_toplevel_drag; mod t0038_subsurface_parent_state; mod t0039_alpha_modifier; +mod t0040_virtual_keyboard; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -129,5 +130,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0037_toplevel_drag, t0038_subsurface_parent_state, t0039_alpha_modifier, + t0040_virtual_keyboard, } } diff --git a/src/it/tests/t0040_virtual_keyboard.rs b/src/it/tests/t0040_virtual_keyboard.rs new file mode 100644 index 00000000..433f14bc --- /dev/null +++ b/src/it/tests/t0040_virtual_keyboard.rs @@ -0,0 +1,188 @@ +use { + crate::{ + backend::KeyState, + clientmem::ClientMem, + it::{test_error::TestResult, testrun::TestRun}, + xkbcommon::XkbContext, + }, + bstr::ByteSlice, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let virtual_keymap_str = { + let xkb = XkbContext::new()?; + let map = xkb.keymap_from_str(VIRTUAL_KEYMAP).unwrap(); + read_keymap(map.map.raw(), map.map_len) + }; + + let ds = run.create_default_setup().await?; + + let s_client = run.create_client().await?; + let s_seat = s_client.get_default_seat().await?; + let s_win = s_client.create_window().await?; + s_win.map2().await?; + s_client.sync().await; + + let s_keymap = s_seat.kb.keymap.expect()?; + let s_key = s_seat.kb.key.expect()?; + let s_modifiers = s_seat.kb.modifiers.expect()?; + + { + let v_client = run.create_client().await?; + let v_seat = v_client.get_default_seat().await?; + let v_kb = v_client + .registry + .get_virtual_keyboard_manager() + .await? + .create_virtual_keyboard(&v_seat.seat)?; + v_kb.set_keymap(VIRTUAL_KEYMAP)?; + v_kb.key(10, KeyState::Pressed)?; + v_kb.key(10, KeyState::Released)?; + v_kb.modifiers(1, 2, 3, 0)?; + v_kb.key(10, KeyState::Pressed)?; + v_kb.key(10, KeyState::Released)?; + v_kb.modifiers(0, 0, 0, 1)?; + v_client.sync().await; + } + + s_client.sync().await; + let (start, keymap) = s_keymap.next().expect("virtual keymap"); + tassert_eq!( + &read_keymap(keymap.fd.raw(), keymap.size as _), + &virtual_keymap_str + ); + { + let (pos, mods) = s_modifiers.next().expect("mods 0"); + tassert_eq!(pos, start + 1); + tassert_eq!( + ( + mods.mods_depressed, + mods.mods_latched, + mods.mods_locked, + mods.group + ), + (0, 0, 0, 0) + ); + } + { + let (pos, key) = s_key.next().expect("key 1"); + tassert_eq!(pos, start + 2); + tassert_eq!((key.key, key.state), (10, 1)); + } + { + let (pos, key) = s_key.next().expect("key 2"); + tassert_eq!(pos, start + 3); + tassert_eq!((key.key, key.state), (10, 0)); + } + { + let (pos, mods) = s_modifiers.next().expect("mods 1"); + tassert_eq!(pos, start + 4); + tassert_eq!( + ( + mods.mods_depressed, + mods.mods_latched, + mods.mods_locked, + mods.group + ), + (1, 2, 3, 0) + ); + } + { + let (pos, key) = s_key.next().expect("key 3"); + tassert_eq!(pos, start + 5); + tassert_eq!((key.key, key.state), (10, 1)); + } + { + let (pos, key) = s_key.next().expect("key 4"); + tassert_eq!(pos, start + 6); + tassert_eq!((key.key, key.state), (10, 0)); + } + { + let (pos, mods) = s_modifiers.next().expect("mods 2"); + tassert_eq!(pos, start + 7); + tassert_eq!( + ( + mods.mods_depressed, + mods.mods_latched, + mods.mods_locked, + mods.group + ), + (0, 0, 0, 0) + ); + } + + ds.kb.press(10); + + s_client.sync().await; + let (pos, keymap) = s_keymap.next().expect("seat keymap"); + tassert_eq!(pos, start + 8); + tassert!(read_keymap(keymap.fd.raw(), keymap.size as _) != virtual_keymap_str); + { + let (pos, mods) = s_modifiers.next().expect("mods 0"); + tassert_eq!(pos, start + 9); + tassert_eq!( + ( + mods.mods_depressed, + mods.mods_latched, + mods.mods_locked, + mods.group + ), + (0, 0, 0, 0) + ); + } + { + let (pos, key) = s_key.next().expect("key 5"); + tassert_eq!(pos, start + 10); + tassert_eq!((key.key, key.state), (10, 1)); + } + { + let (pos, key) = s_key.next().expect("key 6"); + tassert_eq!(pos, start + 11); + tassert_eq!((key.key, key.state), (10, 0)); + } + + Ok(()) +} + +fn read_keymap(fd: i32, size: usize) -> String { + let client_mem = ClientMem::new(fd, size - 1, true).unwrap(); + let client_mem = Rc::new(client_mem).offset(0); + let mut v = vec![]; + client_mem.read(&mut v).unwrap(); + v.as_bstr().to_string() +} + +const VIRTUAL_KEYMAP: &str = r#" + xkb_keymap { + xkb_keycodes { + <2> = 10; # 1 + <29> = 37; # LEFTCTRL + }; + + xkb_types { + type "TWO_LEVEL" { + modifiers = Control; + map[Control] = Level2; + level_name[Level1] = "Base"; + level_name[Level2] = "Control"; + }; + }; + + xkb_compatibility { + interpret.repeat = False; + interpret.locking = False; + interpret Control_L { + action = SetMods(modifiers=Control); + }; + }; + + xkb_symbols { + key <2> { [ 2, at ] }; + key <29> { [ Control_L ] }; + }; + + }; +"#; diff --git a/src/xkbcommon.rs b/src/xkbcommon.rs index 473f6ef8..c6c1531a 100644 --- a/src/xkbcommon.rs +++ b/src/xkbcommon.rs @@ -37,6 +37,7 @@ type xkb_keycode_t = u32; type xkb_layout_index_t = u32; type xkb_level_index_t = u32; type xkb_keysym_t = u32; +type xkb_mod_mask_t = u32; #[repr(C)] struct xkb_rule_names { @@ -97,6 +98,15 @@ extern "C" { fn xkb_state_serialize_mods(state: *mut xkb_state, components: xkb_state_component) -> u32; #[allow(dead_code)] fn xkb_state_serialize_layout(state: *mut xkb_state, components: xkb_state_component) -> u32; + fn xkb_state_update_mask( + state: *mut xkb_state, + depressed_mods: xkb_mod_mask_t, + latched_mods: xkb_mod_mask_t, + locked_mods: xkb_mod_mask_t, + depressed_layout: xkb_layout_index_t, + latched_layout: xkb_layout_index_t, + locked_layout: xkb_layout_index_t, + ) -> xkb_state_component; } pub struct XkbContext { @@ -257,10 +267,8 @@ impl XkbState { self.mods } - #[allow(dead_code)] - pub fn update(&mut self, key: u32, direction: XkbKeyDirection) -> Option { + fn fetch(&mut self, changes: xkb_state_component) -> Option { unsafe { - let changes = xkb_state_update_key(self.state, key + 8, direction.raw() as _); if changes != 0 { self.mods.mods_depressed = xkb_state_serialize_mods(self.state, XKB_STATE_MODS_DEPRESSED.raw() as _); @@ -279,6 +287,34 @@ impl XkbState { } } + pub fn update(&mut self, key: u32, direction: XkbKeyDirection) -> Option { + unsafe { + let changes = xkb_state_update_key(self.state, key + 8, direction.raw() as _); + self.fetch(changes) + } + } + + pub fn set( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + ) -> Option { + unsafe { + let changes = xkb_state_update_mask( + self.state, + mods_depressed, + mods_latched, + mods_locked, + 0, + 0, + group, + ); + self.fetch(changes) + } + } + pub fn unmodified_keysyms(&self, key: u32) -> &[xkb_keysym_t] { let mut res = ptr::null(); unsafe { diff --git a/wire/zwp_virtual_keyboard_manager_v1.txt b/wire/zwp_virtual_keyboard_manager_v1.txt new file mode 100644 index 00000000..8d73de92 --- /dev/null +++ b/wire/zwp_virtual_keyboard_manager_v1.txt @@ -0,0 +1,4 @@ +request create_virtual_keyboard { + seat: id(wl_seat), + id: id(zwp_virtual_keyboard_v1), +} diff --git a/wire/zwp_virtual_keyboard_v1.txt b/wire/zwp_virtual_keyboard_v1.txt new file mode 100644 index 00000000..1de0637c --- /dev/null +++ b/wire/zwp_virtual_keyboard_v1.txt @@ -0,0 +1,21 @@ +request keymap { + format: u32, + fd: fd, + size: u32, +} + +request key { + time: u32, + key: u32, + state: u32, +} + +request modifiers { + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, +} + +request destroy { +}