git.lucas.co / cce-keyboard
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 }