nandi/jolt-nativepublic Fork 0
99fabc1d57f0e0d0674b8ef5fbfd5960927abd38
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Move the jolt libraries under glimmer-backends 19df0d8 · on 99fabc1d57f0e0d0674b8ef5fbfd5960927abd38 · nandi · 12d ago
ffi.clj · 116 lines · 5.7 KBClojure Blame HistoryRaw
  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
(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))))