pixelflux/wayland/
ficlient.rs1use std::os::unix::net::UnixStream;
37use std::sync::{Mutex, OnceLock};
38use std::time::{Duration, Instant};
39
40use wayland_client::backend::WaylandError;
41use wayland_client::protocol::wl_registry;
42use wayland_client::{Connection, Dispatch, EventQueue, QueueHandle, delegate_noop};
43use wayland_protocols_plasma::fake_input::client::org_kde_kwin_fake_input::OrgKdeKwinFakeInput;
44
45use crate::wayland::wlclient::{SyncState, bounded_roundtrip, impl_sync_callback};
46
47const FAKE_INPUT_VERSION: u32 = 5;
50const RETRY_AFTER: Duration = Duration::from_secs(5);
53
54#[derive(Default)]
55struct Globals {
56 fake_input: Option<OrgKdeKwinFakeInput>,
57 sync_done: bool,
58}
59
60impl SyncState for Globals {
61 fn sync_done_mut(&mut self) -> &mut bool {
62 &mut self.sync_done
63 }
64}
65impl_sync_callback!(Globals);
66
67impl Dispatch<wl_registry::WlRegistry, ()> for Globals {
68 fn event(
69 state: &mut Self,
70 registry: &wl_registry::WlRegistry,
71 event: wl_registry::Event,
72 _: &(),
73 _: &Connection,
74 qh: &QueueHandle<Self>,
75 ) {
76 if let wl_registry::Event::Global {
77 name,
78 interface,
79 version,
80 } = event
81 && interface == "org_kde_kwin_fake_input"
82 && state.fake_input.is_none()
83 {
84 state.fake_input = Some(registry.bind(name, version.min(FAKE_INPUT_VERSION), qh, ()));
85 }
86 }
87}
88
89delegate_noop!(Globals: OrgKdeKwinFakeInput);
90
91struct Device {
92 conn: Connection,
93 queue: EventQueue<Globals>,
94 state: Globals,
95 device: OrgKdeKwinFakeInput,
96}
97
98impl Device {
99 fn connect(socket_path: &str) -> Result<Option<Device>, String> {
102 let stream =
103 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
104 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
105 let mut queue = conn.new_event_queue();
106 let qh = queue.handle();
107 let _registry = conn.display().get_registry(&qh, ());
108 let mut state = Globals::default();
109 bounded_roundtrip(&conn, &mut queue, &mut state)?;
110 let Some(device) = state.fake_input.take() else {
111 return Ok(None);
112 };
113 device.authenticate("pixelflux".to_string(), "remote pointer motion".to_string());
114 bounded_roundtrip(&conn, &mut queue, &mut state)?;
116 Ok(Some(Device {
117 conn,
118 queue,
119 state,
120 device,
121 }))
122 }
123
124 fn motion(&mut self, dx: f64, dy: f64) -> Result<(), String> {
128 if let Some(guard) = self.conn.prepare_read() {
129 match guard.read() {
130 Ok(_) => {}
131 Err(WaylandError::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {}
132 Err(e) => return Err(format!("read: {e}")),
133 }
134 }
135 self.queue
136 .dispatch_pending(&mut self.state)
137 .map_err(|e| format!("dispatch: {e}"))?;
138 self.device.pointer_motion(dx, dy);
139 match self.conn.flush() {
140 Ok(()) => Ok(()),
141 Err(WaylandError::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(()),
142 Err(e) => Err(format!("flush: {e}")),
143 }
144 }
145}
146
147#[derive(Default)]
148struct Slot {
149 socket: Option<String>,
151 device: Option<Device>,
152 absent: bool,
155 connecting: bool,
156 last_attempt: Option<Instant>,
157 failure_logged: bool,
158 generation: u64,
161}
162
163fn slot() -> &'static Mutex<Slot> {
164 static SLOT: OnceLock<Mutex<Slot>> = OnceLock::new();
165 SLOT.get_or_init(Default::default)
166}
167
168fn lock_slot() -> std::sync::MutexGuard<'static, Slot> {
169 slot()
170 .lock()
171 .unwrap_or_else(|poisoned| poisoned.into_inner())
172}
173
174pub fn arm(socket_path: Option<String>) {
178 let mut s = lock_slot();
179 s.socket = socket_path;
180 s.device = None;
181 s.absent = false;
182 s.last_attempt = None;
183 s.failure_logged = false;
184 s.generation += 1;
185 spawn_connect(&mut s);
186}
187
188fn spawn_connect(s: &mut Slot) {
191 let Some(sock) = s.socket.clone() else {
192 return;
193 };
194 if s.connecting || s.absent || s.last_attempt.is_some_and(|t| t.elapsed() < RETRY_AFTER) {
195 return;
196 }
197 s.connecting = true;
198 s.last_attempt = Some(Instant::now());
199 let generation = s.generation;
200 let spawned = std::thread::Builder::new()
201 .name("pixelflux-fake-input".into())
202 .spawn(move || {
203 let outcome = Device::connect(&sock);
204 let mut s = lock_slot();
205 s.connecting = false;
206 if s.generation != generation {
207 spawn_connect(&mut s);
210 return;
211 }
212 match outcome {
213 Ok(Some(device)) => {
214 eprintln!("[FakeInput] relative pointer motion rides org_kde_kwin_fake_input on {sock}");
215 s.device = Some(device);
216 s.failure_logged = false;
217 }
218 Ok(None) => s.absent = true,
219 Err(e) => {
220 if !s.failure_logged {
221 eprintln!("[FakeInput] {sock}: {e}; the seat carries relative motion until it is reachable");
222 s.failure_logged = true;
223 }
224 }
225 }
226 });
227 if spawned.is_err() {
228 s.connecting = false;
229 }
230}
231
232pub fn pointer_motion_rel(dx: f64, dy: f64) -> bool {
236 let mut s = lock_slot();
237 if let Some(device) = s.device.as_mut() {
238 match device.motion(dx, dy) {
239 Ok(()) => return true,
240 Err(e) => {
241 eprintln!("[FakeInput] {e}; reconnecting");
242 s.device = None;
243 s.last_attempt = None;
244 }
245 }
246 }
247 spawn_connect(&mut s);
248 false
249}