This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

lib-vt: wasm convenience functions and a simple example (#9309)

This adds a set of Wasm convenience functions to ease memory management.
These are all prefixed with `ghostty_wasm` and are documented as part of
the standard Doxygen docs.

I also added a very simple single-page HTML example that demonstrates
how to use the Wasm module for key encoding.

This also adds a bunch of safety checks to the C API to verify that
valid values are actually passed to the function. This is an easy to hit
bug.

**AI disclosure:** The example is AI-written with Amp. I read through
all the code and understand it but I can't claim there isn't a better
way, I'm far from a JS expert. It is simple and works currently though.
Happy to see improvements if anyone wants to contribute.

+1027 -2
+1
AGENTS.md
··· 20 20 ## libghostty-vt 21 21 22 22 - Build: `zig build lib-vt` 23 + - Build Wasm Module: `zig build lib-vt -Dtarget=wasm32-freestanding` 23 24 - Test: `zig build test-lib-vt` 24 25 - Test filter: `zig build test-lib-vt -Dtest-filter=<test name>` 25 26 - When working on libghostty-vt, do not build the full app.
+10
Doxyfile
··· 18 18 REFERENCED_BY_RELATION = YES 19 19 20 20 #--------------------------------------------------------------------------- 21 + # Preprocessor 22 + #--------------------------------------------------------------------------- 23 + 24 + # Enable preprocessing to handle #ifdef guards 25 + ENABLE_PREPROCESSING = YES 26 + MACRO_EXPANSION = YES 27 + EXPAND_ONLY_PREDEF = YES 28 + PREDEFINED = __wasm__ 29 + 30 + #--------------------------------------------------------------------------- 21 31 # C API Optimization 22 32 #--------------------------------------------------------------------------- 23 33
+76
example/wasm-key-encode/README.md
··· 1 + # WebAssembly Key Encoder Example 2 + 3 + This example demonstrates how to use the Ghostty VT library from WebAssembly to encode key events into terminal escape sequences. 4 + 5 + ## What It Does 6 + 7 + The example demonstrates using the Ghostty VT library from WebAssembly to encode key events: 8 + 9 + 1. Loads the `ghostty-vt.wasm` module 10 + 2. Creates a key encoder with Kitty keyboard protocol support 11 + 3. Creates a key event for left ctrl release 12 + 4. Queries the required buffer size (optional) 13 + 5. Encodes the event into a terminal escape sequence 14 + 6. Displays the result in both hexadecimal and string format 15 + 16 + ## Building 17 + 18 + First, build the WebAssembly module: 19 + 20 + ```bash 21 + zig build lib-vt -Dtarget=wasm32-freestanding -Doptimize=ReleaseSmall 22 + ``` 23 + 24 + This will create `zig-out/bin/ghostty-vt.wasm`. 25 + 26 + ## Running 27 + 28 + **Important:** You must serve this via HTTP, not open it as a file directly. Browsers block loading WASM files from `file://` URLs. 29 + 30 + From the **root of the ghostty repository**, serve with a local HTTP server: 31 + 32 + ```bash 33 + # Using Python (recommended) 34 + python3 -m http.server 8000 35 + 36 + # Or using Node.js 37 + npx serve . 38 + 39 + # Or using PHP 40 + php -S localhost:8000 41 + ``` 42 + 43 + Then open your browser to: 44 + 45 + ``` 46 + http://localhost:8000/example/wasm-key-encode/ 47 + ``` 48 + 49 + Click "Run Example" to see the key encoding in action. 50 + 51 + ## Expected Output 52 + 53 + ``` 54 + Encoding event: left ctrl release with all Kitty flags enabled 55 + Required buffer size: 12 bytes 56 + Encoded 12 bytes 57 + Hex: 1b 5b 35 37 3a 33 3b 32 3a 33 75 58 + String: \x1b[57:3;2:3u 59 + ``` 60 + 61 + ## Notes 62 + 63 + - The example uses the convenience allocator functions exported by the wasm module 64 + - Error handling is included to demonstrate proper usage patterns 65 + - The encoded sequence `\x1b[57:3;2:3u` is a Kitty keyboard protocol sequence for left ctrl release with all features enabled 66 + - The `env.log` function must be provided by the host environment for logging support 67 + 68 + ## Current Limitations 69 + 70 + The current C API is verbose when called from WebAssembly because: 71 + 72 + - Functions use output pointers requiring manual memory allocation in JavaScript 73 + - Options must be set via pointers to values 74 + - Buffer sizes require pointer parameters 75 + 76 + See `WASM_API_PLAN.md` for proposed improvements to make the API more wasm-friendly.
+687
example/wasm-key-encode/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Ghostty VT Key Encoder - WebAssembly Example</title> 7 + <style> 8 + body { 9 + font-family: system-ui, -apple-system, sans-serif; 10 + max-width: 800px; 11 + margin: 40px auto; 12 + padding: 0 20px; 13 + line-height: 1.6; 14 + } 15 + h1 { 16 + color: #333; 17 + } 18 + .output { 19 + background: #f5f5f5; 20 + border: 1px solid #ddd; 21 + border-radius: 4px; 22 + padding: 15px; 23 + margin: 20px 0; 24 + font-family: 'Courier New', monospace; 25 + white-space: pre-wrap; 26 + word-break: break-all; 27 + } 28 + .error { 29 + background: #fee; 30 + border-color: #faa; 31 + color: #c00; 32 + } 33 + button { 34 + background: #0066cc; 35 + color: white; 36 + border: none; 37 + padding: 10px 20px; 38 + border-radius: 4px; 39 + cursor: pointer; 40 + font-size: 14px; 41 + } 42 + button:hover { 43 + background: #0052a3; 44 + } 45 + button:disabled { 46 + background: #ccc; 47 + cursor: not-allowed; 48 + } 49 + .key-input { 50 + width: 100%; 51 + padding: 15px; 52 + font-size: 16px; 53 + border: 2px solid #0066cc; 54 + border-radius: 4px; 55 + margin: 20px 0; 56 + box-sizing: border-box; 57 + } 58 + .key-input:focus { 59 + outline: none; 60 + border-color: #0052a3; 61 + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); 62 + } 63 + .status { 64 + color: #666; 65 + font-size: 14px; 66 + margin: 10px 0; 67 + } 68 + .controls { 69 + background: #f9f9f9; 70 + border: 1px solid #ddd; 71 + border-radius: 4px; 72 + padding: 15px; 73 + margin: 20px 0; 74 + } 75 + .controls h3 { 76 + margin-top: 0; 77 + margin-bottom: 10px; 78 + font-size: 16px; 79 + } 80 + .checkbox-group { 81 + display: flex; 82 + flex-wrap: wrap; 83 + gap: 15px; 84 + } 85 + .checkbox-group label { 86 + display: flex; 87 + align-items: center; 88 + gap: 5px; 89 + cursor: pointer; 90 + } 91 + .checkbox-group input[type="checkbox"] { 92 + cursor: pointer; 93 + } 94 + .radio-group { 95 + display: flex; 96 + gap: 15px; 97 + } 98 + .radio-group label { 99 + display: flex; 100 + align-items: center; 101 + gap: 5px; 102 + cursor: pointer; 103 + } 104 + .radio-group input[type="radio"] { 105 + cursor: pointer; 106 + } 107 + .warning { 108 + background: #fff3cd; 109 + border: 1px solid #ffc107; 110 + border-radius: 4px; 111 + padding: 15px; 112 + margin: 20px 0; 113 + color: #856404; 114 + } 115 + .warning strong { 116 + display: block; 117 + margin-bottom: 5px; 118 + } 119 + </style> 120 + </head> 121 + <body> 122 + <h1>Ghostty VT Key Encoder - WebAssembly Example</h1> 123 + <p>This example demonstrates encoding key events into terminal escape sequences using the Ghostty VT WebAssembly module.</p> 124 + 125 + <div class="warning"> 126 + <strong>⚠️ Warning:</strong> 127 + This is an example of the libghostty-vt WebAssembly API. The JavaScript 128 + keyboard event mapping to the libghostty-vt API may not be perfect 129 + and may result in encoding inaccuracies for certain keys or layouts. 130 + Do not use this as a key encoding reference. 131 + </div> 132 + 133 + <div class="status" id="status">Loading WebAssembly module...</div> 134 + 135 + <div class="controls"> 136 + <h3>Key Action</h3> 137 + <div class="radio-group"> 138 + <label><input type="radio" name="action" value="1" checked> Press</label> 139 + <label><input type="radio" name="action" value="0"> Release</label> 140 + <label><input type="radio" name="action" value="2"> Repeat</label> 141 + </div> 142 + </div> 143 + 144 + <div class="controls"> 145 + <h3>Kitty Keyboard Protocol Flags</h3> 146 + <div class="checkbox-group"> 147 + <label><input type="checkbox" id="flag_disambiguate" checked> Disambiguate</label> 148 + <label><input type="checkbox" id="flag_report_events" checked> Report Events</label> 149 + <label><input type="checkbox" id="flag_report_alternates" checked> Report Alternates</label> 150 + <label><input type="checkbox" id="flag_report_all_as_escapes" checked> Report All As Escapes</label> 151 + <label><input type="checkbox" id="flag_report_text" checked> Report Text</label> 152 + </div> 153 + </div> 154 + 155 + <input type="text" class="key-input" id="keyInput" placeholder="Focus here and press any key combination (e.g., Ctrl+A, Shift+Enter)..." disabled> 156 + 157 + <div id="output" class="output">Waiting for key events...</div> 158 + 159 + <p><strong>Note:</strong> This example must be served via HTTP (not opened directly as a file). See the README for instructions.</p> 160 + 161 + <script> 162 + let wasmInstance = null; 163 + let wasmMemory = null; 164 + let encoderPtr = null; 165 + let lastKeyEvent = null; 166 + 167 + async function loadWasm() { 168 + try { 169 + // Load the wasm module - adjust path as needed 170 + const response = await fetch('../../zig-out/bin/ghostty-vt.wasm'); 171 + const wasmBytes = await response.arrayBuffer(); 172 + 173 + // Instantiate the wasm module 174 + const wasmModule = await WebAssembly.instantiate(wasmBytes, { 175 + env: { 176 + // Logging function for wasm module 177 + log: (ptr, len) => { 178 + const bytes = new Uint8Array(wasmModule.instance.exports.memory.buffer, ptr, len); 179 + const text = new TextDecoder().decode(bytes); 180 + console.log('[wasm]', text); 181 + } 182 + } 183 + }); 184 + 185 + wasmInstance = wasmModule.instance; 186 + wasmMemory = wasmInstance.exports.memory; 187 + 188 + return true; 189 + } catch (e) { 190 + console.error('Failed to load WASM:', e); 191 + if (window.location.protocol === 'file:') { 192 + throw new Error('Cannot load WASM from file:// protocol. Please serve via HTTP (see README)'); 193 + } 194 + return false; 195 + } 196 + } 197 + 198 + function getBuffer() { 199 + return wasmMemory.buffer; 200 + } 201 + 202 + function formatHex(bytes) { 203 + return Array.from(bytes) 204 + .map(b => b.toString(16).padStart(2, '0')) 205 + .join(' '); 206 + } 207 + 208 + function formatString(bytes) { 209 + let result = ''; 210 + for (let i = 0; i < bytes.length; i++) { 211 + if (bytes[i] === 0x1b) { 212 + result += '\\x1b'; 213 + } else { 214 + result += String.fromCharCode(bytes[i]); 215 + } 216 + } 217 + return result; 218 + } 219 + 220 + // Map W3C KeyboardEvent.code values to Ghostty key codes 221 + // Based on include/ghostty/vt/key/event.h 222 + const keyCodeMap = { 223 + // Writing System Keys 224 + 'Backquote': 1, // GHOSTTY_KEY_BACKQUOTE 225 + 'Backslash': 2, // GHOSTTY_KEY_BACKSLASH 226 + 'BracketLeft': 3, // GHOSTTY_KEY_BRACKET_LEFT 227 + 'BracketRight': 4, // GHOSTTY_KEY_BRACKET_RIGHT 228 + 'Comma': 5, // GHOSTTY_KEY_COMMA 229 + 'Digit0': 6, // GHOSTTY_KEY_DIGIT_0 230 + 'Digit1': 7, // GHOSTTY_KEY_DIGIT_1 231 + 'Digit2': 8, // GHOSTTY_KEY_DIGIT_2 232 + 'Digit3': 9, // GHOSTTY_KEY_DIGIT_3 233 + 'Digit4': 10, // GHOSTTY_KEY_DIGIT_4 234 + 'Digit5': 11, // GHOSTTY_KEY_DIGIT_5 235 + 'Digit6': 12, // GHOSTTY_KEY_DIGIT_6 236 + 'Digit7': 13, // GHOSTTY_KEY_DIGIT_7 237 + 'Digit8': 14, // GHOSTTY_KEY_DIGIT_8 238 + 'Digit9': 15, // GHOSTTY_KEY_DIGIT_9 239 + 'Equal': 16, // GHOSTTY_KEY_EQUAL 240 + 'IntlBackslash': 17, // GHOSTTY_KEY_INTL_BACKSLASH 241 + 'IntlRo': 18, // GHOSTTY_KEY_INTL_RO 242 + 'IntlYen': 19, // GHOSTTY_KEY_INTL_YEN 243 + 'KeyA': 20, // GHOSTTY_KEY_A 244 + 'KeyB': 21, // GHOSTTY_KEY_B 245 + 'KeyC': 22, // GHOSTTY_KEY_C 246 + 'KeyD': 23, // GHOSTTY_KEY_D 247 + 'KeyE': 24, // GHOSTTY_KEY_E 248 + 'KeyF': 25, // GHOSTTY_KEY_F 249 + 'KeyG': 26, // GHOSTTY_KEY_G 250 + 'KeyH': 27, // GHOSTTY_KEY_H 251 + 'KeyI': 28, // GHOSTTY_KEY_I 252 + 'KeyJ': 29, // GHOSTTY_KEY_J 253 + 'KeyK': 30, // GHOSTTY_KEY_K 254 + 'KeyL': 31, // GHOSTTY_KEY_L 255 + 'KeyM': 32, // GHOSTTY_KEY_M 256 + 'KeyN': 33, // GHOSTTY_KEY_N 257 + 'KeyO': 34, // GHOSTTY_KEY_O 258 + 'KeyP': 35, // GHOSTTY_KEY_P 259 + 'KeyQ': 36, // GHOSTTY_KEY_Q 260 + 'KeyR': 37, // GHOSTTY_KEY_R 261 + 'KeyS': 38, // GHOSTTY_KEY_S 262 + 'KeyT': 39, // GHOSTTY_KEY_T 263 + 'KeyU': 40, // GHOSTTY_KEY_U 264 + 'KeyV': 41, // GHOSTTY_KEY_V 265 + 'KeyW': 42, // GHOSTTY_KEY_W 266 + 'KeyX': 43, // GHOSTTY_KEY_X 267 + 'KeyY': 44, // GHOSTTY_KEY_Y 268 + 'KeyZ': 45, // GHOSTTY_KEY_Z 269 + 'Minus': 46, // GHOSTTY_KEY_MINUS 270 + 'Period': 47, // GHOSTTY_KEY_PERIOD 271 + 'Quote': 48, // GHOSTTY_KEY_QUOTE 272 + 'Semicolon': 49, // GHOSTTY_KEY_SEMICOLON 273 + 'Slash': 50, // GHOSTTY_KEY_SLASH 274 + 275 + // Functional Keys 276 + 'AltLeft': 51, // GHOSTTY_KEY_ALT_LEFT 277 + 'AltRight': 52, // GHOSTTY_KEY_ALT_RIGHT 278 + 'Backspace': 53, // GHOSTTY_KEY_BACKSPACE 279 + 'CapsLock': 54, // GHOSTTY_KEY_CAPS_LOCK 280 + 'ContextMenu': 55, // GHOSTTY_KEY_CONTEXT_MENU 281 + 'ControlLeft': 56, // GHOSTTY_KEY_CONTROL_LEFT 282 + 'ControlRight': 57, // GHOSTTY_KEY_CONTROL_RIGHT 283 + 'Enter': 58, // GHOSTTY_KEY_ENTER 284 + 'MetaLeft': 59, // GHOSTTY_KEY_META_LEFT 285 + 'MetaRight': 60, // GHOSTTY_KEY_META_RIGHT 286 + 'ShiftLeft': 61, // GHOSTTY_KEY_SHIFT_LEFT 287 + 'ShiftRight': 62, // GHOSTTY_KEY_SHIFT_RIGHT 288 + 'Space': 63, // GHOSTTY_KEY_SPACE 289 + 'Tab': 64, // GHOSTTY_KEY_TAB 290 + 'Convert': 65, // GHOSTTY_KEY_CONVERT 291 + 'KanaMode': 66, // GHOSTTY_KEY_KANA_MODE 292 + 'NonConvert': 67, // GHOSTTY_KEY_NON_CONVERT 293 + 294 + // Control Pad Section 295 + 'Delete': 68, // GHOSTTY_KEY_DELETE 296 + 'End': 69, // GHOSTTY_KEY_END 297 + 'Help': 70, // GHOSTTY_KEY_HELP 298 + 'Home': 71, // GHOSTTY_KEY_HOME 299 + 'Insert': 72, // GHOSTTY_KEY_INSERT 300 + 'PageDown': 73, // GHOSTTY_KEY_PAGE_DOWN 301 + 'PageUp': 74, // GHOSTTY_KEY_PAGE_UP 302 + 303 + // Arrow Pad Section 304 + 'ArrowDown': 75, // GHOSTTY_KEY_ARROW_DOWN 305 + 'ArrowLeft': 76, // GHOSTTY_KEY_ARROW_LEFT 306 + 'ArrowRight': 77, // GHOSTTY_KEY_ARROW_RIGHT 307 + 'ArrowUp': 78, // GHOSTTY_KEY_ARROW_UP 308 + 309 + // Numpad Section 310 + 'NumLock': 79, // GHOSTTY_KEY_NUM_LOCK 311 + 'Numpad0': 80, // GHOSTTY_KEY_NUMPAD_0 312 + 'Numpad1': 81, // GHOSTTY_KEY_NUMPAD_1 313 + 'Numpad2': 82, // GHOSTTY_KEY_NUMPAD_2 314 + 'Numpad3': 83, // GHOSTTY_KEY_NUMPAD_3 315 + 'Numpad4': 84, // GHOSTTY_KEY_NUMPAD_4 316 + 'Numpad5': 85, // GHOSTTY_KEY_NUMPAD_5 317 + 'Numpad6': 86, // GHOSTTY_KEY_NUMPAD_6 318 + 'Numpad7': 87, // GHOSTTY_KEY_NUMPAD_7 319 + 'Numpad8': 88, // GHOSTTY_KEY_NUMPAD_8 320 + 'Numpad9': 89, // GHOSTTY_KEY_NUMPAD_9 321 + 'NumpadAdd': 90, // GHOSTTY_KEY_NUMPAD_ADD 322 + 'NumpadBackspace': 91, // GHOSTTY_KEY_NUMPAD_BACKSPACE 323 + 'NumpadClear': 92, // GHOSTTY_KEY_NUMPAD_CLEAR 324 + 'NumpadClearEntry': 93, // GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY 325 + 'NumpadComma': 94, // GHOSTTY_KEY_NUMPAD_COMMA 326 + 'NumpadDecimal': 95, // GHOSTTY_KEY_NUMPAD_DECIMAL 327 + 'NumpadDivide': 96, // GHOSTTY_KEY_NUMPAD_DIVIDE 328 + 'NumpadEnter': 97, // GHOSTTY_KEY_NUMPAD_ENTER 329 + 'NumpadEqual': 98, // GHOSTTY_KEY_NUMPAD_EQUAL 330 + 'NumpadMemoryAdd': 99, // GHOSTTY_KEY_NUMPAD_MEMORY_ADD 331 + 'NumpadMemoryClear': 100,// GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR 332 + 'NumpadMemoryRecall': 101,// GHOSTTY_KEY_NUMPAD_MEMORY_RECALL 333 + 'NumpadMemoryStore': 102,// GHOSTTY_KEY_NUMPAD_MEMORY_STORE 334 + 'NumpadMemorySubtract': 103,// GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT 335 + 'NumpadMultiply': 104, // GHOSTTY_KEY_NUMPAD_MULTIPLY 336 + 'NumpadParenLeft': 105, // GHOSTTY_KEY_NUMPAD_PAREN_LEFT 337 + 'NumpadParenRight': 106, // GHOSTTY_KEY_NUMPAD_PAREN_RIGHT 338 + 'NumpadSubtract': 107, // GHOSTTY_KEY_NUMPAD_SUBTRACT 339 + 'NumpadSeparator': 108, // GHOSTTY_KEY_NUMPAD_SEPARATOR 340 + 'NumpadUp': 109, // GHOSTTY_KEY_NUMPAD_UP 341 + 'NumpadDown': 110, // GHOSTTY_KEY_NUMPAD_DOWN 342 + 'NumpadRight': 111, // GHOSTTY_KEY_NUMPAD_RIGHT 343 + 'NumpadLeft': 112, // GHOSTTY_KEY_NUMPAD_LEFT 344 + 'NumpadBegin': 113, // GHOSTTY_KEY_NUMPAD_BEGIN 345 + 'NumpadHome': 114, // GHOSTTY_KEY_NUMPAD_HOME 346 + 'NumpadEnd': 115, // GHOSTTY_KEY_NUMPAD_END 347 + 'NumpadInsert': 116, // GHOSTTY_KEY_NUMPAD_INSERT 348 + 'NumpadDelete': 117, // GHOSTTY_KEY_NUMPAD_DELETE 349 + 'NumpadPageUp': 118, // GHOSTTY_KEY_NUMPAD_PAGE_UP 350 + 'NumpadPageDown': 119, // GHOSTTY_KEY_NUMPAD_PAGE_DOWN 351 + 352 + // Function Section 353 + 'Escape': 120, // GHOSTTY_KEY_ESCAPE 354 + 'F1': 121, // GHOSTTY_KEY_F1 355 + 'F2': 122, // GHOSTTY_KEY_F2 356 + 'F3': 123, // GHOSTTY_KEY_F3 357 + 'F4': 124, // GHOSTTY_KEY_F4 358 + 'F5': 125, // GHOSTTY_KEY_F5 359 + 'F6': 126, // GHOSTTY_KEY_F6 360 + 'F7': 127, // GHOSTTY_KEY_F7 361 + 'F8': 128, // GHOSTTY_KEY_F8 362 + 'F9': 129, // GHOSTTY_KEY_F9 363 + 'F10': 130, // GHOSTTY_KEY_F10 364 + 'F11': 131, // GHOSTTY_KEY_F11 365 + 'F12': 132, // GHOSTTY_KEY_F12 366 + 'F13': 133, // GHOSTTY_KEY_F13 367 + 'F14': 134, // GHOSTTY_KEY_F14 368 + 'F15': 135, // GHOSTTY_KEY_F15 369 + 'F16': 136, // GHOSTTY_KEY_F16 370 + 'F17': 137, // GHOSTTY_KEY_F17 371 + 'F18': 138, // GHOSTTY_KEY_F18 372 + 'F19': 139, // GHOSTTY_KEY_F19 373 + 'F20': 140, // GHOSTTY_KEY_F20 374 + 'F21': 141, // GHOSTTY_KEY_F21 375 + 'F22': 142, // GHOSTTY_KEY_F22 376 + 'F23': 143, // GHOSTTY_KEY_F23 377 + 'F24': 144, // GHOSTTY_KEY_F24 378 + 'F25': 145, // GHOSTTY_KEY_F25 379 + 'Fn': 146, // GHOSTTY_KEY_FN 380 + 'FnLock': 147, // GHOSTTY_KEY_FN_LOCK 381 + 'PrintScreen': 148, // GHOSTTY_KEY_PRINT_SCREEN 382 + 'ScrollLock': 149, // GHOSTTY_KEY_SCROLL_LOCK 383 + 'Pause': 150, // GHOSTTY_KEY_PAUSE 384 + 385 + // Media Keys 386 + 'BrowserBack': 151, // GHOSTTY_KEY_BROWSER_BACK 387 + 'BrowserFavorites': 152, // GHOSTTY_KEY_BROWSER_FAVORITES 388 + 'BrowserForward': 153, // GHOSTTY_KEY_BROWSER_FORWARD 389 + 'BrowserHome': 154, // GHOSTTY_KEY_BROWSER_HOME 390 + 'BrowserRefresh': 155, // GHOSTTY_KEY_BROWSER_REFRESH 391 + 'BrowserSearch': 156, // GHOSTTY_KEY_BROWSER_SEARCH 392 + 'BrowserStop': 157, // GHOSTTY_KEY_BROWSER_STOP 393 + 'Eject': 158, // GHOSTTY_KEY_EJECT 394 + 'LaunchApp1': 159, // GHOSTTY_KEY_LAUNCH_APP_1 395 + 'LaunchApp2': 160, // GHOSTTY_KEY_LAUNCH_APP_2 396 + 'LaunchMail': 161, // GHOSTTY_KEY_LAUNCH_MAIL 397 + 'MediaPlayPause': 162, // GHOSTTY_KEY_MEDIA_PLAY_PAUSE 398 + 'MediaSelect': 163, // GHOSTTY_KEY_MEDIA_SELECT 399 + 'MediaStop': 164, // GHOSTTY_KEY_MEDIA_STOP 400 + 'MediaTrackNext': 165, // GHOSTTY_KEY_MEDIA_TRACK_NEXT 401 + 'MediaTrackPrevious': 166,// GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS 402 + 'Power': 167, // GHOSTTY_KEY_POWER 403 + 'Sleep': 168, // GHOSTTY_KEY_SLEEP 404 + 'AudioVolumeDown': 169, // GHOSTTY_KEY_AUDIO_VOLUME_DOWN 405 + 'AudioVolumeMute': 170, // GHOSTTY_KEY_AUDIO_VOLUME_MUTE 406 + 'AudioVolumeUp': 171, // GHOSTTY_KEY_AUDIO_VOLUME_UP 407 + 'WakeUp': 172, // GHOSTTY_KEY_WAKE_UP 408 + 409 + // Legacy, Non-standard, and Special Keys 410 + 'Copy': 173, // GHOSTTY_KEY_COPY 411 + 'Cut': 174, // GHOSTTY_KEY_CUT 412 + 'Paste': 175, // GHOSTTY_KEY_PASTE 413 + }; 414 + 415 + function encodeKeyEvent(event) { 416 + if (!encoderPtr) return null; 417 + 418 + try { 419 + // Create key event 420 + const eventPtrPtr = wasmInstance.exports.ghostty_wasm_alloc_opaque(); 421 + const result = wasmInstance.exports.ghostty_key_event_new(0, eventPtrPtr); 422 + 423 + if (result !== 0) { 424 + throw new Error(`ghostty_key_event_new failed with result ${result}`); 425 + } 426 + 427 + const eventPtr = new DataView(getBuffer()).getUint32(eventPtrPtr, true); 428 + 429 + // Get action from radio buttons 430 + const actionRadio = document.querySelector('input[name="action"]:checked'); 431 + const action = parseInt(actionRadio.value); 432 + wasmInstance.exports.ghostty_key_event_set_action(eventPtr, action); 433 + 434 + // Map key code from event.code (preferred, layout-independent) 435 + let keyCode = keyCodeMap[event.code] || 0; // GHOSTTY_KEY_UNIDENTIFIED = 0 436 + wasmInstance.exports.ghostty_key_event_set_key(eventPtr, keyCode); 437 + 438 + // Map modifiers with left/right side information 439 + let mods = 0; 440 + if (event.shiftKey) { 441 + mods |= 0x01; // GHOSTTY_MODS_SHIFT 442 + if (event.code === 'ShiftRight') mods |= 0x40; // GHOSTTY_MODS_SHIFT_SIDE 443 + } 444 + if (event.ctrlKey) { 445 + mods |= 0x02; // GHOSTTY_MODS_CTRL 446 + if (event.code === 'ControlRight') mods |= 0x80; // GHOSTTY_MODS_CTRL_SIDE 447 + } 448 + if (event.altKey) { 449 + mods |= 0x04; // GHOSTTY_MODS_ALT 450 + if (event.code === 'AltRight') mods |= 0x100; // GHOSTTY_MODS_ALT_SIDE 451 + } 452 + if (event.metaKey) { 453 + mods |= 0x08; // GHOSTTY_MODS_SUPER 454 + if (event.code === 'MetaRight') mods |= 0x200; // GHOSTTY_MODS_SUPER_SIDE 455 + } 456 + wasmInstance.exports.ghostty_key_event_set_mods(eventPtr, mods); 457 + 458 + // Set UTF-8 text from the key event (the actual character produced) 459 + if (event.key.length === 1) { 460 + const utf8Bytes = new TextEncoder().encode(event.key); 461 + const utf8Ptr = wasmInstance.exports.ghostty_wasm_alloc_buffer(utf8Bytes.length); 462 + new Uint8Array(getBuffer()).set(utf8Bytes, utf8Ptr); 463 + wasmInstance.exports.ghostty_key_event_set_utf8(eventPtr, utf8Ptr, utf8Bytes.length); 464 + } 465 + 466 + // Set unshifted codepoint 467 + const unshiftedCodepoint = getUnshiftedCodepoint(event); 468 + if (unshiftedCodepoint !== 0) { 469 + wasmInstance.exports.ghostty_key_event_set_unshifted_codepoint(eventPtr, unshiftedCodepoint); 470 + } 471 + 472 + // Encode the key event 473 + const requiredPtr = wasmInstance.exports.ghostty_wasm_alloc_usize(); 474 + wasmInstance.exports.ghostty_key_encoder_encode( 475 + encoderPtr, eventPtr, 0, 0, requiredPtr 476 + ); 477 + 478 + const required = new DataView(getBuffer()).getUint32(requiredPtr, true); 479 + 480 + const bufPtr = wasmInstance.exports.ghostty_wasm_alloc_buffer(required); 481 + const writtenPtr = wasmInstance.exports.ghostty_wasm_alloc_usize(); 482 + const encodeResult = wasmInstance.exports.ghostty_key_encoder_encode( 483 + encoderPtr, eventPtr, bufPtr, required, writtenPtr 484 + ); 485 + 486 + if (encodeResult !== 0) { 487 + return null; // No encoding for this key 488 + } 489 + 490 + const written = new DataView(getBuffer()).getUint32(writtenPtr, true); 491 + const encoded = new Uint8Array(getBuffer()).slice(bufPtr, bufPtr + written); 492 + 493 + return { 494 + bytes: Array.from(encoded), 495 + hex: formatHex(encoded), 496 + string: formatString(encoded) 497 + }; 498 + } catch (e) { 499 + console.error('Encoding error:', e); 500 + return null; 501 + } 502 + } 503 + 504 + function getUnshiftedCodepoint(event) { 505 + // Derive unshifted codepoint from the physical key code 506 + const code = event.code; 507 + 508 + // Letter keys (KeyA-KeyZ) -> lowercase letters 509 + if (code.startsWith('Key')) { 510 + const letter = code.substring(3).toLowerCase(); 511 + return letter.codePointAt(0); 512 + } 513 + 514 + // Digit keys (Digit0-Digit9) -> the digit itself 515 + if (code.startsWith('Digit')) { 516 + const digit = code.substring(5); 517 + return digit.codePointAt(0); 518 + } 519 + 520 + // Space 521 + if (code === 'Space') { 522 + return ' '.codePointAt(0); 523 + } 524 + 525 + // Symbol keys -> unshifted character 526 + const unshiftedSymbols = { 527 + 'Minus': '-', 'Equal': '=', 'BracketLeft': '[', 'BracketRight': ']', 528 + 'Backslash': '\\', 'Semicolon': ';', 'Quote': "'", 529 + 'Backquote': '`', 'Comma': ',', 'Period': '.', 'Slash': '/' 530 + }; 531 + 532 + if (unshiftedSymbols[code]) { 533 + return unshiftedSymbols[code].codePointAt(0); 534 + } 535 + 536 + // Fallback: use the produced character's codepoint 537 + if (event.key.length > 0) { 538 + return event.key.codePointAt(0) || 0; 539 + } 540 + 541 + return 0; 542 + } 543 + 544 + function getKittyFlags() { 545 + let flags = 0; 546 + if (document.getElementById('flag_disambiguate').checked) flags |= 0x01; 547 + if (document.getElementById('flag_report_events').checked) flags |= 0x02; 548 + if (document.getElementById('flag_report_alternates').checked) flags |= 0x04; 549 + if (document.getElementById('flag_report_all_as_escapes').checked) flags |= 0x08; 550 + if (document.getElementById('flag_report_text').checked) flags |= 0x10; 551 + return flags; 552 + } 553 + 554 + function updateEncoderFlags() { 555 + if (!encoderPtr) return; 556 + 557 + const flags = getKittyFlags(); 558 + const flagsPtr = wasmInstance.exports.ghostty_wasm_alloc_u8(); 559 + new DataView(getBuffer()).setUint8(flagsPtr, flags); 560 + wasmInstance.exports.ghostty_key_encoder_setopt( 561 + encoderPtr, 562 + 5, // GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS 563 + flagsPtr 564 + ); 565 + 566 + // Re-encode last key with new flags 567 + reencodeLastKey(); 568 + } 569 + 570 + function displayEncoding(event) { 571 + const outputDiv = document.getElementById('output'); 572 + const encoded = encodeKeyEvent(event); 573 + 574 + const actionRadio = document.querySelector('input[name="action"]:checked'); 575 + const actionName = actionRadio.parentElement.textContent.trim(); 576 + 577 + let output = `Action: ${actionName}\n`; 578 + output += `Key: ${event.key} (code: ${event.code})\n`; 579 + output += `Modifiers: `; 580 + const mods = []; 581 + if (event.shiftKey) mods.push('Shift'); 582 + if (event.ctrlKey) mods.push('Ctrl'); 583 + if (event.altKey) mods.push('Alt'); 584 + if (event.metaKey) mods.push('Meta'); 585 + output += mods.length ? mods.join('+') : 'none'; 586 + output += '\n'; 587 + 588 + // Show Kitty flags state 589 + const flags = []; 590 + if (document.getElementById('flag_disambiguate').checked) flags.push('Disambiguate'); 591 + if (document.getElementById('flag_report_events').checked) flags.push('Report Events'); 592 + if (document.getElementById('flag_report_alternates').checked) flags.push('Report Alternates'); 593 + if (document.getElementById('flag_report_all_as_escapes').checked) flags.push('Report All As Escapes'); 594 + if (document.getElementById('flag_report_text').checked) flags.push('Report Text'); 595 + output += 'Kitty Flags:\n'; 596 + if (flags.length) { 597 + flags.forEach(flag => output += ` - ${flag}\n`); 598 + } else { 599 + output += ' - none\n'; 600 + } 601 + output += '\n'; 602 + 603 + if (encoded) { 604 + output += `Encoded ${encoded.bytes.length} bytes\n`; 605 + output += `Hex: ${encoded.hex}\n`; 606 + output += `String: ${encoded.string}`; 607 + } else { 608 + output += 'No encoding for this key event'; 609 + } 610 + 611 + outputDiv.textContent = output; 612 + } 613 + 614 + function handleKeyEvent(event) { 615 + // Allow modifier keys to be pressed without clearing input 616 + // Only prevent default for keys we want to capture 617 + if (event.key !== 'Tab' && event.key !== 'F5') { 618 + event.preventDefault(); 619 + } 620 + 621 + lastKeyEvent = event; 622 + displayEncoding(event); 623 + } 624 + 625 + function reencodeLastKey() { 626 + if (lastKeyEvent) { 627 + displayEncoding(lastKeyEvent); 628 + } 629 + } 630 + 631 + async function init() { 632 + const statusDiv = document.getElementById('status'); 633 + const keyInput = document.getElementById('keyInput'); 634 + const outputDiv = document.getElementById('output'); 635 + 636 + try { 637 + statusDiv.textContent = 'Loading WebAssembly module...'; 638 + 639 + const loaded = await loadWasm(); 640 + if (!loaded) { 641 + throw new Error('Failed to load WebAssembly module'); 642 + } 643 + 644 + // Create key encoder 645 + const encoderPtrPtr = wasmInstance.exports.ghostty_wasm_alloc_opaque(); 646 + const result = wasmInstance.exports.ghostty_key_encoder_new(0, encoderPtrPtr); 647 + 648 + if (result !== 0) { 649 + throw new Error(`ghostty_key_encoder_new failed with result ${result}`); 650 + } 651 + 652 + encoderPtr = new DataView(getBuffer()).getUint32(encoderPtrPtr, true); 653 + 654 + // Set kitty flags based on checkboxes 655 + updateEncoderFlags(); 656 + 657 + statusDiv.textContent = ''; 658 + keyInput.disabled = false; 659 + keyInput.focus(); 660 + 661 + // Listen for key events (only keydown since action is selected manually) 662 + keyInput.addEventListener('keydown', handleKeyEvent); 663 + 664 + // Listen for flag changes 665 + const flagCheckboxes = document.querySelectorAll('.checkbox-group input[type="checkbox"]'); 666 + flagCheckboxes.forEach(checkbox => { 667 + checkbox.addEventListener('change', updateEncoderFlags); 668 + }); 669 + 670 + // Listen for action changes 671 + const actionRadios = document.querySelectorAll('input[name="action"]'); 672 + actionRadios.forEach(radio => { 673 + radio.addEventListener('change', reencodeLastKey); 674 + }); 675 + } catch (e) { 676 + statusDiv.textContent = `Error: ${e.message}`; 677 + statusDiv.style.color = '#c00'; 678 + outputDiv.className = 'output error'; 679 + outputDiv.textContent = `Error: ${e.message}\n\nStack trace:\n${e.stack}`; 680 + } 681 + } 682 + 683 + // Initialize on page load 684 + window.addEventListener('DOMContentLoaded', init); 685 + </script> 686 + </body> 687 + </html>
+2
include/ghostty/vt.h
··· 32 32 * - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences 33 33 * - @ref paste "Paste Utilities" - Validate paste data safety 34 34 * - @ref allocator "Memory Management" - Memory management and custom allocators 35 + * - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions 35 36 * 36 37 * @section examples_sec Examples 37 38 * ··· 69 70 #include <ghostty/vt/osc.h> 70 71 #include <ghostty/vt/key.h> 71 72 #include <ghostty/vt/paste.h> 73 + #include <ghostty/vt/wasm.h> 72 74 73 75 #ifdef __cplusplus 74 76 }
+141
include/ghostty/vt/wasm.h
··· 1 + /** 2 + * @file wasm.h 3 + * 4 + * WebAssembly utility functions for libghostty-vt. 5 + */ 6 + 7 + #ifndef GHOSTTY_VT_WASM_H 8 + #define GHOSTTY_VT_WASM_H 9 + 10 + #ifdef __wasm__ 11 + 12 + #include <stddef.h> 13 + #include <stdint.h> 14 + 15 + /** @defgroup wasm WebAssembly Utilities 16 + * 17 + * Convenience functions for allocating various types in WebAssembly builds. 18 + * **These are only available the libghostty-vt wasm module.** 19 + * 20 + * Ghostty relies on pointers to various types for ABI compatibility, and 21 + * creating those pointers in Wasm can be tedious. These functions provide 22 + * a purely additive set of utilities that simplify memory management in 23 + * Wasm environments without changing the core C library API. 24 + * 25 + * @note These functions always use the default allocator. If you need 26 + * custom allocation strategies, you should allocate types manually using 27 + * your custom allocator. This is a very rare use case in the WebAssembly 28 + * world so these are optimized for simplicity. 29 + * 30 + * ## Example Usage 31 + * 32 + * Here's a simple example of using the Wasm utilities with the key encoder: 33 + * 34 + * @code 35 + * const { exports } = wasmInstance; 36 + * const view = new DataView(wasmMemory.buffer); 37 + * 38 + * // Create key encoder 39 + * const encoderPtr = exports.ghostty_wasm_alloc_opaque(); 40 + * exports.ghostty_key_encoder_new(null, encoderPtr); 41 + * const encoder = view.getUint32(encoder, true); 42 + * 43 + * // Configure encoder with Kitty protocol flags 44 + * const flagsPtr = exports.ghostty_wasm_alloc_u8(); 45 + * view.setUint8(flagsPtr, 0x1F); 46 + * exports.ghostty_key_encoder_setopt(encoder, 5, flagsPtr); 47 + * 48 + * // Allocate output buffer and size pointer 49 + * const bufferSize = 32; 50 + * const bufPtr = exports.ghostty_wasm_alloc_buffer(bufferSize); 51 + * const writtenPtr = exports.ghostty_wasm_alloc_usize(); 52 + * 53 + * // Encode the key event 54 + * exports.ghostty_key_encoder_encode( 55 + * encoder, eventPtr, bufPtr, bufferSize, writtenPtr 56 + * ); 57 + * 58 + * // Read encoded output 59 + * const bytesWritten = view.getUint32(writtenPtr, true); 60 + * const encoded = new Uint8Array(wasmMemory.buffer, bufPtr, bytesWritten); 61 + * @endcode 62 + * 63 + * @remark The code above is pretty ugly! This is the lowest level interface 64 + * to the libghostty-vt Wasm module. In practice, this should be wrapped 65 + * in a higher-level API that abstracts away all this. 66 + * 67 + * @{ 68 + */ 69 + 70 + /** 71 + * Allocate an opaque pointer. This can be used for any opaque pointer 72 + * types such as GhosttyKeyEncoder, GhosttyKeyEvent, etc. 73 + * 74 + * @return Pointer to allocated opaque pointer, or NULL if allocation failed 75 + * @ingroup wasm 76 + */ 77 + void** ghostty_wasm_alloc_opaque(void); 78 + 79 + /** 80 + * Free an opaque pointer allocated by ghostty_wasm_alloc_opaque(). 81 + * 82 + * @param ptr Pointer to free, or NULL (NULL is safely ignored) 83 + * @ingroup wasm 84 + */ 85 + void ghostty_wasm_free_opaque(void **ptr); 86 + 87 + /** 88 + * Allocate a buffer of the specified length. 89 + * 90 + * @param len Number of bytes to allocate 91 + * @return Pointer to allocated buffer, or NULL if allocation failed 92 + * @ingroup wasm 93 + */ 94 + uint8_t* ghostty_wasm_alloc_buffer(size_t len); 95 + 96 + /** 97 + * Free a buffer allocated by ghostty_wasm_alloc_buffer(). 98 + * 99 + * @param ptr Pointer to the buffer to free, or NULL (NULL is safely ignored) 100 + * @param len Length of the buffer (must match the length passed to alloc) 101 + * @ingroup wasm 102 + */ 103 + void ghostty_wasm_free_buffer(uint8_t *ptr, size_t len); 104 + 105 + /** 106 + * Allocate a single uint8_t value. 107 + * 108 + * @return Pointer to allocated uint8_t, or NULL if allocation failed 109 + * @ingroup wasm 110 + */ 111 + uint8_t* ghostty_wasm_alloc_u8(void); 112 + 113 + /** 114 + * Free a uint8_t allocated by ghostty_wasm_alloc_u8(). 115 + * 116 + * @param ptr Pointer to free, or NULL (NULL is safely ignored) 117 + * @ingroup wasm 118 + */ 119 + void ghostty_wasm_free_u8(uint8_t *ptr); 120 + 121 + /** 122 + * Allocate a single size_t value. 123 + * 124 + * @return Pointer to allocated size_t, or NULL if allocation failed 125 + * @ingroup wasm 126 + */ 127 + size_t* ghostty_wasm_alloc_usize(void); 128 + 129 + /** 130 + * Free a size_t allocated by ghostty_wasm_alloc_usize(). 131 + * 132 + * @param ptr Pointer to free, or NULL (NULL is safely ignored) 133 + * @ingroup wasm 134 + */ 135 + void ghostty_wasm_free_usize(size_t *ptr); 136 + 137 + /** @} */ 138 + 139 + #endif /* __wasm__ */ 140 + 141 + #endif /* GHOSTTY_VT_WASM_H */
+1 -1
src/input/key_encode.zig
··· 77 77 event: key.KeyEvent, 78 78 opts: Options, 79 79 ) std.Io.Writer.Error!void { 80 - //std.log.warn("KEYENCODER event={} opts={}", .{ event, opts }); 80 + std.log.warn("KEYENCODER event={} opts={}", .{ event, opts }); 81 81 return if (opts.kitty_flags.int() != 0) try kitty( 82 82 writer, 83 83 event,
+3
src/lib/allocator.zig
··· 2 2 const builtin = @import("builtin"); 3 3 const testing = std.testing; 4 4 5 + /// Convenience functions 6 + pub const convenience = @import("allocator/convenience.zig"); 7 + 5 8 /// Useful alias since they're required to create Zig allocators 6 9 pub const ZigVTable = std.mem.Allocator.VTable; 7 10
+50
src/lib/allocator/convenience.zig
··· 1 + //! This contains convenience functions for allocating various types. 2 + //! 3 + //! The primary use case for this is Wasm builds. Ghostty relies a lot on 4 + //! pointers to various types for ABI compatibility and creating those pointers 5 + //! in Wasm is tedious. This file contains a purely additive set of functions 6 + //! that can be exposed to the Wasm module without changing the API from the 7 + //! C library. 8 + //! 9 + //! Given these are convenience methods, they always use the default allocator. 10 + //! If a caller is using a custom allocator, they have the expertise to 11 + //! allocate these types manually using their custom allocator. 12 + 13 + // Get our default allocator at comptime since it is known. 14 + const default = @import("../allocator.zig").default; 15 + const alloc = default(null); 16 + 17 + pub const Opaque = *anyopaque; 18 + 19 + pub fn allocOpaque() callconv(.c) ?*Opaque { 20 + return alloc.create(*anyopaque) catch return null; 21 + } 22 + 23 + pub fn freeOpaque(ptr: ?*Opaque) callconv(.c) void { 24 + if (ptr) |p| alloc.destroy(p); 25 + } 26 + 27 + pub fn allocBuffer(len: usize) callconv(.c) ?[*]u8 { 28 + const slice = alloc.alloc(u8, len) catch return null; 29 + return slice.ptr; 30 + } 31 + 32 + pub fn freeBuffer(ptr: ?[*]u8, len: usize) callconv(.c) void { 33 + if (ptr) |p| alloc.free(p[0..len]); 34 + } 35 + 36 + pub fn allocU8() callconv(.c) ?*u8 { 37 + return alloc.create(u8) catch return null; 38 + } 39 + 40 + pub fn freeU8(ptr: ?*u8) callconv(.c) void { 41 + if (ptr) |p| alloc.destroy(p); 42 + } 43 + 44 + pub fn allocUsize() callconv(.c) ?*usize { 45 + return alloc.create(usize) catch return null; 46 + } 47 + 48 + pub fn freeUsize(ptr: ?*usize) callconv(.c) void { 49 + if (ptr) |p| alloc.destroy(p); 50 + }
+13
src/lib_vt.zig
··· 126 126 @export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" }); 127 127 @export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" }); 128 128 @export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" }); 129 + 130 + // On Wasm we need to export our allocator convenience functions. 131 + if (builtin.target.cpu.arch.isWasm()) { 132 + const alloc = @import("lib/allocator/convenience.zig"); 133 + @export(&alloc.allocOpaque, .{ .name = "ghostty_wasm_alloc_opaque" }); 134 + @export(&alloc.freeOpaque, .{ .name = "ghostty_wasm_free_opaque" }); 135 + @export(&alloc.allocBuffer, .{ .name = "ghostty_wasm_alloc_buffer" }); 136 + @export(&alloc.freeBuffer, .{ .name = "ghostty_wasm_free_buffer" }); 137 + @export(&alloc.allocU8, .{ .name = "ghostty_wasm_alloc_u8" }); 138 + @export(&alloc.freeU8, .{ .name = "ghostty_wasm_free_u8" }); 139 + @export(&alloc.allocUsize, .{ .name = "ghostty_wasm_alloc_usize" }); 140 + @export(&alloc.freeUsize, .{ .name = "ghostty_wasm_free_usize" }); 141 + } 129 142 } 130 143 } 131 144
+18 -1
src/terminal/c/key_encode.zig
··· 10 10 const Result = @import("result.zig").Result; 11 11 const KeyEvent = @import("key_event.zig").Event; 12 12 13 + const log = std.log.scoped(.key_encode); 14 + 13 15 /// Wrapper around key encoding options that tracks the allocator for C API usage. 14 16 const KeyEncoderWrapper = struct { 15 17 opts: key_encode.Options, ··· 70 72 option: Option, 71 73 value: ?*const anyopaque, 72 74 ) callconv(.c) void { 75 + if (comptime std.debug.runtime_safety) { 76 + _ = std.meta.intToEnum(Option, @intFromEnum(option)) catch { 77 + log.warn("setopt invalid option value={d}", .{@intFromEnum(option)}); 78 + return; 79 + }; 80 + } 81 + 73 82 return switch (option) { 74 83 inline else => |comptime_option| setoptTyped( 75 84 encoder_, ··· 95 104 const bits: u5 = @truncate(value.*); 96 105 break :flags @bitCast(bits); 97 106 }, 98 - .macos_option_as_alt => opts.macos_option_as_alt = value.*, 107 + .macos_option_as_alt => { 108 + if (comptime std.debug.runtime_safety) { 109 + _ = std.meta.intToEnum(OptionAsAlt, @intFromEnum(value.*)) catch { 110 + log.warn("setopt invalid OptionAsAlt value={d}", .{@intFromEnum(value.*)}); 111 + return; 112 + }; 113 + } 114 + opts.macos_option_as_alt = value.*; 115 + }, 99 116 } 100 117 } 101 118
+16
src/terminal/c/key_event.zig
··· 6 6 const key = @import("../../input/key.zig"); 7 7 const Result = @import("result.zig").Result; 8 8 9 + const log = std.log.scoped(.key_event); 10 + 9 11 /// Wrapper around KeyEvent that tracks the allocator for C API usage. 10 12 /// The UTF-8 text is not owned by this wrapper - the caller is responsible 11 13 /// for ensuring the lifetime of any UTF-8 text set via set_utf8. ··· 36 38 } 37 39 38 40 pub fn set_action(event_: Event, action: key.Action) callconv(.c) void { 41 + if (comptime std.debug.runtime_safety) { 42 + _ = std.meta.intToEnum(key.Action, @intFromEnum(action)) catch { 43 + log.warn("set_action invalid action value={d}", .{@intFromEnum(action)}); 44 + return; 45 + }; 46 + } 47 + 39 48 const event: *key.KeyEvent = &event_.?.event; 40 49 event.action = action; 41 50 } ··· 46 55 } 47 56 48 57 pub fn set_key(event_: Event, k: key.Key) callconv(.c) void { 58 + if (comptime std.debug.runtime_safety) { 59 + _ = std.meta.intToEnum(key.Key, @intFromEnum(k)) catch { 60 + log.warn("set_key invalid key value={d}", .{@intFromEnum(k)}); 61 + return; 62 + }; 63 + } 64 + 49 65 const event: *key.KeyEvent = &event_.?.event; 50 66 event.key = k; 51 67 }
+9
src/terminal/c/osc.zig
··· 6 6 const osc = @import("../osc.zig"); 7 7 const Result = @import("result.zig").Result; 8 8 9 + const log = std.log.scoped(.osc); 10 + 9 11 /// C: GhosttyOscParser 10 12 pub const Parser = ?*osc.Parser; 11 13 ··· 68 70 data: CommandData, 69 71 out: ?*anyopaque, 70 72 ) callconv(.c) bool { 73 + if (comptime std.debug.runtime_safety) { 74 + _ = std.meta.intToEnum(CommandData, @intFromEnum(data)) catch { 75 + log.warn("commandData invalid data value={d}", .{@intFromEnum(data)}); 76 + return false; 77 + }; 78 + } 79 + 71 80 return switch (data) { 72 81 inline else => |comptime_data| commandDataTyped( 73 82 command_,