1use std::{fs::File, io};
29
30use parking_lot::Mutex;
31
32use crate::input::{InputDecoder, InputEvent, Keymap};
33
34#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
40pub enum TerminalEvent {
41 Input(InputEvent),
44 Resize,
48 Debug(DebugQuery),
54 Closed,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
61pub struct DebugQuery {
62 pub id: u64,
64 pub op: DebugOp,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
70pub enum DebugOp {
71 Info,
74 Text,
77 Resize,
80 Quit,
83 Frame,
85 Tree,
88 Values,
90}
91enum Ctl {
93 Event(TerminalEvent),
98 Bytes(Vec<u8>),
100 Keymap(Keymap),
102}
103
104static SHARED_CTL: Mutex<Option<flume::Sender<Ctl>>> = Mutex::new(None);
108
109pub fn send_event(event: TerminalEvent) -> bool {
112 send_ctl(Ctl::Event(event))
113}
114
115pub fn inject_bytes(bytes: Vec<u8>) -> bool {
118 send_ctl(Ctl::Bytes(bytes))
119}
120
121fn send_ctl(ctl: Ctl) -> bool {
122 let sender = SHARED_CTL.lock().clone();
123 sender.is_some_and(|sender| sender.send(ctl).is_ok())
124}
125
126#[cfg(test)]
130pub fn publish_ingress_for_test() -> flume::Receiver<TerminalEvent> {
131 let (ctl_tx, ctl_rx) = flume::unbounded();
132 let (event_tx, event_rx) = flume::unbounded();
133 *SHARED_CTL.lock() = Some(ctl_tx);
134 std::thread::spawn(move || {
135 while let Ok(ctl) = ctl_rx.recv() {
136 if let Ctl::Event(event) = ctl
137 && event_tx.send(event).is_err()
138 {
139 return;
140 }
141 }
142 });
143 event_rx
144}
145
146pub struct Pump {
149 task: tokio::task::JoinHandle<()>,
150 bridge: Option<Bridge>,
151 ctl: flume::Sender<Ctl>,
152}
153
154struct Bridge {
156 stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
157 worker: Option<std::thread::JoinHandle<()>>,
158}
159
160impl Pump {
161 pub(crate) fn publish(&self) {
164 *SHARED_CTL.lock() = Some(self.ctl.clone());
165 }
166
167 pub(crate) fn set_keymap(&self, keymap: Keymap) {
170 let _ = self.ctl.send(Ctl::Keymap(keymap));
171 }
172
173 pub(crate) fn stop(&mut self) {
179 self.task.abort();
180 if let Some(bridge) = self.bridge.as_mut() {
181 bridge
182 .stop
183 .store(true, std::sync::atomic::Ordering::Release);
184 if let Some(worker) = bridge.worker.take() {
185 let _ = worker.join();
186 }
187 }
188 }
189}
190
191impl Drop for Pump {
192 fn drop(&mut self) {
193 self.stop();
194 }
195}
196
197pub struct PumpChannels {
199 pub pump: Pump,
201 pub events: flume::Receiver<TerminalEvent>,
203 pub resize: tokio::sync::watch::Receiver<u64>,
206}
207
208enum ByteSource {
210 #[cfg(unix)]
213 Fd(tokio::io::unix::AsyncFd<std::os::fd::OwnedFd>),
214 Thread(flume::Receiver<Vec<u8>>),
216}
217
218impl ByteSource {
219 async fn next(&mut self) -> io::Result<Option<Vec<u8>>> {
224 match self {
225 #[cfg(unix)]
226 Self::Fd(fd) => loop {
227 let mut guard = fd.readable().await?;
228 let mut bytes = [0_u8; 4096];
229 match guard.try_io(|fd| read_fd(fd.get_ref(), &mut bytes)) {
230 Ok(Ok(0)) => return Ok(None),
231 Ok(Ok(read)) => return Ok(Some(bytes[..read].to_vec())),
232 Ok(Err(error)) => return Err(error),
233 Err(_) => {},
234 }
235 },
236 Self::Thread(rx) => Ok(rx.recv_async().await.ok()),
237 }
238 }
239}
240
241#[cfg(unix)]
243fn read_fd(fd: &std::os::fd::OwnedFd, bytes: &mut [u8]) -> io::Result<usize> {
244 use std::os::fd::AsRawFd as _;
245 loop {
246 let read = unsafe { nix::libc::read(fd.as_raw_fd(), bytes.as_mut_ptr().cast(), bytes.len()) };
248 if read >= 0 {
249 return Ok(read as usize);
250 }
251 let error = io::Error::last_os_error();
252 if error.kind() != io::ErrorKind::Interrupted {
253 return Err(error);
254 }
255 }
256}
257
258pub fn spawn(
266 input: Input,
267 mut decoder: InputDecoder,
268 preserved: &[u8],
269 #[cfg_attr(windows, expect(unused_variables, reason = "windows polls geometry instead"))]
270 resize: Option<ResizeFd>,
271) -> io::Result<PumpChannels> {
272 let (events_tx, events_rx) = flume::unbounded();
273 let (resize_tx, resize_rx) = tokio::sync::watch::channel(0_u64);
274 let (ctl_tx, ctl_rx) = flume::unbounded();
275
276 let (source, bridge) = input.into_source()?;
277 #[cfg(unix)]
278 let resize = resize.map(tokio::io::unix::AsyncFd::new).transpose()?;
279 #[cfg(windows)]
280 let resize = ();
281
282 let mut events = Vec::new();
283 decoder.feed(preserved, std::time::Instant::now(), &mut events);
284
285 let task = tokio::spawn(actor(source, decoder, events, events_tx, ctl_rx, resize, resize_tx));
286 Ok(PumpChannels {
287 pump: Pump { task, bridge, ctl: ctl_tx },
288 events: events_rx,
289 resize: resize_rx,
290 })
291}
292
293#[cfg(unix)]
295pub type ResizeFd = std::os::fd::OwnedFd;
296#[cfg(windows)]
297pub(crate) type ResizeFd = std::convert::Infallible;
298
299pub enum Input {
301 #[cfg(unix)]
303 #[cfg_attr(
304 target_os = "macos",
305 allow(dead_code, reason = "macOS terminals bridge; tests spawn pollable pipe sources")
306 )]
307 Pollable(File),
308 Bridged(File),
311}
312
313impl Input {
314 fn into_source(self) -> io::Result<(ByteSource, Option<Bridge>)> {
315 match self {
316 #[cfg(unix)]
317 Self::Pollable(file) => {
318 use std::os::fd::AsRawFd as _;
319 if unsafe {
321 nix::libc::fcntl(file.as_raw_fd(), nix::libc::F_SETFL, nix::libc::O_NONBLOCK)
322 } < 0
323 {
324 return Err(io::Error::last_os_error());
325 }
326 let fd = tokio::io::unix::AsyncFd::new(std::os::fd::OwnedFd::from(file))?;
327 Ok((ByteSource::Fd(fd), None))
328 },
329 Self::Bridged(file) => {
330 let (tx, rx) = flume::unbounded();
331 let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
332 let bridge_stop = std::sync::Arc::clone(&stop);
333 let worker = std::thread::Builder::new()
334 .name("omp-tui-input".into())
335 .spawn(move || bridge_loop(file, &tx, &bridge_stop))?;
336 Ok((ByteSource::Thread(rx), Some(Bridge { stop, worker: Some(worker) })))
337 },
338 }
339 }
340}
341
342fn bridge_loop(input: File, tx: &flume::Sender<Vec<u8>>, stop: &std::sync::atomic::AtomicBool) {
346 use std::sync::atomic::Ordering;
347 let mut bytes = [0_u8; 4096];
348 #[cfg(unix)]
349 {
350 use std::{io::Read as _, os::fd::AsRawFd as _};
351 let mut input = input;
352 let mut descriptor =
353 nix::libc::pollfd { fd: input.as_raw_fd(), events: nix::libc::POLLIN, revents: 0 };
354 while !stop.load(Ordering::Acquire) {
355 descriptor.revents = 0;
356 let ready = unsafe { nix::libc::poll(&mut descriptor, 1, 50) };
358 if ready < 0 {
359 if io::Error::last_os_error().kind() == io::ErrorKind::Interrupted {
360 continue;
361 }
362 return;
363 }
364 if ready == 0 {
365 continue;
366 }
367 if descriptor.revents & (nix::libc::POLLERR | nix::libc::POLLNVAL) != 0 {
368 return;
369 }
370 match input.read(&mut bytes) {
371 Ok(0) => return,
372 Ok(read) => {
373 if tx.send(bytes[..read].to_vec()).is_err() {
374 return;
375 }
376 },
377 Err(error)
378 if matches!(
379 error.kind(),
380 io::ErrorKind::Interrupted | io::ErrorKind::WouldBlock
381 ) => {},
382 Err(_) => return,
383 }
384 }
385 }
386 #[cfg(windows)]
387 {
388 use std::{io::Read as _, os::windows::io::AsRawHandle as _};
389 let mut input = input;
390 let handle = input.as_raw_handle();
391 while !stop.load(Ordering::Acquire) {
392 let ready =
393 unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(handle, 50) };
394 if ready == windows_sys::Win32::Foundation::WAIT_TIMEOUT {
395 continue;
396 }
397 if ready != windows_sys::Win32::Foundation::WAIT_OBJECT_0 {
398 return;
399 }
400 match input.read(&mut bytes) {
401 Ok(0) => return,
402 Ok(read) => {
403 if tx.send(bytes[..read].to_vec()).is_err() {
404 return;
405 }
406 },
407 Err(_) => return,
408 }
409 }
410 }
411}
412
413async fn actor(
416 mut source: ByteSource,
417 mut decoder: InputDecoder,
418 mut events: Vec<InputEvent>,
419 events_tx: flume::Sender<TerminalEvent>,
420 ctl_rx: flume::Receiver<Ctl>,
421 #[cfg(unix)] resize: Option<tokio::io::unix::AsyncFd<std::os::fd::OwnedFd>>,
422 #[cfg(windows)] resize: (),
423 resize_tx: tokio::sync::watch::Sender<u64>,
424) {
425 let mut resize_wakes = 0_u64;
429 loop {
430 for event in std::mem::take(&mut events) {
431 if events_tx.send(TerminalEvent::Input(event)).is_err() {
432 return;
433 }
434 }
435 let wake = decoder.deadline().map(tokio::time::Instant::from_std);
436 tokio::select! {
437 biased;
440 () = resize_readable(#[cfg(unix)] resize.as_ref()) => {
441 resize_wakes += 1;
442 if resize_tx.send(resize_wakes).is_err() {
443 return;
444 }
445 },
446 chunk = source.next() => if let Ok(Some(bytes)) = chunk {
447 decoder.feed(&bytes, std::time::Instant::now(), &mut events);
448 } else {
449 let _ = events_tx.send(TerminalEvent::Closed);
450 return;
451 },
452 ctl = ctl_rx.recv_async() => {
456 let Ok(ctl) = ctl else {
457 return;
459 };
460 if !apply_ctl(ctl, &mut decoder, &mut events, &events_tx) {
461 return;
462 }
463 },
464 () = deadline(wake) => {
465 decoder.tick(std::time::Instant::now(), &mut events);
466 },
467 }
468 }
469}
470
471fn apply_ctl(
475 ctl: Ctl,
476 decoder: &mut InputDecoder,
477 events: &mut Vec<InputEvent>,
478 events_tx: &flume::Sender<TerminalEvent>,
479) -> bool {
480 match ctl {
481 Ctl::Bytes(bytes) => {
482 decoder.feed(&bytes, std::time::Instant::now(), events);
483 true
484 },
485 Ctl::Event(event) => {
486 for decoded in events.drain(..) {
487 if events_tx.send(TerminalEvent::Input(decoded)).is_err() {
488 return false;
489 }
490 }
491 events_tx.send(event).is_ok()
492 },
493 Ctl::Keymap(keymap) => {
494 *decoder.keymap_mut() = keymap;
495 true
496 },
497 }
498}
499
500#[cfg(unix)]
503async fn resize_readable(resize: Option<&tokio::io::unix::AsyncFd<std::os::fd::OwnedFd>>) -> () {
504 let Some(fd) = resize else {
505 return std::future::pending().await;
506 };
507 loop {
508 let Ok(mut guard) = fd.readable().await else {
509 return std::future::pending().await;
510 };
511 let mut bytes = [0_u8; 128];
512 match guard.try_io(|fd| read_fd(fd.get_ref(), &mut bytes)) {
513 Ok(Ok(0)) => return std::future::pending().await,
514 Ok(Ok(_)) => return,
515 Ok(Err(_)) => return std::future::pending().await,
516 Err(_) => {},
517 }
518 }
519}
520
521#[cfg(windows)]
522async fn resize_readable(_resize: ()) {
523 std::future::pending().await
524}
525
526async fn deadline(at: Option<tokio::time::Instant>) {
528 match at {
529 Some(at) => tokio::time::sleep_until(at).await,
530 None => std::future::pending().await,
531 }
532}