[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
7.2 kB
263 lines
1export const EVENT_KEY = 1;
2export const EVENT_MOUSE = 2;
3export const EVENT_RESIZE = 3;
4export const EVENT_CURSOR = 4;
5
6export const MOD_ALT = 1;
7export const MOD_CTRL = 2;
8export const MOD_SHIFT = 4;
9export const MOD_MOTION = 8;
10export const MOD_RELEASE = 16;
11
12export const KEY_F1 = 0xFFFF;
13export const KEY_F2 = 0xFFFE;
14export const KEY_F3 = 0xFFFD;
15export const KEY_F4 = 0xFFFC;
16export const KEY_F5 = 0xFFFB;
17export const KEY_F6 = 0xFFFA;
18export const KEY_F7 = 0xFFF9;
19export const KEY_F8 = 0xFFF8;
20export const KEY_F9 = 0xFFF7;
21export const KEY_F10 = 0xFFF6;
22export const KEY_F11 = 0xFFF5;
23export const KEY_F12 = 0xFFF4;
24export const KEY_ARROW_UP = 0xFFF3;
25export const KEY_ARROW_DOWN = 0xFFF2;
26export const KEY_ARROW_LEFT = 0xFFF1;
27export const KEY_ARROW_RIGHT = 0xFFF0;
28export const KEY_HOME = 0xFFEF;
29export const KEY_END = 0xFFEE;
30export const KEY_INSERT = 0xFFED;
31export const KEY_DELETE = 0xFFEC;
32export const KEY_PGUP = 0xFFEB;
33export const KEY_PGDN = 0xFFEA;
34export const KEY_BACKTAB = 0xFFE9;
35export const KEY_NUMPAD_0 = 0xFFE2;
36export const KEY_NUMPAD_1 = 0xFFE1;
37export const KEY_NUMPAD_2 = 0xFFE0;
38export const KEY_NUMPAD_3 = 0xFFDF;
39export const KEY_NUMPAD_4 = 0xFFDE;
40export const KEY_NUMPAD_5 = 0xFFDD;
41export const KEY_NUMPAD_6 = 0xFFDC;
42export const KEY_NUMPAD_7 = 0xFFDB;
43export const KEY_NUMPAD_8 = 0xFFDA;
44export const KEY_NUMPAD_9 = 0xFFD9;
45export const KEY_NUMPAD_DECIMAL = 0xFFD8;
46export const KEY_NUMPAD_DIVIDE = 0xFFD7;
47export const KEY_NUMPAD_MULTIPLY = 0xFFD6;
48export const KEY_NUMPAD_SUBTRACT = 0xFFD5;
49export const KEY_NUMPAD_ADD = 0xFFD4;
50export const KEY_NUMPAD_ENTER = 0xFFD3;
51export const KEY_NUMPAD_EQUAL = 0xFFD2;
52
53export const KEY_SHIFT_LEFT = 0xFFD1;
54export const KEY_SHIFT_RIGHT = 0xFFD0;
55export const KEY_CONTROL_LEFT = 0xFFCF;
56export const KEY_CONTROL_RIGHT = 0xFFCE;
57export const KEY_ALT_LEFT = 0xFFCD;
58export const KEY_ALT_RIGHT = 0xFFCC;
59export const KEY_SUPER_LEFT = 0xFFCB;
60export const KEY_SUPER_RIGHT = 0xFFCA;
61export const KEY_HYPER_LEFT = 0xFFC9;
62export const KEY_HYPER_RIGHT = 0xFFC8;
63export const KEY_META_LEFT = 0xFFC7;
64export const KEY_META_RIGHT = 0xFFC6;
65export const KEY_CAPS_LOCK = 0xFFC5;
66export const KEY_NUM_LOCK = 0xFFC4;
67export const KEY_SCROLL_LOCK = 0xFFC3;
68
69export const KEY_MOUSE_LEFT = 0xFFE8;
70export const KEY_MOUSE_RIGHT = 0xFFE7;
71export const KEY_MOUSE_MIDDLE = 0xFFE6;
72export const KEY_MOUSE_RELEASE = 0xFFE5;
73export const KEY_MOUSE_WHEEL_UP = 0xFFE4;
74export const KEY_MOUSE_WHEEL_DOWN = 0xFFE3;
75export const KEY_ESC = 0x1B;
76export const KEY_ENTER = 0x0D;
77export const KEY_TAB = 0x09;
78export const KEY_BACKSPACE = 0x7F;
79export const KEY_SPACE = 0x20;
80
81import {
82 array,
83 int32,
84 offsets,
85 struct,
86 uint16,
87 uint32,
88 uint8,
89} from "./typedef.ts";
90
91const MAX_TEXT_CODEPOINTS = 8;
92
93const InputEventLayout = struct({
94 type: uint8(),
95 mod: uint8(),
96 key: uint16(),
97 ch: uint32(),
98 x: int32(),
99 y: int32(),
100 w: int32(),
101 h: int32(),
102 action: uint8(),
103 shifted: uint32(),
104 base: uint32(),
105 text: array(uint32(), MAX_TEXT_CODEPOINTS),
106 text_len: uint8(),
107});
108
109const {
110 type: OFFSET_TYPE,
111 mod: OFFSET_MOD,
112 key: OFFSET_KEY,
113 ch: OFFSET_CH,
114 x: OFFSET_X,
115 y: OFFSET_Y,
116 w: OFFSET_W,
117 h: OFFSET_H,
118 action: OFFSET_ACTION,
119 text_len: OFFSET_TEXT_LEN,
120 shifted: OFFSET_SHIFTED,
121 base: OFFSET_BASE,
122 text: OFFSET_TEXT,
123} = offsets(InputEventLayout);
124
125export interface NativeInputEvent {
126 type: number;
127 mod: number;
128 key: number;
129 ch: number;
130 x: number;
131 y: number;
132 w: number;
133 h: number;
134 action: number;
135 shifted: number;
136 base: number;
137 text: number[];
138}
139
140export function readEvent(view: DataView, ptr: number): NativeInputEvent {
141 let len = view.getUint8(ptr + OFFSET_TEXT_LEN);
142 let text: number[] = [];
143 for (let i = 0; i < len && i < MAX_TEXT_CODEPOINTS; i++) {
144 text.push(view.getUint32(ptr + OFFSET_TEXT + i * 4, true));
145 }
146 return {
147 type: view.getUint8(ptr + OFFSET_TYPE),
148 mod: view.getUint8(ptr + OFFSET_MOD),
149 key: view.getUint16(ptr + OFFSET_KEY, true),
150 ch: view.getUint32(ptr + OFFSET_CH, true),
151 x: view.getInt32(ptr + OFFSET_X, true),
152 y: view.getInt32(ptr + OFFSET_Y, true),
153 w: view.getInt32(ptr + OFFSET_W, true),
154 h: view.getInt32(ptr + OFFSET_H, true),
155 action: view.getUint8(ptr + OFFSET_ACTION),
156 shifted: view.getUint32(ptr + OFFSET_SHIFTED, true),
157 base: view.getUint32(ptr + OFFSET_BASE, true),
158 text,
159 };
160}
161
162export interface InputNative {
163 memory: WebAssembly.Memory;
164 state: number;
165 buffer: number;
166 scan(st: number, buf: number, len: number, now: number): number;
167 count(st: number): number;
168 event(st: number, index: number): number;
169 delay(st: number): number;
170}
171
172/**
173 * Attachment surface provided by a TermInfo handle: the shared memory,
174 * its bump allocator, and the capability struct / raw terminfo region
175 * pointers. See terminfo.ts internals().
176 */
177export interface InputAttach {
178 memory: WebAssembly.Memory;
179 exports: Record<string, CallableFunction>;
180 structPtr: number;
181 bytesPtr: number;
182 bytesLen: number;
183 alloc(size: number, align?: number): number;
184}
185
186import { compiled } from "./wasm.ts";
187
188export async function createInputNative(
189 escLatency: number,
190 attach?: InputAttach,
191): Promise<InputNative> {
192 let memory = attach?.memory ?? new WebAssembly.Memory({ initial: 4 });
193
194 let raw: unknown;
195 if (attach) {
196 // Reuse the handle's instance: instantiating the module again over
197 // the shared memory would rewrite its data segments and clobber
198 // static state already initialized there.
199 raw = attach.exports;
200 } else {
201 let instance = await WebAssembly.instantiate(compiled, {
202 env: { memory },
203 clay: {
204 measureTextFunction() {},
205 queryScrollOffsetFunction(ret: number) {
206 let v = new DataView(memory.buffer);
207 v.setFloat32(ret, 0, true);
208 v.setFloat32(ret + 4, 0, true);
209 },
210 },
211 });
212 raw = instance.exports;
213 }
214
215 let exports = raw as {
216 __heap_base: WebAssembly.Global;
217 input_size(): number;
218 input_init(
219 mem: number,
220 escLatency: number,
221 terminfo: number,
222 terminfoLen: number,
223 ti: number,
224 ): number;
225 input_scan(st: number, buf: number, len: number, now: number): number;
226 input_count(st: number): number;
227 input_event(st: number, index: number): number;
228 input_delay(st: number): number;
229 };
230
231 let size = exports.input_size();
232 let state: number;
233 let buffer: number;
234 if (attach) {
235 let arena = attach.alloc(size);
236 buffer = attach.alloc(SCAN_BUFFER_SIZE);
237 state = exports.input_init(
238 arena,
239 escLatency,
240 attach.bytesLen > 0 ? attach.bytesPtr : 0,
241 attach.bytesLen,
242 attach.structPtr,
243 );
244 } else {
245 let heap = exports.__heap_base.value as number;
246 state = exports.input_init(heap, escLatency, 0, 0, 0);
247 buffer = (heap + size + 7) & ~7;
248 }
249
250 return {
251 memory,
252 state,
253 buffer,
254 scan: exports.input_scan,
255 count: exports.input_count,
256 event: exports.input_event,
257 delay: exports.input_delay,
258 };
259}
260
261// Must match SCAN_BUFFER_SIZE in input.c — the maximum bytes input_scan()
262// can accept in a single call.
263export const SCAN_BUFFER_SIZE = 4096;