on-screen keyboard
git clone https://git.lucas.co/cce-keyboard.git
src/vkbd.rs (5.2K)
1 //! `zwp_virtual_keyboard_v1`: how a key on the board reaches the window that
2 //! has keyboard focus.
3 //!
4 //! It runs on a Wayland connection of its own rather than on the one the
5 //! cce-ui runner drives the surface with. The protocol is requests only — the
6 //! compositor never sends the keyboard an event — so this side needs no
7 //! dispatching, only a flush after each request, and keeping it off the
8 //! runner's `EngineState` leaves cce-ui untouched. The compositor treats the
9 //! device like any keyboard (cce-compositor `input_manager.rs`
10 //! `handle_new_virtual_keyboard`): its keys run the window manager's
11 //! bindings, and the focused client repeats a held key at the virtual
12 //! keyboard's repeat info, so the board sends no repeats of its own.
13
14 use std::collections::BTreeSet;
15 use std::io::Write;
16 use std::os::fd::{AsFd, OwnedFd};
17
18 use wayland_client::globals::{registry_queue_init, GlobalListContents};
19 use wayland_client::protocol::{wl_keyboard, wl_registry, wl_seat};
20 use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, QueueHandle};
21 use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
22 zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1, zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
23 };
24
25 pub struct VirtualKeyboard {
26 conn: Connection,
27 _queue: EventQueue<Sink>,
28 keyboard: ZwpVirtualKeyboardV1,
29 /// Codes this side has pressed and not yet released, so a board that
30 /// closes mid-press never leaves a key stuck down in the seat.
31 down: BTreeSet<u32>,
32 }
33
34 /// The connection's dispatch state: nothing here has events worth reading.
35 struct Sink;
36
37 impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for Sink {
38 fn event(
39 _: &mut Self,
40 _: &wl_registry::WlRegistry,
41 _: wl_registry::Event,
42 _: &GlobalListContents,
43 _: &Connection,
44 _: &QueueHandle<Self>,
45 ) {
46 }
47 }
48
49 delegate_noop!(Sink: ignore wl_seat::WlSeat);
50 delegate_noop!(Sink: ZwpVirtualKeyboardManagerV1);
51 delegate_noop!(Sink: ZwpVirtualKeyboardV1);
52
53 impl VirtualKeyboard {
54 /// Connect, create the keyboard on the first seat, and upload `keymap`
55 /// (XKB text). The roundtrip at the end surfaces a refusal — a protocol
56 /// error on the create or the keymap — here, rather than as a silently
57 /// dead connection on the first key.
58 pub fn connect(keymap: &str) -> Result<Self, String> {
59 let conn = Connection::connect_to_env().map_err(|e| format!("no Wayland display: {e}"))?;
60 let (globals, mut queue) = registry_queue_init::<Sink>(&conn).map_err(|e| format!("registry: {e}"))?;
61 let qh = queue.handle();
62 let seat: wl_seat::WlSeat = globals.bind(&qh, 1..=1, ()).map_err(|e| format!("wl_seat: {e}"))?;
63 let manager: ZwpVirtualKeyboardManagerV1 = globals
64 .bind(&qh, 1..=1, ())
65 .map_err(|e| format!("the compositor offers no zwp_virtual_keyboard_manager_v1: {e}"))?;
66 let keyboard = manager.create_virtual_keyboard(&seat, &qh, ());
67 let fd = keymap_fd(keymap).map_err(|e| format!("keymap memfd: {e}"))?;
68 // The size counts the terminating NUL the format requires.
69 keyboard.keymap(wl_keyboard::KeymapFormat::XkbV1.into(), fd.as_fd(), keymap.len() as u32 + 1);
70 queue.roundtrip(&mut Sink).map_err(|e| format!("virtual keyboard refused: {e}"))?;
71 Ok(Self { conn, _queue: queue, keyboard, down: BTreeSet::new() })
72 }
73
74 /// Press or release `code` (evdev). A release of a key that is not down
75 /// is dropped.
76 pub fn key(&mut self, code: u32, pressed: bool) {
77 if pressed {
78 self.down.insert(code);
79 } else if !self.down.remove(&code) {
80 return;
81 }
82 let state = if pressed { wl_keyboard::KeyState::Pressed } else { wl_keyboard::KeyState::Released };
83 self.keyboard.key(now_ms(), code, state.into());
84 self.flush();
85 }
86
87 /// The depressed-modifier mask. Sent beside the modifier keys themselves
88 /// so the seat's state is stated, not inferred from the key stream.
89 pub fn modifiers(&mut self, depressed: u32) {
90 self.keyboard.modifiers(depressed, 0, 0, 0);
91 self.flush();
92 }
93
94 fn flush(&self) {
95 if let Err(e) = self.conn.flush() {
96 log::error!("[cce-keyboard] virtual keyboard connection lost: {e}");
97 }
98 }
99 }
100
101 impl Drop for VirtualKeyboard {
102 fn drop(&mut self) {
103 for code in std::mem::take(&mut self.down).into_iter().rev() {
104 self.keyboard.key(now_ms(), code, wl_keyboard::KeyState::Released.into());
105 }
106 self.keyboard.modifiers(0, 0, 0, 0);
107 self.keyboard.destroy();
108 let _ = self.conn.flush();
109 }
110 }
111
112 /// The keymap in an anonymous file, NUL-terminated, for the compositor to map.
113 fn keymap_fd(keymap: &str) -> std::io::Result<OwnedFd> {
114 let fd = rustix::fs::memfd_create("cce-keyboard-keymap", rustix::fs::MemfdFlags::CLOEXEC)?;
115 let mut file = std::fs::File::from(fd);
116 file.write_all(keymap.as_bytes())?;
117 file.write_all(&[0])?;
118 Ok(file.into())
119 }
120
121 /// Key event time: CLOCK_MONOTONIC in ms, the clock input timestamps use.
122 fn now_ms() -> u32 {
123 let t = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
124 (t.tv_sec as u64 * 1000 + t.tv_nsec as u64 / 1_000_000) as u32
125 }