package miaou-driver-matrix
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Miaou high-performance terminal driver with diff rendering
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.5.2.tar.gz
md5=60a3b9f181f24572a06a9492532bfdda
sha512=fcc35a275066be2900e6201782faf47503076fa4640f08cf78067835a6f447b74613009e55b2ac799adb7ca46f1bffa261fc5971753f2cc3c6bef327511c7ef6
doc/src/miaou-driver-matrix.driver/matrix_input.ml.html
Source file matrix_input.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157(*****************************************************************************) (* *) (* SPDX-License-Identifier: MIT *) (* Copyright (c) 2025 Nomadic Labs <contact@nomadic-labs.com> *) (* *) (*****************************************************************************) (** Matrix driver input handling — decoupled reader fiber with event queue. A dedicated Eio fiber continuously awaits readability on the terminal fd via {!Eio_unix.await_readable}, reads bytes with a non-blocking {!Input_parser.refill_nonblocking}, parses keys, and pushes {!Matrix_io.event} values into a mutex-protected queue. The main tick loop drains the queue at the start of every tick and processes all buffered events, giving sub-millisecond input latency regardless of the TPS setting. *) module Parser = Miaou_driver_common.Input_parser (** {2 Event queue} *) module Event_queue : sig type t val create : unit -> t (** Push an event (called from reader fiber). *) val push : t -> Matrix_io.event -> unit (** Drain all pending events into a list (oldest first). Returns the empty list when the queue is empty. *) val drain : t -> Matrix_io.event list end = struct type t = {mu : Mutex.t; q : Matrix_io.event Queue.t} let create () = {mu = Mutex.create (); q = Queue.create ()} let push t ev = Mutex.lock t.mu ; Queue.push ev t.q ; Mutex.unlock t.mu let drain t = Mutex.lock t.mu ; let events = Queue.fold (fun acc ev -> ev :: acc) [] t.q in Queue.clear t.q ; Mutex.unlock t.mu ; List.rev events end (** {2 Input reader} *) type t = { terminal : Matrix_terminal.t; parser : Parser.t; exit_flag : bool Atomic.t; queue : Event_queue.t; shutdown : bool Atomic.t; mutable last_refresh_time : float; } (* Refresh interval in seconds — controls how often a synthetic Refresh event is injected so that service_cycle runs even when idle. *) let refresh_interval = 1.0 (** Convert Parser.key to event. Terminal mouse protocols use 1-indexed coordinates; convert to 0-indexed to match the buffer coordinate system. *) let key_to_event = function | Parser.Mouse {row; col; ; release} -> let row = row - 1 and col = col - 1 in if release then Matrix_io.Mouse (row, col, button) else Matrix_io.MousePress (row, col, button) | Parser.MouseDrag {row; col} -> Matrix_io.MouseDrag (row - 1, col - 1) | Parser.Refresh -> Matrix_io.Refresh | key -> Matrix_io.Key (Parser.key_to_string key) (** Check for non-input events (quit signal, resize, render_notify). *) let check_non_input_events (t : t) = if Atomic.get t.exit_flag then ( Event_queue.push t.queue Matrix_io.Quit ; true) else if Matrix_terminal.resize_pending t.terminal then ( Matrix_terminal.clear_resize_pending t.terminal ; Event_queue.push t.queue Matrix_io.Resize ; true) else if Miaou_helpers.Render_notify.should_render () then ( Event_queue.push t.queue Matrix_io.Refresh ; true) else false (** Inject a periodic Refresh event so service_cycle runs when idle. *) let maybe_inject_refresh (t : t) = let now = Unix.gettimeofday () in if now -. t.last_refresh_time >= refresh_interval then ( t.last_refresh_time <- now ; Event_queue.push t.queue Matrix_io.Refresh) (** Parse all available keys from the parser buffer into the queue. *) let parse_all_into_queue (t : t) = let rec go () = match Parser.parse_key t.parser with | Some key -> Event_queue.push t.queue (key_to_event key) ; go () | None -> () in go () (** Reader-fiber main loop. Uses {!Eio_unix.await_readable} to yield to the Eio scheduler while waiting for terminal input, then performs a non-blocking read + parse burst. *) let reader_loop (t : t) _env = let fd = Parser.fd t.parser in while not (Atomic.get t.shutdown) do if not (check_non_input_events t) then begin (* Yield to the Eio scheduler until the fd has data *) (try Eio_unix.await_readable fd with | Eio.Cancel.Cancelled _ -> () | Unix.Unix_error (Unix.EINTR, _, _) -> ()) ; if not (Atomic.get t.shutdown) then begin let n = Parser.refill_nonblocking t.parser in if n > 0 then parse_all_into_queue t else maybe_inject_refresh t end end done let create ?(handle_sigint = true) terminal = let fd = Matrix_terminal.fd terminal in let exit_flag = Matrix_terminal.install_signals' terminal (fun () -> Matrix_terminal.cleanup terminal) ~handle_sigint () in { terminal; parser = Parser.create fd; exit_flag; queue = Event_queue.create (); shutdown = Atomic.make false; last_refresh_time = 0.0; } (** Start the background reader fiber. Must be called after terminal raw-mode setup, from inside an Eio switch. *) let start t = let open Miaou_helpers.Fiber_runtime in spawn (fun env -> reader_loop t env) (** Signal the reader fiber to stop. *) let stop t = Atomic.set t.shutdown true (** Drain all pending events (oldest first). *) let drain t = Event_queue.drain t.queue
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>