1use std::collections::{HashMap, HashSet};
22use std::os::fd::AsFd;
23use std::os::unix::net::UnixStream;
24use std::sync::atomic::{AtomicU64, Ordering};
25use std::sync::{Mutex, OnceLock};
26use std::time::Duration;
27
28use wayland_client::protocol::{wl_registry, wl_seat};
29use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, QueueHandle};
30use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
31 zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1,
32 zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
33};
34
35use crate::wayland::keymap::{compile_rmlvo, KeymapPolicy};
36use crate::wayland::wlclient::{bounded_roundtrip, impl_sync_callback, memfd_with, SyncState};
37
38const OVERLAY_FIRST_XKB: u32 = 150;
41const OVERLAY_LAST_XKB: u32 = 255;
42const OVERLAY_SLOTS: usize = (OVERLAY_LAST_XKB - OVERLAY_FIRST_XKB + 1) as usize;
43const EVDEV_OFFSET: u32 = 8;
45const KEYMAP_FORMAT_XKB_V1: u32 = 1;
46
47#[derive(Default)]
48struct Globals {
49 seat: Option<wl_seat::WlSeat>,
50 manager: Option<ZwpVirtualKeyboardManagerV1>,
51 sync_done: bool,
52}
53
54impl SyncState for Globals {
55 fn sync_done_mut(&mut self) -> &mut bool {
56 &mut self.sync_done
57 }
58}
59impl_sync_callback!(Globals);
60
61impl Dispatch<wl_registry::WlRegistry, ()> for Globals {
62 fn event(
63 state: &mut Self,
64 registry: &wl_registry::WlRegistry,
65 event: wl_registry::Event,
66 _: &(),
67 _: &Connection,
68 qh: &QueueHandle<Self>,
69 ) {
70 if let wl_registry::Event::Global { name, interface, .. } = event {
71 match interface.as_str() {
73 "wl_seat" if state.seat.is_none() => {
74 state.seat = Some(registry.bind(name, 1, qh, ()));
75 }
76 "zwp_virtual_keyboard_manager_v1" if state.manager.is_none() => {
77 state.manager = Some(registry.bind(name, 1, qh, ()));
78 }
79 _ => {}
80 }
81 }
82 }
83}
84
85delegate_noop!(Globals: ignore wl_seat::WlSeat);
86delegate_noop!(Globals: ZwpVirtualKeyboardManagerV1);
87delegate_noop!(Globals: ZwpVirtualKeyboardV1);
88
89pub(crate) fn us_base_text() -> Option<&'static str> {
92 static CACHE: OnceLock<Option<String>> = OnceLock::new();
93 CACHE.get_or_init(|| compile_rmlvo("", "", "us", "", "")).as_deref()
94}
95
96fn shared_policy() -> Option<&'static Mutex<KeymapPolicy>> {
101 static POLICY: OnceLock<Option<Mutex<KeymapPolicy>>> = OnceLock::new();
102 POLICY
103 .get_or_init(|| {
104 let mut policy =
105 KeymapPolicy::with_overlay_range(OVERLAY_FIRST_XKB, OVERLAY_LAST_XKB);
106 policy.rebuild_base(us_base_text()?.to_string()).then(|| Mutex::new(policy))
107 })
108 .as_ref()
109}
110
111fn upload_keymap(
112 vk: &ZwpVirtualKeyboardV1,
113 queue: &mut EventQueue<Globals>,
114 text: &str,
115) -> Result<(), String> {
116 let mut data = text.as_bytes().to_vec();
117 data.push(0);
119 let fd = memfd_with(&data)?;
120 vk.keymap(KEYMAP_FORMAT_XKB_V1, fd.as_fd(), data.len() as u32);
121 queue.flush().map_err(|e| format!("flush keymap: {e}"))
122}
123
124fn universal_keysym(c: char) -> Option<u32> {
130 match c {
131 '\n' | '\r' => Some(0xFF0D),
132 '\t' => Some(0xFF09),
133 '\x1b' => Some(0xFF1B),
134 _ => match c as u32 {
135 cp @ 0x20..=0xFF => Some(cp),
136 cp @ 0x100.. => Some(0x0100_0000 | cp),
137 _ => None,
138 },
139 }
140}
141
142static KEYMAP_GENERATION: AtomicU64 = AtomicU64::new(1);
145
146struct Typer {
147 conn: Connection,
148 queue: EventQueue<Globals>,
149 state: Globals,
150 vk: ZwpVirtualKeyboardV1,
151 uploaded_generation: u64,
152}
153
154fn typers() -> &'static Mutex<HashMap<String, Typer>> {
155 static TYPERS: OnceLock<Mutex<HashMap<String, Typer>>> = OnceLock::new();
156 TYPERS.get_or_init(|| Mutex::new(HashMap::new()))
157}
158
159fn connect_typer(socket_path: &str) -> Result<Typer, String> {
160 let stream =
161 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
162 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
163 let mut queue = conn.new_event_queue();
164 let qh = queue.handle();
165 let _registry = conn.display().get_registry(&qh, ());
166 let mut state = Globals::default();
167 bounded_roundtrip(&conn, &mut queue, &mut state)?;
168 let seat = state.seat.take().ok_or("app compositor advertises no wl_seat")?;
169 let manager = state
170 .manager
171 .take()
172 .ok_or("app compositor does not advertise zwp_virtual_keyboard_manager_v1")?;
173 let vk = manager.create_virtual_keyboard(&seat, &qh, ());
174 bounded_roundtrip(&conn, &mut queue, &mut state)?;
176 Ok(Typer { conn, queue, state, vk, uploaded_generation: 0 })
177}
178
179fn flush_keysyms(
180 typer: &mut Typer,
181 policy: &mut KeymapPolicy,
182 keysyms: &[u32],
183) -> Result<(), String> {
184 let none = HashSet::new();
185 for chunk in keysyms.chunks(OVERLAY_SLOTS) {
188 let (keycodes, changed) = policy.bind_many_plain(chunk, &none);
189 if changed {
190 KEYMAP_GENERATION.fetch_add(1, Ordering::Relaxed);
191 }
192 let generation = KEYMAP_GENERATION.load(Ordering::Relaxed);
196 if typer.uploaded_generation != generation {
197 upload_keymap(&typer.vk, &mut typer.queue, &policy.keymap_text())?;
198 bounded_roundtrip(&typer.conn, &mut typer.queue, &mut typer.state)?;
199 std::thread::sleep(Duration::from_millis(10));
200 typer.uploaded_generation = generation;
201 }
202 for &kc in &keycodes {
205 if kc < EVDEV_OFFSET {
207 continue;
208 }
209 typer.vk.key(0, kc - EVDEV_OFFSET, 1);
210 typer.vk.key(0, kc - EVDEV_OFFSET, 0);
211 }
212 typer.queue.flush().map_err(|e| format!("flush keys: {e}"))?;
213 bounded_roundtrip(&typer.conn, &mut typer.queue, &mut typer.state)?;
214 }
215 Ok(())
216}
217
218pub fn type_text_to(socket_path: &str, text: &str) -> Result<(), String> {
222 let syms: Vec<u32> = text.chars().filter_map(universal_keysym).collect();
223 type_keysyms_to(socket_path, &syms)
224}
225
226pub fn type_keysyms_to(socket_path: &str, keysyms: &[u32]) -> Result<(), String> {
230 let mut policy = shared_policy()
231 .ok_or("us base keymap failed to compile")?
232 .lock()
233 .unwrap_or_else(|poisoned| poisoned.into_inner());
234 let mut typers = typers().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
235 let mut last_err = None;
236 for _ in 0..2 {
237 if !typers.contains_key(socket_path) {
238 typers.insert(socket_path.to_string(), connect_typer(socket_path)?);
239 }
240 let typer = typers.get_mut(socket_path).expect("typer just ensured");
241 match flush_keysyms(typer, &mut policy, keysyms) {
242 Ok(()) => return Ok(()),
243 Err(e) => {
244 typers.remove(socket_path);
248 last_err = Some(e);
249 }
250 }
251 }
252 Err(last_err.unwrap_or_else(|| "virtual-keyboard flush failed".to_string()))
253}