(ns glimmer-tui.ffi "Raw bindings for libjolttui's retained-tree ABI (`crates/jolt-tui/include/jolttui.h`). Nothing here interprets a prop or an event; that is `glimmer-tui.core`'s job. This namespace exists so the boundary is one readable list of symbols, and so the marshalling rules that come with it are stated once. **Strings returned by this library are borrowed.** They come out of scratch buffers the next call of the same family overwrites — props, tags and screen lines share one, dumps have their own, and an event's name and its text have one each, so an event can be read whole. Jolt copies a `:string` return into a Scheme string as it crosses, so holding the value is safe. **Every call belongs to the thread that opened the session.** The library keeps its state in thread-local storage, so a call from anywhere else is inert rather than unsound — still a bug. `glimmer-tui.core` routes off-thread work through glimmer's `schedule`." (:require [jolt.ffi :as ffi])) ;; --- the session ------------------------------------------------------------- ;; Two ways in, one code path out of them: `raw-open` takes the terminal, and ;; `raw-headless` opens a session of a fixed size with no terminal at all — ;; same layout, same painting, same focus ring, read back with `screen-line`. (ffi/defcfn raw-open "tui_open" [:int] :int) (ffi/defcfn raw-headless "tui_headless" [:int :int] :int) (ffi/defcfn close! "tui_close" [] :void) (ffi/defcfn raw-should-close "tui_should_close" [] :int) (ffi/defcfn quit-loop! "tui_quit" [] :void) ;; --- the loop ---------------------------------------------------------------- ;; `tick` waits up to timeout_ms for input and answers how much it handled; ;; `frame!` lays the tree out, paints it and sends only the cells that changed. (ffi/defcfn tick "tui_tick" [:int] :int) (ffi/defcfn frame! "tui_frame" [] :void) ;; --- the screen, in cells ---------------------------------------------------- (ffi/defcfn screen-width "tui_screen_width" [] :int) (ffi/defcfn screen-height "tui_screen_height" [] :int) (ffi/defcfn screen-line "tui_screen_line" [:int] :string) ;; --- input by hand ----------------------------------------------------------- ;; The same entry points a real terminal's input arrives through, so a test ;; drives the UI exactly as a person does. (ffi/defcfn raw-feed-key "tui_feed_key" [:string] :int) (ffi/defcfn raw-feed-click "tui_feed_click" [:int :int] :int) (ffi/defcfn raw-feed-wheel "tui_feed_wheel" [:int :int :int] :int) (ffi/defcfn focus "tui_focus" [] :int) ;; --- the tree ---------------------------------------------------------------- (ffi/defcfn tree-root "tui_tree_root" [] :int) (ffi/defcfn node-new "tui_node_new" [:string] :int) (ffi/defcfn node-free! "tui_node_free" [:int] :void) (ffi/defcfn raw-node-exists "tui_node_exists" [:int] :int) (ffi/defcfn node-set-str! "tui_node_set_str" [:int :string :string] :void) (ffi/defcfn node-set-num! "tui_node_set_num" [:int :string :double] :void) (ffi/defcfn raw-node-set-bool "tui_node_set_bool" [:int :string :int] :void) (ffi/defcfn node-clear-props! "tui_node_clear_props" [:int] :void) (ffi/defcfn node-get-str "tui_node_get_str" [:int :string] :string) (ffi/defcfn node-get-num "tui_node_get_num" [:int :string] :double) (ffi/defcfn raw-node-get-bool "tui_node_get_bool" [:int :string] :int) (ffi/defcfn node-tag "tui_node_tag" [:int] :string) (ffi/defcfn node-parent "tui_node_parent" [:int] :int) (ffi/defcfn node-child-count "tui_node_child_count" [:int] :int) (ffi/defcfn node-child-at "tui_node_child_at" [:int :int] :int) (ffi/defcfn tree-dump "tui_tree_dump" [:int] :string) (ffi/defcfn raw-node-append "tui_node_append" [:int :int] :int) (ffi/defcfn node-remove! "tui_node_remove" [:int :int] :void) (ffi/defcfn raw-node-insert-after "tui_node_insert_after" [:int :int :int] :int) (ffi/defcfn raw-node-replace "tui_node_replace" [:int :int :int] :int) ;; --- events ------------------------------------------------------------------ (ffi/defcfn raw-poll-event "tui_tree_poll_event" [] :int) (ffi/defcfn event-node "tui_tree_event_node" [] :int) (ffi/defcfn event-name "tui_tree_event_name" [] :string) (ffi/defcfn event-text "tui_tree_event_text" [] :string) (ffi/defcfn event-num "tui_tree_event_num" [] :double) ;; --- the int/bool seam ------------------------------------------------------- ;; C has no booleans; every predicate here crosses as 0 or 1. Converting at the ;; binding rather than at each call site keeps the rest of the backend written ;; in jolt's own truthiness. (defn open! "Take the terminal: raw mode, the alternate screen, the cursor hidden unless a focused entry asks for it, and mouse reporting when `mouse?`." [mouse?] (not (zero? (raw-open (if mouse? 1 0))))) (defn headless! "Open a session of `width` by `height` cells with no terminal at all." [width height] (not (zero? (raw-headless width height)))) (defn should-close? [] (not (zero? (raw-should-close)))) (defn node-exists? [node] (not (zero? (raw-node-exists node)))) (defn node-set-bool! [node key value] (raw-node-set-bool node key (if value 1 0))) (defn node-get-bool [node key] (not (zero? (raw-node-get-bool node key)))) (defn node-append! [parent child] (not (zero? (raw-node-append parent child)))) (defn node-insert-after! [parent child sibling] (not (zero? (raw-node-insert-after parent child sibling)))) (defn node-replace! [parent old new] (not (zero? (raw-node-replace parent old new)))) (defn poll-event! [] (not (zero? (raw-poll-event)))) (defn feed-key! "Type one key, named as the terminal names it. True when the backend acted on it, false when it went out as a `key` event instead." [name] (not (zero? (raw-feed-key name)))) (defn feed-click! [x y] (not (zero? (raw-feed-click x y)))) (defn feed-wheel! [x y by] (not (zero? (raw-feed-wheel x y by))))