anvilsign in

collin/anvil

main / crates / anvil-web / assets / wterm / core / wasm-bridge.js
1import { WASM_BASE64 } from "./wasm-inline.js";
2function decodeBase64(base64) {
3 const binary = atob(base64);
4 const bytes = new Uint8Array(binary.length);
5 for (let i = 0; i < binary.length; i++)
6 bytes[i] = binary.charCodeAt(i);
7 return bytes.buffer;
8}
9export class WasmBridge {
10 get dv() {
11 if (this._dvBuffer !== this.memory.buffer) {
12 this._dvBuffer = this.memory.buffer;
13 this._dv = new DataView(this.memory.buffer);
14 }
15 return this._dv;
16 }
17 constructor(instance) {
18 this.gridPtr = 0;
19 this.dirtyPtr = 0;
20 this.writeBufferPtr = 0;
21 this.cellSize = 12;
22 this.maxCols = 256;
23 this.encoder = new TextEncoder();
24 this.decoder = new TextDecoder();
25 this._dvBuffer = null;
26 this.linkCache = new Map();
27 this.exports = instance.exports;
28 this.memory = this.exports.memory;
29 }
30 static async load(url) {
31 let bytes;
32 if (url) {
33 const response = await fetch(url);
34 if (!response.ok) {
35 throw new Error(`[wterm] Failed to load WASM from ${url}: ${response.status} ${response.statusText}`);
36 }
37 bytes = await response.arrayBuffer();
38 }
39 else {
40 bytes = decodeBase64(WASM_BASE64);
41 }
42 const { instance } = await WebAssembly.instantiate(bytes);
43 return new WasmBridge(instance);
44 }
45 init(cols, rows) {
46 this.exports.init(cols, rows);
47 this.linkCache.clear();
48 this._updatePointers();
49 }
50 _updatePointers() {
51 this.gridPtr = this.exports.getGridPtr();
52 this.dirtyPtr = this.exports.getDirtyPtr();
53 this.writeBufferPtr = this.exports.getWriteBuffer();
54 this.cellSize = this.exports.getCellSize();
55 this.maxCols = this.exports.getMaxCols();
56 }
57 writeString(str, afterChunk) {
58 const encoded = this.encoder.encode(str);
59 this.writeRaw(encoded, afterChunk);
60 }
61 writeRaw(data, afterChunk) {
62 let offset = 0;
63 while (offset < data.length) {
64 const chunk = Math.min(data.length - offset, 8192);
65 const buf = new Uint8Array(this.memory.buffer, this.writeBufferPtr, 8192);
66 buf.set(data.subarray(offset, offset + chunk));
67 this.exports.writeBytes(chunk);
68 offset += chunk;
69 afterChunk?.();
70 }
71 }
72 getCell(row, col) {
73 const offset = this.gridPtr + (row * this.maxCols + col) * this.cellSize;
74 const dv = this.dv;
75 const result = {
76 char: dv.getUint32(offset, true),
77 fg: dv.getUint16(offset + 4, true),
78 bg: dv.getUint16(offset + 6, true),
79 flags: dv.getUint8(offset + 8),
80 width: dv.getUint8(offset + 9),
81 };
82 Object.assign(result, this._readLink(dv.getUint16(offset + 10, true)));
83 return result;
84 }
85 isDirtyRow(row) {
86 return new Uint8Array(this.memory.buffer, this.dirtyPtr, 256)[row] !== 0;
87 }
88 clearDirty() {
89 this.exports.clearDirty();
90 }
91 getCursor() {
92 return {
93 row: this.exports.getCursorRow(),
94 col: this.exports.getCursorCol(),
95 visible: this.exports.getCursorVisible() !== 0,
96 };
97 }
98 getCols() {
99 return this.exports.getCols();
100 }
101 getRows() {
102 return this.exports.getRows();
103 }
104 cursorKeysApp() {
105 return this.exports.getCursorKeysApp() !== 0;
106 }
107 bracketedPaste() {
108 return this.exports.getBracketedPaste() !== 0;
109 }
110 usingAltScreen() {
111 return this.exports.getUsingAltScreen() !== 0;
112 }
113 mouseTracking() {
114 const mode = this.exports.getMouseTracking();
115 return mode === 1000 || mode === 1002 ? mode : 0;
116 }
117 mouseSgr() {
118 return this.exports.getMouseSgr() !== 0;
119 }
120 focusEvents() {
121 return this.exports.getFocusEvents() !== 0;
122 }
123 synchronizedOutput() {
124 return this.exports.getSynchronizedOutput() !== 0;
125 }
126 synchronizedOutputGeneration() {
127 return this.exports.getSynchronizedOutputGeneration();
128 }
129 getTitle() {
130 if (this.exports.getTitleChanged() === 0)
131 return null;
132 const ptr = this.exports.getTitlePtr();
133 const len = this.exports.getTitleLen();
134 const bytes = new Uint8Array(this.memory.buffer, ptr, len);
135 return this.decoder.decode(bytes);
136 }
137 getResponse() {
138 const len = this.exports.getResponseLen();
139 if (len === 0)
140 return null;
141 const ptr = this.exports.getResponsePtr();
142 const bytes = new Uint8Array(this.memory.buffer, ptr, len);
143 const str = this.decoder.decode(bytes);
144 this.exports.clearResponse();
145 return str;
146 }
147 getResourceState() {
148 const capacity = this.exports.getHyperlinkCapacity?.();
149 const used = this.exports.getHyperlinkCount?.();
150 const rejected = this.exports.getHyperlinkRejectedCount?.();
151 if (capacity === undefined ||
152 used === undefined ||
153 rejected === undefined) {
154 return {};
155 }
156 return {
157 hyperlinks: {
158 capacity,
159 used,
160 rejected,
161 saturated: used >= capacity,
162 },
163 };
164 }
165 getScrollbackCount() {
166 return this.exports.getScrollbackCount();
167 }
168 getScrollbackDiscardedCount() {
169 return this.exports.getScrollbackDiscardedCount();
170 }
171 getScrollbackCell(offset, col) {
172 const ptr = this.exports.getScrollbackLine(offset);
173 const off = ptr + col * this.cellSize;
174 const dv = this.dv;
175 const result = {
176 char: dv.getUint32(off, true),
177 fg: dv.getUint16(off + 4, true),
178 bg: dv.getUint16(off + 6, true),
179 flags: dv.getUint8(off + 8),
180 width: dv.getUint8(off + 9),
181 };
182 Object.assign(result, this._readLink(dv.getUint16(off + 10, true)));
183 return result;
184 }
185 getScrollbackLineLen(offset) {
186 return this.exports.getScrollbackLineLen(offset);
187 }
188 getUnhandledSequences() {
189 const count = this.exports.getDebugLogCount();
190 if (count === 0)
191 return [];
192 const ptr = this.exports.getDebugLogPtr();
193 const entrySize = this.exports.getDebugLogEntrySize();
194 const maxEntries = this.exports.getDebugLogMax();
195 const total = Math.min(count, maxEntries);
196 const dv = new DataView(this.memory.buffer);
197 const entries = [];
198 const startIdx = count >= maxEntries ? count % maxEntries : 0;
199 for (let i = 0; i < total; i++) {
200 const idx = (startIdx + i) % maxEntries;
201 const off = ptr + idx * entrySize;
202 const finalByte = dv.getUint8(off);
203 if (finalByte === 0)
204 continue;
205 const privateByte = dv.getUint8(off + 1);
206 const paramCount = dv.getUint8(off + 2);
207 const params = [];
208 for (let p = 0; p < Math.min(paramCount, 4); p++) {
209 params.push(dv.getUint16(off + 4 + p * 2, true));
210 }
211 entries.push({
212 final: String.fromCharCode(finalByte),
213 private: privateByte ? String.fromCharCode(privateByte) : "",
214 paramCount,
215 params,
216 });
217 }
218 return entries;
219 }
220 resize(cols, rows) {
221 this.exports.resizeTerminal(cols, rows);
222 this._updatePointers();
223 }
224 _readLink(index) {
225 if (index === 0)
226 return undefined;
227 const cached = this.linkCache.get(index);
228 if (cached)
229 return cached;
230 const uriLen = this.exports.getLinkUriLen(index);
231 if (uriLen === 0)
232 return undefined;
233 const uri = this.decoder.decode(new Uint8Array(this.memory.buffer, this.exports.getLinkUriPtr(index), uriLen));
234 const idLen = this.exports.getLinkIdLen(index);
235 const linkId = idLen === 0
236 ? undefined
237 : this.decoder.decode(new Uint8Array(this.memory.buffer, this.exports.getLinkIdPtr(index), idLen));
238 const value = {
239 linkUri: uri,
240 linkId,
241 linkKey: linkId ? `e\0${linkId}\0${uri}` : `b\0${index}`,
242 };
243 this.linkCache.set(index, value);
244 return value;
245 }
246}