Go bindings for libghostty-vt.
0
fork

Configure Feed

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

Merge pull request #12 from mitchellh/push-lwxvznmznmyl

encoding: add key, mouse, and focus encoding bindings

authored by

Mitchell Hashimoto and committed by
GitHub
ffed4984 2312dc43

+1495 -35
+3 -4
TODO.md
··· 2 2 3 3 ## Not Bound 4 4 5 - - [ ] Key encoding (`key.h`, `key/encoder.h`, `key/event.h`) 6 - - [ ] Mouse encoding (`mouse.h`, `mouse/encoder.h`, `mouse/event.h`) 5 + - [x] Key encoding (`key.h`, `key/encoder.h`, `key/event.h`) 6 + - [x] Mouse encoding (`mouse.h`, `mouse/encoder.h`, `mouse/event.h`) 7 7 - [ ] OSC parser (`osc.h`) 8 8 - [ ] SGR parser (`sgr.h`) 9 9 - [ ] Paste utilities (`paste.h`) 10 - - [ ] Focus encoding (`focus.h`) 10 + - [x] Focus encoding (`focus.h`) 11 11 - [x] Kitty graphics (`kitty_graphics.h`) 12 12 - [x] Allocator (`allocator.h` — `ghostty_alloc`, `ghostty_free`) 13 13 - [ ] Selection type (`selection.h`) ··· 22 22 - [ ] `ghostty_grid_ref_hyperlink_uri()` 23 23 - [ ] `ghostty_terminal_point_from_grid_ref()` 24 24 - [ ] `ghostty_formatter_format_buf()` 25 - - [ ] `ghostty_focus_encode()`
+68
focus.go
··· 1 + package libghostty 2 + 3 + // Focus encoding — encode focus in/out events into terminal escape 4 + // sequences (CSI I / CSI O) for focus reporting mode (mode 1004). 5 + // Wraps the C API from focus.h. 6 + 7 + /* 8 + #include <ghostty/vt.h> 9 + */ 10 + import "C" 11 + 12 + import "unsafe" 13 + 14 + // FocusEvent represents a focus gained or lost event for focus 15 + // reporting mode (mode 1004). 16 + // 17 + // C: GhosttyFocusEvent 18 + type FocusEvent int 19 + 20 + const ( 21 + // FocusGained indicates the terminal window gained focus. 22 + FocusGained FocusEvent = C.GHOSTTY_FOCUS_GAINED 23 + 24 + // FocusLost indicates the terminal window lost focus. 25 + FocusLost FocusEvent = C.GHOSTTY_FOCUS_LOST 26 + ) 27 + 28 + // FocusEncode encodes a focus event into a terminal escape sequence 29 + // and returns the result as a byte slice. 30 + func FocusEncode(event FocusEvent) ([]byte, error) { 31 + // Focus sequences are short (CSI I or CSI O = 3 bytes). 32 + var buf [16]byte 33 + var outLen C.size_t 34 + result := C.ghostty_focus_encode( 35 + C.GhosttyFocusEvent(event), 36 + (*C.char)(unsafe.Pointer(&buf[0])), 37 + C.size_t(len(buf)), 38 + &outLen, 39 + ) 40 + 41 + if result == C.GHOSTTY_SUCCESS { 42 + if outLen == 0 { 43 + return nil, nil 44 + } 45 + out := make([]byte, outLen) 46 + copy(out, buf[:outLen]) 47 + return out, nil 48 + } 49 + 50 + if result == C.GHOSTTY_OUT_OF_SPACE { 51 + dynBuf := make([]byte, outLen) 52 + var written C.size_t 53 + if err := resultError(C.ghostty_focus_encode( 54 + C.GhosttyFocusEvent(event), 55 + (*C.char)(unsafe.Pointer(&dynBuf[0])), 56 + outLen, 57 + &written, 58 + )); err != nil { 59 + return nil, err 60 + } 61 + if written == 0 { 62 + return nil, nil 63 + } 64 + return dynBuf[:written], nil 65 + } 66 + 67 + return nil, &Error{Result: Result(result)} 68 + }
+29
focus_test.go
··· 1 + package libghostty 2 + 3 + import ( 4 + "testing" 5 + ) 6 + 7 + func TestFocusEncodeGained(t *testing.T) { 8 + out, err := FocusEncode(FocusGained) 9 + if err != nil { 10 + t.Fatal(err) 11 + } 12 + // Focus gained: CSI I = \x1b[I 13 + expected := "\x1b[I" 14 + if string(out) != expected { 15 + t.Fatalf("expected %q, got %q", expected, string(out)) 16 + } 17 + } 18 + 19 + func TestFocusEncodeLost(t *testing.T) { 20 + out, err := FocusEncode(FocusLost) 21 + if err != nil { 22 + t.Fatal(err) 23 + } 24 + // Focus lost: CSI O = \x1b[O 25 + expected := "\x1b[O" 26 + if string(out) != expected { 27 + t.Fatalf("expected %q, got %q", expected, string(out)) 28 + } 29 + }
+210
key_encoder.go
··· 1 + package libghostty 2 + 3 + // Key encoder — encodes key events into terminal escape sequences. 4 + // Wraps the C APIs from key/encoder.h. 5 + 6 + /* 7 + #include <ghostty/vt.h> 8 + */ 9 + import "C" 10 + 11 + import "unsafe" 12 + 13 + // KeyEncoder encodes key events into terminal escape sequences, 14 + // supporting both legacy encoding and the Kitty Keyboard Protocol. 15 + // 16 + // Basic usage: 17 + // 1. Create an encoder with NewKeyEncoder. 18 + // 2. Configure options with SetOpt* methods or SetOptFromTerminal. 19 + // 3. Create key events, encode them with Encode, and free them. 20 + // 4. Free the encoder with Close when done. 21 + // 22 + // C: GhosttyKeyEncoder 23 + type KeyEncoder struct { 24 + ptr C.GhosttyKeyEncoder 25 + } 26 + 27 + // KittyKeyFlags is a bitmask of Kitty keyboard protocol flags. 28 + // C: GhosttyKittyKeyFlags 29 + type KittyKeyFlags uint8 30 + 31 + const ( 32 + // KittyKeyDisabled disables the Kitty keyboard protocol (all flags off). 33 + KittyKeyDisabled KittyKeyFlags = C.GHOSTTY_KITTY_KEY_DISABLED 34 + 35 + // KittyKeyDisambiguate enables disambiguating escape codes. 36 + KittyKeyDisambiguate KittyKeyFlags = C.GHOSTTY_KITTY_KEY_DISAMBIGUATE 37 + 38 + // KittyKeyReportEvents enables reporting key press and release events. 39 + KittyKeyReportEvents KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_EVENTS 40 + 41 + // KittyKeyReportAlternates enables reporting alternate key codes. 42 + KittyKeyReportAlternates KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ALTERNATES 43 + 44 + // KittyKeyReportAll reports all key events including those normally 45 + // handled by the terminal. 46 + KittyKeyReportAll KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ALL 47 + 48 + // KittyKeyReportAssociated reports associated text with key events. 49 + KittyKeyReportAssociated KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED 50 + 51 + // KittyKeyAll enables all Kitty keyboard protocol flags. 52 + KittyKeyAll KittyKeyFlags = C.GHOSTTY_KITTY_KEY_ALL 53 + ) 54 + 55 + // OptionAsAlt determines whether the macOS "option" key is treated as 56 + // "alt". 57 + // 58 + // C: GhosttyOptionAsAlt 59 + type OptionAsAlt int 60 + 61 + const ( 62 + // OptionAsAltFalse means the option key is not treated as alt. 63 + OptionAsAltFalse OptionAsAlt = C.GHOSTTY_OPTION_AS_ALT_FALSE 64 + 65 + // OptionAsAltTrue means the option key is treated as alt. 66 + OptionAsAltTrue OptionAsAlt = C.GHOSTTY_OPTION_AS_ALT_TRUE 67 + 68 + // OptionAsAltLeft means only the left option key is treated as alt. 69 + OptionAsAltLeft OptionAsAlt = C.GHOSTTY_OPTION_AS_ALT_LEFT 70 + 71 + // OptionAsAltRight means only the right option key is treated as alt. 72 + OptionAsAltRight OptionAsAlt = C.GHOSTTY_OPTION_AS_ALT_RIGHT 73 + ) 74 + 75 + // KeyEncoderOption identifies an encoder configuration option for use 76 + // with SetOpt. 77 + // 78 + // C: GhosttyKeyEncoderOption 79 + type KeyEncoderOption int 80 + 81 + const ( 82 + // KeyEncoderOptCursorKeyApplication sets DEC mode 1: cursor key 83 + // application mode (value: bool). 84 + KeyEncoderOptCursorKeyApplication KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION 85 + 86 + // KeyEncoderOptKeypadKeyApplication sets DEC mode 66: keypad key 87 + // application mode (value: bool). 88 + KeyEncoderOptKeypadKeyApplication KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION 89 + 90 + // KeyEncoderOptIgnoreKeypadWithNumlock sets DEC mode 1035: ignore 91 + // keypad with numlock (value: bool). 92 + KeyEncoderOptIgnoreKeypadWithNumlock KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK 93 + 94 + // KeyEncoderOptAltEscPrefix sets DEC mode 1036: alt sends escape 95 + // prefix (value: bool). 96 + KeyEncoderOptAltEscPrefix KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX 97 + 98 + // KeyEncoderOptModifyOtherKeysState2 sets xterm modifyOtherKeys 99 + // mode 2 (value: bool). 100 + KeyEncoderOptModifyOtherKeysState2 KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2 101 + 102 + // KeyEncoderOptKittyFlags sets Kitty keyboard protocol flags 103 + // (value: KittyKeyFlags bitmask). 104 + KeyEncoderOptKittyFlags KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS 105 + 106 + // KeyEncoderOptMacOSOptionAsAlt sets the macOS option-as-alt 107 + // setting (value: OptionAsAlt). 108 + KeyEncoderOptMacOSOptionAsAlt KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT 109 + 110 + // KeyEncoderOptBackarrowKeyMode sets backarrow key mode (value: bool). 111 + // When false (default), backspace emits 0x7f; when true, 0x08. 112 + KeyEncoderOptBackarrowKeyMode KeyEncoderOption = C.GHOSTTY_KEY_ENCODER_OPT_BACKARROW_KEY_MODE 113 + ) 114 + 115 + // NewKeyEncoder creates a new key encoder with default options. 116 + // The encoder must be freed with Close when no longer needed. 117 + func NewKeyEncoder() (*KeyEncoder, error) { 118 + var ptr C.GhosttyKeyEncoder 119 + if err := resultError(C.ghostty_key_encoder_new(nil, &ptr)); err != nil { 120 + return nil, err 121 + } 122 + return &KeyEncoder{ptr: ptr}, nil 123 + } 124 + 125 + // Close frees the underlying key encoder handle. After this call, 126 + // the encoder must not be used. 127 + func (enc *KeyEncoder) Close() { 128 + C.ghostty_key_encoder_free(enc.ptr) 129 + } 130 + 131 + // SetOptBool sets a boolean encoder option. Use this for options 132 + // that accept a bool value (most encoder options). 133 + func (enc *KeyEncoder) SetOptBool(opt KeyEncoderOption, val bool) { 134 + v := C.bool(val) 135 + C.ghostty_key_encoder_setopt(enc.ptr, C.GhosttyKeyEncoderOption(opt), unsafe.Pointer(&v)) 136 + } 137 + 138 + // SetOptKittyFlags sets the Kitty keyboard protocol flags on the 139 + // encoder. 140 + func (enc *KeyEncoder) SetOptKittyFlags(flags KittyKeyFlags) { 141 + v := C.GhosttyKittyKeyFlags(flags) 142 + C.ghostty_key_encoder_setopt(enc.ptr, C.GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS, unsafe.Pointer(&v)) 143 + } 144 + 145 + // SetOptOptionAsAlt sets the macOS option-as-alt behavior on the 146 + // encoder. 147 + func (enc *KeyEncoder) SetOptOptionAsAlt(val OptionAsAlt) { 148 + v := C.GhosttyOptionAsAlt(val) 149 + C.ghostty_key_encoder_setopt(enc.ptr, C.GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT, unsafe.Pointer(&v)) 150 + } 151 + 152 + // SetOptFromTerminal reads the terminal's current modes and flags and 153 + // applies them to the encoder's options. This sets cursor key 154 + // application mode, keypad mode, alt escape prefix, modifyOtherKeys 155 + // state, and Kitty keyboard protocol flags from the terminal state. 156 + // 157 + // Note that the macOS option-as-alt option cannot be determined from 158 + // terminal state and is reset to OptionAsAltFalse by this call. Use 159 + // SetOptOptionAsAlt afterward if needed. 160 + func (enc *KeyEncoder) SetOptFromTerminal(t *Terminal) { 161 + C.ghostty_key_encoder_setopt_from_terminal(enc.ptr, t.ptr) 162 + } 163 + 164 + // Encode encodes a key event into a terminal escape sequence and 165 + // returns the result as a byte slice. Not all key events produce 166 + // output (e.g. unmodified modifier keys); in that case, a nil slice 167 + // and nil error are returned. 168 + func (enc *KeyEncoder) Encode(event *KeyEvent) ([]byte, error) { 169 + // Most escape sequences fit in 128 bytes. Try with a stack buffer 170 + // first; fall back to a larger heap allocation if needed. 171 + var buf [128]byte 172 + var outLen C.size_t 173 + result := C.ghostty_key_encoder_encode( 174 + enc.ptr, 175 + event.ptr, 176 + (*C.char)(unsafe.Pointer(&buf[0])), 177 + C.size_t(len(buf)), 178 + &outLen, 179 + ) 180 + 181 + if result == C.GHOSTTY_SUCCESS { 182 + if outLen == 0 { 183 + return nil, nil 184 + } 185 + out := make([]byte, outLen) 186 + copy(out, buf[:outLen]) 187 + return out, nil 188 + } 189 + 190 + if result == C.GHOSTTY_OUT_OF_SPACE { 191 + // outLen contains the required buffer size. 192 + dynBuf := make([]byte, outLen) 193 + var written C.size_t 194 + if err := resultError(C.ghostty_key_encoder_encode( 195 + enc.ptr, 196 + event.ptr, 197 + (*C.char)(unsafe.Pointer(&dynBuf[0])), 198 + outLen, 199 + &written, 200 + )); err != nil { 201 + return nil, err 202 + } 203 + if written == 0 { 204 + return nil, nil 205 + } 206 + return dynBuf[:written], nil 207 + } 208 + 209 + return nil, &Error{Result: Result(result)} 210 + }
+395
key_event.go
··· 1 + package libghostty 2 + 3 + // Key event representation and manipulation. 4 + // Wraps the C APIs from key/event.h. 5 + 6 + /* 7 + #include <ghostty/vt.h> 8 + */ 9 + import "C" 10 + 11 + import "unsafe" 12 + 13 + // KeyEvent is an opaque handle representing a keyboard input event 14 + // containing information about the physical key pressed, modifiers, 15 + // and generated text. 16 + // 17 + // C: GhosttyKeyEvent 18 + type KeyEvent struct { 19 + ptr C.GhosttyKeyEvent 20 + } 21 + 22 + // KeyAction represents the type of keyboard input event (press, release, 23 + // or repeat). 24 + // 25 + // C: GhosttyKeyAction 26 + type KeyAction int 27 + 28 + const ( 29 + // KeyActionRelease indicates a key was released. 30 + KeyActionRelease KeyAction = C.GHOSTTY_KEY_ACTION_RELEASE 31 + 32 + // KeyActionPress indicates a key was pressed. 33 + KeyActionPress KeyAction = C.GHOSTTY_KEY_ACTION_PRESS 34 + 35 + // KeyActionRepeat indicates a key is being held down (repeat). 36 + KeyActionRepeat KeyAction = C.GHOSTTY_KEY_ACTION_REPEAT 37 + ) 38 + 39 + // Mods is a bitmask representing keyboard modifier keys. 40 + // Use the Mods* constants to test and set individual modifiers. 41 + // 42 + // C: GhosttyMods 43 + type Mods uint16 44 + 45 + const ( 46 + // ModShift indicates the Shift key is pressed. 47 + ModShift Mods = C.GHOSTTY_MODS_SHIFT 48 + 49 + // ModCtrl indicates the Control key is pressed. 50 + ModCtrl Mods = C.GHOSTTY_MODS_CTRL 51 + 52 + // ModAlt indicates the Alt/Option key is pressed. 53 + ModAlt Mods = C.GHOSTTY_MODS_ALT 54 + 55 + // ModSuper indicates the Super/Command/Windows key is pressed. 56 + ModSuper Mods = C.GHOSTTY_MODS_SUPER 57 + 58 + // ModCapsLock indicates Caps Lock is active. 59 + ModCapsLock Mods = C.GHOSTTY_MODS_CAPS_LOCK 60 + 61 + // ModNumLock indicates Num Lock is active. 62 + ModNumLock Mods = C.GHOSTTY_MODS_NUM_LOCK 63 + 64 + // ModShiftSide indicates right Shift is pressed (0 = left, 1 = right). 65 + // Only meaningful when ModShift is set. 66 + ModShiftSide Mods = C.GHOSTTY_MODS_SHIFT_SIDE 67 + 68 + // ModCtrlSide indicates right Ctrl is pressed (0 = left, 1 = right). 69 + // Only meaningful when ModCtrl is set. 70 + ModCtrlSide Mods = C.GHOSTTY_MODS_CTRL_SIDE 71 + 72 + // ModAltSide indicates right Alt is pressed (0 = left, 1 = right). 73 + // Only meaningful when ModAlt is set. 74 + ModAltSide Mods = C.GHOSTTY_MODS_ALT_SIDE 75 + 76 + // ModSuperSide indicates right Super is pressed (0 = left, 1 = right). 77 + // Only meaningful when ModSuper is set. 78 + ModSuperSide Mods = C.GHOSTTY_MODS_SUPER_SIDE 79 + ) 80 + 81 + // Key represents a physical key code. These are layout-independent and 82 + // based on the W3C UI Events KeyboardEvent code standard. 83 + // 84 + // C: GhosttyKey 85 + type Key int 86 + 87 + // Writing System Keys (W3C § 3.1.1) 88 + const ( 89 + KeyUnidentified Key = C.GHOSTTY_KEY_UNIDENTIFIED 90 + KeyBackquote Key = C.GHOSTTY_KEY_BACKQUOTE 91 + KeyBackslash Key = C.GHOSTTY_KEY_BACKSLASH 92 + KeyBracketLeft Key = C.GHOSTTY_KEY_BRACKET_LEFT 93 + KeyBracketRight Key = C.GHOSTTY_KEY_BRACKET_RIGHT 94 + KeyComma Key = C.GHOSTTY_KEY_COMMA 95 + KeyDigit0 Key = C.GHOSTTY_KEY_DIGIT_0 96 + KeyDigit1 Key = C.GHOSTTY_KEY_DIGIT_1 97 + KeyDigit2 Key = C.GHOSTTY_KEY_DIGIT_2 98 + KeyDigit3 Key = C.GHOSTTY_KEY_DIGIT_3 99 + KeyDigit4 Key = C.GHOSTTY_KEY_DIGIT_4 100 + KeyDigit5 Key = C.GHOSTTY_KEY_DIGIT_5 101 + KeyDigit6 Key = C.GHOSTTY_KEY_DIGIT_6 102 + KeyDigit7 Key = C.GHOSTTY_KEY_DIGIT_7 103 + KeyDigit8 Key = C.GHOSTTY_KEY_DIGIT_8 104 + KeyDigit9 Key = C.GHOSTTY_KEY_DIGIT_9 105 + KeyEqual Key = C.GHOSTTY_KEY_EQUAL 106 + KeyIntlBackslash Key = C.GHOSTTY_KEY_INTL_BACKSLASH 107 + KeyIntlRo Key = C.GHOSTTY_KEY_INTL_RO 108 + KeyIntlYen Key = C.GHOSTTY_KEY_INTL_YEN 109 + KeyA Key = C.GHOSTTY_KEY_A 110 + KeyB Key = C.GHOSTTY_KEY_B 111 + KeyC Key = C.GHOSTTY_KEY_C 112 + KeyD Key = C.GHOSTTY_KEY_D 113 + KeyE Key = C.GHOSTTY_KEY_E 114 + KeyF Key = C.GHOSTTY_KEY_F 115 + KeyG Key = C.GHOSTTY_KEY_G 116 + KeyH Key = C.GHOSTTY_KEY_H 117 + KeyI Key = C.GHOSTTY_KEY_I 118 + KeyJ Key = C.GHOSTTY_KEY_J 119 + KeyK Key = C.GHOSTTY_KEY_K 120 + KeyL Key = C.GHOSTTY_KEY_L 121 + KeyM Key = C.GHOSTTY_KEY_M 122 + KeyN Key = C.GHOSTTY_KEY_N 123 + KeyO Key = C.GHOSTTY_KEY_O 124 + KeyP Key = C.GHOSTTY_KEY_P 125 + KeyQ Key = C.GHOSTTY_KEY_Q 126 + KeyR Key = C.GHOSTTY_KEY_R 127 + KeyS Key = C.GHOSTTY_KEY_S 128 + KeyT Key = C.GHOSTTY_KEY_T 129 + KeyU Key = C.GHOSTTY_KEY_U 130 + KeyV Key = C.GHOSTTY_KEY_V 131 + KeyW Key = C.GHOSTTY_KEY_W 132 + KeyX Key = C.GHOSTTY_KEY_X 133 + KeyY Key = C.GHOSTTY_KEY_Y 134 + KeyZ Key = C.GHOSTTY_KEY_Z 135 + KeyMinus Key = C.GHOSTTY_KEY_MINUS 136 + KeyPeriod Key = C.GHOSTTY_KEY_PERIOD 137 + KeyQuote Key = C.GHOSTTY_KEY_QUOTE 138 + KeySemicolon Key = C.GHOSTTY_KEY_SEMICOLON 139 + KeySlash Key = C.GHOSTTY_KEY_SLASH 140 + ) 141 + 142 + // Functional Keys (W3C § 3.1.2) 143 + const ( 144 + KeyAltLeft Key = C.GHOSTTY_KEY_ALT_LEFT 145 + KeyAltRight Key = C.GHOSTTY_KEY_ALT_RIGHT 146 + KeyBackspace Key = C.GHOSTTY_KEY_BACKSPACE 147 + KeyCapsLock Key = C.GHOSTTY_KEY_CAPS_LOCK 148 + KeyContextMenu Key = C.GHOSTTY_KEY_CONTEXT_MENU 149 + KeyControlLeft Key = C.GHOSTTY_KEY_CONTROL_LEFT 150 + KeyControlRight Key = C.GHOSTTY_KEY_CONTROL_RIGHT 151 + KeyEnter Key = C.GHOSTTY_KEY_ENTER 152 + KeyMetaLeft Key = C.GHOSTTY_KEY_META_LEFT 153 + KeyMetaRight Key = C.GHOSTTY_KEY_META_RIGHT 154 + KeyShiftLeft Key = C.GHOSTTY_KEY_SHIFT_LEFT 155 + KeyShiftRight Key = C.GHOSTTY_KEY_SHIFT_RIGHT 156 + KeySpace Key = C.GHOSTTY_KEY_SPACE 157 + KeyTab Key = C.GHOSTTY_KEY_TAB 158 + KeyConvert Key = C.GHOSTTY_KEY_CONVERT 159 + KeyKanaMode Key = C.GHOSTTY_KEY_KANA_MODE 160 + KeyNonConvert Key = C.GHOSTTY_KEY_NON_CONVERT 161 + ) 162 + 163 + // Control Pad Section (W3C § 3.2) 164 + const ( 165 + KeyDelete Key = C.GHOSTTY_KEY_DELETE 166 + KeyEnd Key = C.GHOSTTY_KEY_END 167 + KeyHelp Key = C.GHOSTTY_KEY_HELP 168 + KeyHome Key = C.GHOSTTY_KEY_HOME 169 + KeyInsert Key = C.GHOSTTY_KEY_INSERT 170 + KeyPageDown Key = C.GHOSTTY_KEY_PAGE_DOWN 171 + KeyPageUp Key = C.GHOSTTY_KEY_PAGE_UP 172 + ) 173 + 174 + // Arrow Pad Section (W3C § 3.3) 175 + const ( 176 + KeyArrowDown Key = C.GHOSTTY_KEY_ARROW_DOWN 177 + KeyArrowLeft Key = C.GHOSTTY_KEY_ARROW_LEFT 178 + KeyArrowRight Key = C.GHOSTTY_KEY_ARROW_RIGHT 179 + KeyArrowUp Key = C.GHOSTTY_KEY_ARROW_UP 180 + ) 181 + 182 + // Numpad Section (W3C § 3.4) 183 + const ( 184 + KeyNumLock Key = C.GHOSTTY_KEY_NUM_LOCK 185 + KeyNumpad0 Key = C.GHOSTTY_KEY_NUMPAD_0 186 + KeyNumpad1 Key = C.GHOSTTY_KEY_NUMPAD_1 187 + KeyNumpad2 Key = C.GHOSTTY_KEY_NUMPAD_2 188 + KeyNumpad3 Key = C.GHOSTTY_KEY_NUMPAD_3 189 + KeyNumpad4 Key = C.GHOSTTY_KEY_NUMPAD_4 190 + KeyNumpad5 Key = C.GHOSTTY_KEY_NUMPAD_5 191 + KeyNumpad6 Key = C.GHOSTTY_KEY_NUMPAD_6 192 + KeyNumpad7 Key = C.GHOSTTY_KEY_NUMPAD_7 193 + KeyNumpad8 Key = C.GHOSTTY_KEY_NUMPAD_8 194 + KeyNumpad9 Key = C.GHOSTTY_KEY_NUMPAD_9 195 + KeyNumpadAdd Key = C.GHOSTTY_KEY_NUMPAD_ADD 196 + KeyNumpadBackspace Key = C.GHOSTTY_KEY_NUMPAD_BACKSPACE 197 + KeyNumpadClear Key = C.GHOSTTY_KEY_NUMPAD_CLEAR 198 + KeyNumpadClearEntry Key = C.GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY 199 + KeyNumpadComma Key = C.GHOSTTY_KEY_NUMPAD_COMMA 200 + KeyNumpadDecimal Key = C.GHOSTTY_KEY_NUMPAD_DECIMAL 201 + KeyNumpadDivide Key = C.GHOSTTY_KEY_NUMPAD_DIVIDE 202 + KeyNumpadEnter Key = C.GHOSTTY_KEY_NUMPAD_ENTER 203 + KeyNumpadEqual Key = C.GHOSTTY_KEY_NUMPAD_EQUAL 204 + KeyNumpadMemoryAdd Key = C.GHOSTTY_KEY_NUMPAD_MEMORY_ADD 205 + KeyNumpadMemoryClear Key = C.GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR 206 + KeyNumpadMemoryRecall Key = C.GHOSTTY_KEY_NUMPAD_MEMORY_RECALL 207 + KeyNumpadMemoryStore Key = C.GHOSTTY_KEY_NUMPAD_MEMORY_STORE 208 + KeyNumpadMemorySub Key = C.GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT 209 + KeyNumpadMultiply Key = C.GHOSTTY_KEY_NUMPAD_MULTIPLY 210 + KeyNumpadParenLeft Key = C.GHOSTTY_KEY_NUMPAD_PAREN_LEFT 211 + KeyNumpadParenRight Key = C.GHOSTTY_KEY_NUMPAD_PAREN_RIGHT 212 + KeyNumpadSubtract Key = C.GHOSTTY_KEY_NUMPAD_SUBTRACT 213 + KeyNumpadSeparator Key = C.GHOSTTY_KEY_NUMPAD_SEPARATOR 214 + KeyNumpadUp Key = C.GHOSTTY_KEY_NUMPAD_UP 215 + KeyNumpadDown Key = C.GHOSTTY_KEY_NUMPAD_DOWN 216 + KeyNumpadRight Key = C.GHOSTTY_KEY_NUMPAD_RIGHT 217 + KeyNumpadLeft Key = C.GHOSTTY_KEY_NUMPAD_LEFT 218 + KeyNumpadBegin Key = C.GHOSTTY_KEY_NUMPAD_BEGIN 219 + KeyNumpadHome Key = C.GHOSTTY_KEY_NUMPAD_HOME 220 + KeyNumpadEnd Key = C.GHOSTTY_KEY_NUMPAD_END 221 + KeyNumpadInsert Key = C.GHOSTTY_KEY_NUMPAD_INSERT 222 + KeyNumpadDelete Key = C.GHOSTTY_KEY_NUMPAD_DELETE 223 + KeyNumpadPageUp Key = C.GHOSTTY_KEY_NUMPAD_PAGE_UP 224 + KeyNumpadPageDown Key = C.GHOSTTY_KEY_NUMPAD_PAGE_DOWN 225 + ) 226 + 227 + // Function Section (W3C § 3.5) 228 + const ( 229 + KeyEscape Key = C.GHOSTTY_KEY_ESCAPE 230 + KeyF1 Key = C.GHOSTTY_KEY_F1 231 + KeyF2 Key = C.GHOSTTY_KEY_F2 232 + KeyF3 Key = C.GHOSTTY_KEY_F3 233 + KeyF4 Key = C.GHOSTTY_KEY_F4 234 + KeyF5 Key = C.GHOSTTY_KEY_F5 235 + KeyF6 Key = C.GHOSTTY_KEY_F6 236 + KeyF7 Key = C.GHOSTTY_KEY_F7 237 + KeyF8 Key = C.GHOSTTY_KEY_F8 238 + KeyF9 Key = C.GHOSTTY_KEY_F9 239 + KeyF10 Key = C.GHOSTTY_KEY_F10 240 + KeyF11 Key = C.GHOSTTY_KEY_F11 241 + KeyF12 Key = C.GHOSTTY_KEY_F12 242 + KeyF13 Key = C.GHOSTTY_KEY_F13 243 + KeyF14 Key = C.GHOSTTY_KEY_F14 244 + KeyF15 Key = C.GHOSTTY_KEY_F15 245 + KeyF16 Key = C.GHOSTTY_KEY_F16 246 + KeyF17 Key = C.GHOSTTY_KEY_F17 247 + KeyF18 Key = C.GHOSTTY_KEY_F18 248 + KeyF19 Key = C.GHOSTTY_KEY_F19 249 + KeyF20 Key = C.GHOSTTY_KEY_F20 250 + KeyF21 Key = C.GHOSTTY_KEY_F21 251 + KeyF22 Key = C.GHOSTTY_KEY_F22 252 + KeyF23 Key = C.GHOSTTY_KEY_F23 253 + KeyF24 Key = C.GHOSTTY_KEY_F24 254 + KeyF25 Key = C.GHOSTTY_KEY_F25 255 + KeyFn Key = C.GHOSTTY_KEY_FN 256 + KeyFnLock Key = C.GHOSTTY_KEY_FN_LOCK 257 + KeyPrintScreen Key = C.GHOSTTY_KEY_PRINT_SCREEN 258 + KeyScrollLock Key = C.GHOSTTY_KEY_SCROLL_LOCK 259 + KeyPause Key = C.GHOSTTY_KEY_PAUSE 260 + ) 261 + 262 + // Media Keys (W3C § 3.6) 263 + const ( 264 + KeyBrowserBack Key = C.GHOSTTY_KEY_BROWSER_BACK 265 + KeyBrowserFavorites Key = C.GHOSTTY_KEY_BROWSER_FAVORITES 266 + KeyBrowserForward Key = C.GHOSTTY_KEY_BROWSER_FORWARD 267 + KeyBrowserHome Key = C.GHOSTTY_KEY_BROWSER_HOME 268 + KeyBrowserRefresh Key = C.GHOSTTY_KEY_BROWSER_REFRESH 269 + KeyBrowserSearch Key = C.GHOSTTY_KEY_BROWSER_SEARCH 270 + KeyBrowserStop Key = C.GHOSTTY_KEY_BROWSER_STOP 271 + KeyEject Key = C.GHOSTTY_KEY_EJECT 272 + KeyLaunchApp1 Key = C.GHOSTTY_KEY_LAUNCH_APP_1 273 + KeyLaunchApp2 Key = C.GHOSTTY_KEY_LAUNCH_APP_2 274 + KeyLaunchMail Key = C.GHOSTTY_KEY_LAUNCH_MAIL 275 + KeyMediaPlayPause Key = C.GHOSTTY_KEY_MEDIA_PLAY_PAUSE 276 + KeyMediaSelect Key = C.GHOSTTY_KEY_MEDIA_SELECT 277 + KeyMediaStop Key = C.GHOSTTY_KEY_MEDIA_STOP 278 + KeyMediaTrackNext Key = C.GHOSTTY_KEY_MEDIA_TRACK_NEXT 279 + KeyMediaTrackPrevious Key = C.GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS 280 + KeyPower Key = C.GHOSTTY_KEY_POWER 281 + KeySleep Key = C.GHOSTTY_KEY_SLEEP 282 + KeyAudioVolumeDown Key = C.GHOSTTY_KEY_AUDIO_VOLUME_DOWN 283 + KeyAudioVolumeMute Key = C.GHOSTTY_KEY_AUDIO_VOLUME_MUTE 284 + KeyAudioVolumeUp Key = C.GHOSTTY_KEY_AUDIO_VOLUME_UP 285 + KeyWakeUp Key = C.GHOSTTY_KEY_WAKE_UP 286 + ) 287 + 288 + // Legacy, Non-standard, and Special Keys (W3C § 3.7) 289 + const ( 290 + KeyCopy Key = C.GHOSTTY_KEY_COPY 291 + KeyCut Key = C.GHOSTTY_KEY_CUT 292 + KeyPaste Key = C.GHOSTTY_KEY_PASTE 293 + ) 294 + 295 + // NewKeyEvent creates a new key event with default values. The event 296 + // must be freed with Close when no longer needed. 297 + func NewKeyEvent() (*KeyEvent, error) { 298 + var ptr C.GhosttyKeyEvent 299 + if err := resultError(C.ghostty_key_event_new(nil, &ptr)); err != nil { 300 + return nil, err 301 + } 302 + return &KeyEvent{ptr: ptr}, nil 303 + } 304 + 305 + // Close frees the underlying key event handle. After this call, the 306 + // key event must not be used. 307 + func (e *KeyEvent) Close() { 308 + C.ghostty_key_event_free(e.ptr) 309 + } 310 + 311 + // SetAction sets the key action (press, release, repeat). 312 + func (e *KeyEvent) SetAction(action KeyAction) { 313 + C.ghostty_key_event_set_action(e.ptr, C.GhosttyKeyAction(action)) 314 + } 315 + 316 + // Action returns the key action (press, release, repeat). 317 + func (e *KeyEvent) Action() KeyAction { 318 + return KeyAction(C.ghostty_key_event_get_action(e.ptr)) 319 + } 320 + 321 + // SetKey sets the physical key code. 322 + func (e *KeyEvent) SetKey(key Key) { 323 + C.ghostty_key_event_set_key(e.ptr, C.GhosttyKey(key)) 324 + } 325 + 326 + // Key returns the physical key code. 327 + func (e *KeyEvent) Key() Key { 328 + return Key(C.ghostty_key_event_get_key(e.ptr)) 329 + } 330 + 331 + // SetMods sets the modifier keys bitmask. 332 + func (e *KeyEvent) SetMods(mods Mods) { 333 + C.ghostty_key_event_set_mods(e.ptr, C.GhosttyMods(mods)) 334 + } 335 + 336 + // Mods returns the modifier keys bitmask. 337 + func (e *KeyEvent) Mods() Mods { 338 + return Mods(C.ghostty_key_event_get_mods(e.ptr)) 339 + } 340 + 341 + // SetConsumedMods sets the consumed modifiers bitmask. 342 + func (e *KeyEvent) SetConsumedMods(mods Mods) { 343 + C.ghostty_key_event_set_consumed_mods(e.ptr, C.GhosttyMods(mods)) 344 + } 345 + 346 + // ConsumedMods returns the consumed modifiers bitmask. 347 + func (e *KeyEvent) ConsumedMods() Mods { 348 + return Mods(C.ghostty_key_event_get_consumed_mods(e.ptr)) 349 + } 350 + 351 + // SetComposing sets whether the key event is part of a composition sequence. 352 + func (e *KeyEvent) SetComposing(composing bool) { 353 + C.ghostty_key_event_set_composing(e.ptr, C.bool(composing)) 354 + } 355 + 356 + // Composing reports whether the key event is part of a composition sequence. 357 + func (e *KeyEvent) Composing() bool { 358 + return bool(C.ghostty_key_event_get_composing(e.ptr)) 359 + } 360 + 361 + // SetUTF8 sets the UTF-8 text generated by the key for the current 362 + // keyboard layout. Must contain the unmodified character before any 363 + // Ctrl/Meta transformations. Do not pass C0 control characters or 364 + // platform function key codes; pass an empty string instead and let 365 + // the encoder use the logical key. 366 + // 367 + // The string is not copied by the key event. The caller must ensure 368 + // the string remains valid for the lifetime needed by the event. 369 + func (e *KeyEvent) SetUTF8(s string) { 370 + if len(s) == 0 { 371 + C.ghostty_key_event_set_utf8(e.ptr, nil, 0) 372 + return 373 + } 374 + C.ghostty_key_event_set_utf8(e.ptr, (*C.char)(unsafe.Pointer(unsafe.StringData(s))), C.size_t(len(s))) 375 + } 376 + 377 + // UTF8 returns the UTF-8 text generated by the key event. 378 + func (e *KeyEvent) UTF8() string { 379 + var n C.size_t 380 + p := C.ghostty_key_event_get_utf8(e.ptr, &n) 381 + if p == nil || n == 0 { 382 + return "" 383 + } 384 + return C.GoStringN(p, C.int(n)) 385 + } 386 + 387 + // SetUnshiftedCodepoint sets the unshifted Unicode codepoint. 388 + func (e *KeyEvent) SetUnshiftedCodepoint(cp rune) { 389 + C.ghostty_key_event_set_unshifted_codepoint(e.ptr, C.uint32_t(cp)) 390 + } 391 + 392 + // UnshiftedCodepoint returns the unshifted Unicode codepoint. 393 + func (e *KeyEvent) UnshiftedCodepoint() rune { 394 + return rune(C.ghostty_key_event_get_unshifted_codepoint(e.ptr)) 395 + }
+252
key_test.go
··· 1 + package libghostty 2 + 3 + import ( 4 + "testing" 5 + ) 6 + 7 + func TestKeyEventNewClose(t *testing.T) { 8 + ev, err := NewKeyEvent() 9 + if err != nil { 10 + t.Fatal(err) 11 + } 12 + defer ev.Close() 13 + } 14 + 15 + func TestKeyEventAction(t *testing.T) { 16 + ev, err := NewKeyEvent() 17 + if err != nil { 18 + t.Fatal(err) 19 + } 20 + defer ev.Close() 21 + 22 + ev.SetAction(KeyActionPress) 23 + if got := ev.Action(); got != KeyActionPress { 24 + t.Fatalf("expected KeyActionPress, got %d", got) 25 + } 26 + 27 + ev.SetAction(KeyActionRelease) 28 + if got := ev.Action(); got != KeyActionRelease { 29 + t.Fatalf("expected KeyActionRelease, got %d", got) 30 + } 31 + } 32 + 33 + func TestKeyEventKey(t *testing.T) { 34 + ev, err := NewKeyEvent() 35 + if err != nil { 36 + t.Fatal(err) 37 + } 38 + defer ev.Close() 39 + 40 + ev.SetKey(KeyA) 41 + if got := ev.Key(); got != KeyA { 42 + t.Fatalf("expected KeyA, got %d", got) 43 + } 44 + } 45 + 46 + func TestKeyEventMods(t *testing.T) { 47 + ev, err := NewKeyEvent() 48 + if err != nil { 49 + t.Fatal(err) 50 + } 51 + defer ev.Close() 52 + 53 + ev.SetMods(ModCtrl | ModShift) 54 + if got := ev.Mods(); got != ModCtrl|ModShift { 55 + t.Fatalf("expected Ctrl|Shift, got %d", got) 56 + } 57 + } 58 + 59 + func TestKeyEventConsumedMods(t *testing.T) { 60 + ev, err := NewKeyEvent() 61 + if err != nil { 62 + t.Fatal(err) 63 + } 64 + defer ev.Close() 65 + 66 + ev.SetConsumedMods(ModAlt) 67 + if got := ev.ConsumedMods(); got != ModAlt { 68 + t.Fatalf("expected ModAlt, got %d", got) 69 + } 70 + } 71 + 72 + func TestKeyEventComposing(t *testing.T) { 73 + ev, err := NewKeyEvent() 74 + if err != nil { 75 + t.Fatal(err) 76 + } 77 + defer ev.Close() 78 + 79 + ev.SetComposing(true) 80 + if !ev.Composing() { 81 + t.Fatal("expected composing to be true") 82 + } 83 + 84 + ev.SetComposing(false) 85 + if ev.Composing() { 86 + t.Fatal("expected composing to be false") 87 + } 88 + } 89 + 90 + func TestKeyEventUTF8(t *testing.T) { 91 + ev, err := NewKeyEvent() 92 + if err != nil { 93 + t.Fatal(err) 94 + } 95 + defer ev.Close() 96 + 97 + // Default should be empty. 98 + if got := ev.UTF8(); got != "" { 99 + t.Fatalf("expected empty, got %q", got) 100 + } 101 + 102 + text := "a" 103 + ev.SetUTF8(text) 104 + if got := ev.UTF8(); got != text { 105 + t.Fatalf("expected %q, got %q", text, got) 106 + } 107 + } 108 + 109 + func TestKeyEventUnshiftedCodepoint(t *testing.T) { 110 + ev, err := NewKeyEvent() 111 + if err != nil { 112 + t.Fatal(err) 113 + } 114 + defer ev.Close() 115 + 116 + ev.SetUnshiftedCodepoint('a') 117 + if got := ev.UnshiftedCodepoint(); got != 'a' { 118 + t.Fatalf("expected 'a', got %c", got) 119 + } 120 + } 121 + 122 + func TestKeyEncoderNewClose(t *testing.T) { 123 + enc, err := NewKeyEncoder() 124 + if err != nil { 125 + t.Fatal(err) 126 + } 127 + defer enc.Close() 128 + } 129 + 130 + func TestKeyEncoderEncodeSimpleKey(t *testing.T) { 131 + enc, err := NewKeyEncoder() 132 + if err != nil { 133 + t.Fatal(err) 134 + } 135 + defer enc.Close() 136 + 137 + ev, err := NewKeyEvent() 138 + if err != nil { 139 + t.Fatal(err) 140 + } 141 + defer ev.Close() 142 + 143 + // Encode 'a' key press with UTF-8 text. 144 + ev.SetAction(KeyActionPress) 145 + ev.SetKey(KeyA) 146 + ev.SetUTF8("a") 147 + 148 + out, err := enc.Encode(ev) 149 + if err != nil { 150 + t.Fatal(err) 151 + } 152 + if len(out) == 0 { 153 + t.Fatal("expected non-empty output for 'a' key press") 154 + } 155 + if string(out) != "a" { 156 + t.Fatalf("expected 'a', got %q", string(out)) 157 + } 158 + } 159 + 160 + func TestKeyEncoderEncodeArrowKey(t *testing.T) { 161 + enc, err := NewKeyEncoder() 162 + if err != nil { 163 + t.Fatal(err) 164 + } 165 + defer enc.Close() 166 + 167 + ev, err := NewKeyEvent() 168 + if err != nil { 169 + t.Fatal(err) 170 + } 171 + defer ev.Close() 172 + 173 + // Arrow up should produce an escape sequence. 174 + ev.SetAction(KeyActionPress) 175 + ev.SetKey(KeyArrowUp) 176 + 177 + out, err := enc.Encode(ev) 178 + if err != nil { 179 + t.Fatal(err) 180 + } 181 + if len(out) == 0 { 182 + t.Fatal("expected non-empty output for arrow up") 183 + } 184 + // Default mode: CSI A (\x1b[A) 185 + if string(out) != "\x1b[A" { 186 + t.Fatalf("expected \\x1b[A, got %q", string(out)) 187 + } 188 + } 189 + 190 + func TestKeyEncoderSetOptFromTerminal(t *testing.T) { 191 + term, err := NewTerminal(WithSize(80, 24)) 192 + if err != nil { 193 + t.Fatal(err) 194 + } 195 + defer term.Close() 196 + 197 + enc, err := NewKeyEncoder() 198 + if err != nil { 199 + t.Fatal(err) 200 + } 201 + defer enc.Close() 202 + 203 + // Should not panic. 204 + enc.SetOptFromTerminal(term) 205 + 206 + ev, err := NewKeyEvent() 207 + if err != nil { 208 + t.Fatal(err) 209 + } 210 + defer ev.Close() 211 + 212 + ev.SetAction(KeyActionPress) 213 + ev.SetKey(KeyA) 214 + ev.SetUTF8("a") 215 + 216 + out, err := enc.Encode(ev) 217 + if err != nil { 218 + t.Fatal(err) 219 + } 220 + if string(out) != "a" { 221 + t.Fatalf("expected 'a', got %q", string(out)) 222 + } 223 + } 224 + 225 + func TestKeyEncoderCursorKeyApplicationMode(t *testing.T) { 226 + enc, err := NewKeyEncoder() 227 + if err != nil { 228 + t.Fatal(err) 229 + } 230 + defer enc.Close() 231 + 232 + // Enable cursor key application mode. 233 + enc.SetOptBool(KeyEncoderOptCursorKeyApplication, true) 234 + 235 + ev, err := NewKeyEvent() 236 + if err != nil { 237 + t.Fatal(err) 238 + } 239 + defer ev.Close() 240 + 241 + ev.SetAction(KeyActionPress) 242 + ev.SetKey(KeyArrowUp) 243 + 244 + out, err := enc.Encode(ev) 245 + if err != nil { 246 + t.Fatal(err) 247 + } 248 + // Application mode: SS3 A (\x1bOA) 249 + if string(out) != "\x1bOA" { 250 + t.Fatalf("expected \\x1bOA in application mode, got %q", string(out)) 251 + } 252 + }
+241
mouse_encoder.go
··· 1 + package libghostty 2 + 3 + // Mouse encoder — encodes mouse events into terminal escape sequences. 4 + // Wraps the C APIs from mouse/encoder.h. 5 + 6 + /* 7 + #include <ghostty/vt.h> 8 + 9 + // Helper to create a properly initialized GhosttyMouseEncoderSize (sized struct). 10 + static inline GhosttyMouseEncoderSize init_mouse_encoder_size() { 11 + GhosttyMouseEncoderSize s = {0}; 12 + s.size = sizeof(GhosttyMouseEncoderSize); 13 + return s; 14 + } 15 + */ 16 + import "C" 17 + 18 + import "unsafe" 19 + 20 + // MouseEncoder encodes mouse events into terminal escape sequences, 21 + // supporting X10, UTF-8, SGR, URxvt, and SGR-Pixels mouse protocols. 22 + // 23 + // Basic usage: 24 + // 1. Create an encoder with NewMouseEncoder. 25 + // 2. Configure options with SetOpt* methods or SetOptFromTerminal. 26 + // 3. Create mouse events, encode them with Encode, and free them. 27 + // 4. Free the encoder with Close when done. 28 + // 29 + // C: GhosttyMouseEncoder 30 + type MouseEncoder struct { 31 + ptr C.GhosttyMouseEncoder 32 + } 33 + 34 + // MouseTrackingMode selects which mouse events the terminal tracks. 35 + // 36 + // C: GhosttyMouseTrackingMode 37 + type MouseTrackingMode int 38 + 39 + const ( 40 + // MouseTrackingNone disables mouse reporting. 41 + MouseTrackingNone MouseTrackingMode = C.GHOSTTY_MOUSE_TRACKING_NONE 42 + 43 + // MouseTrackingX10 enables X10 mouse mode. 44 + MouseTrackingX10 MouseTrackingMode = C.GHOSTTY_MOUSE_TRACKING_X10 45 + 46 + // MouseTrackingNormal enables normal mouse mode (button press/release only). 47 + MouseTrackingNormal MouseTrackingMode = C.GHOSTTY_MOUSE_TRACKING_NORMAL 48 + 49 + // MouseTrackingButton enables button-event tracking mode. 50 + MouseTrackingButton MouseTrackingMode = C.GHOSTTY_MOUSE_TRACKING_BUTTON 51 + 52 + // MouseTrackingAny enables any-event tracking mode. 53 + MouseTrackingAny MouseTrackingMode = C.GHOSTTY_MOUSE_TRACKING_ANY 54 + ) 55 + 56 + // MouseFormat selects the wire format for mouse escape sequences. 57 + // 58 + // C: GhosttyMouseFormat 59 + type MouseFormat int 60 + 61 + const ( 62 + MouseFormatX10 MouseFormat = C.GHOSTTY_MOUSE_FORMAT_X10 63 + MouseFormatUTF8 MouseFormat = C.GHOSTTY_MOUSE_FORMAT_UTF8 64 + MouseFormatSGR MouseFormat = C.GHOSTTY_MOUSE_FORMAT_SGR 65 + MouseFormatURxvt MouseFormat = C.GHOSTTY_MOUSE_FORMAT_URXVT 66 + MouseFormatSGRPixels MouseFormat = C.GHOSTTY_MOUSE_FORMAT_SGR_PIXELS 67 + ) 68 + 69 + // MouseEncoderSize describes the rendered terminal geometry used to 70 + // convert surface-space positions into encoded coordinates. 71 + // 72 + // C: GhosttyMouseEncoderSize 73 + type MouseEncoderSize struct { 74 + // ScreenWidth is the full screen width in pixels. 75 + ScreenWidth uint32 76 + 77 + // ScreenHeight is the full screen height in pixels. 78 + ScreenHeight uint32 79 + 80 + // CellWidth is the cell width in pixels. Must be non-zero. 81 + CellWidth uint32 82 + 83 + // CellHeight is the cell height in pixels. Must be non-zero. 84 + CellHeight uint32 85 + 86 + // PaddingTop is the top padding in pixels. 87 + PaddingTop uint32 88 + 89 + // PaddingBottom is the bottom padding in pixels. 90 + PaddingBottom uint32 91 + 92 + // PaddingRight is the right padding in pixels. 93 + PaddingRight uint32 94 + 95 + // PaddingLeft is the left padding in pixels. 96 + PaddingLeft uint32 97 + } 98 + 99 + // MouseEncoderOption identifies a mouse encoder configuration option 100 + // for use with SetOpt. 101 + // 102 + // C: GhosttyMouseEncoderOption 103 + type MouseEncoderOption int 104 + 105 + const ( 106 + // MouseEncoderOptEvent sets the mouse tracking mode 107 + // (value: MouseTrackingMode). 108 + MouseEncoderOptEvent MouseEncoderOption = C.GHOSTTY_MOUSE_ENCODER_OPT_EVENT 109 + 110 + // MouseEncoderOptFormat sets the mouse output format 111 + // (value: MouseFormat). 112 + MouseEncoderOptFormat MouseEncoderOption = C.GHOSTTY_MOUSE_ENCODER_OPT_FORMAT 113 + 114 + // MouseEncoderOptSize sets the renderer size context 115 + // (value: MouseEncoderSize). 116 + MouseEncoderOptSize MouseEncoderOption = C.GHOSTTY_MOUSE_ENCODER_OPT_SIZE 117 + 118 + // MouseEncoderOptAnyButtonPressed sets whether any mouse button 119 + // is currently pressed (value: bool). 120 + MouseEncoderOptAnyButtonPressed MouseEncoderOption = C.GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED 121 + 122 + // MouseEncoderOptTrackLastCell enables motion deduplication by 123 + // last cell (value: bool). 124 + MouseEncoderOptTrackLastCell MouseEncoderOption = C.GHOSTTY_MOUSE_ENCODER_OPT_TRACK_LAST_CELL 125 + ) 126 + 127 + // NewMouseEncoder creates a new mouse encoder with default options. 128 + // The encoder must be freed with Close when no longer needed. 129 + func NewMouseEncoder() (*MouseEncoder, error) { 130 + var ptr C.GhosttyMouseEncoder 131 + if err := resultError(C.ghostty_mouse_encoder_new(nil, &ptr)); err != nil { 132 + return nil, err 133 + } 134 + return &MouseEncoder{ptr: ptr}, nil 135 + } 136 + 137 + // Close frees the underlying mouse encoder handle. After this call, 138 + // the encoder must not be used. 139 + func (enc *MouseEncoder) Close() { 140 + C.ghostty_mouse_encoder_free(enc.ptr) 141 + } 142 + 143 + // SetOptTrackingMode sets the mouse tracking mode on the encoder. 144 + func (enc *MouseEncoder) SetOptTrackingMode(mode MouseTrackingMode) { 145 + v := C.GhosttyMouseTrackingMode(mode) 146 + C.ghostty_mouse_encoder_setopt(enc.ptr, C.GHOSTTY_MOUSE_ENCODER_OPT_EVENT, unsafe.Pointer(&v)) 147 + } 148 + 149 + // SetOptFormat sets the mouse output format on the encoder. 150 + func (enc *MouseEncoder) SetOptFormat(format MouseFormat) { 151 + v := C.GhosttyMouseFormat(format) 152 + C.ghostty_mouse_encoder_setopt(enc.ptr, C.GHOSTTY_MOUSE_ENCODER_OPT_FORMAT, unsafe.Pointer(&v)) 153 + } 154 + 155 + // SetOptSize sets the renderer size context on the encoder. 156 + func (enc *MouseEncoder) SetOptSize(s MouseEncoderSize) { 157 + cs := C.init_mouse_encoder_size() 158 + cs.screen_width = C.uint32_t(s.ScreenWidth) 159 + cs.screen_height = C.uint32_t(s.ScreenHeight) 160 + cs.cell_width = C.uint32_t(s.CellWidth) 161 + cs.cell_height = C.uint32_t(s.CellHeight) 162 + cs.padding_top = C.uint32_t(s.PaddingTop) 163 + cs.padding_bottom = C.uint32_t(s.PaddingBottom) 164 + cs.padding_right = C.uint32_t(s.PaddingRight) 165 + cs.padding_left = C.uint32_t(s.PaddingLeft) 166 + C.ghostty_mouse_encoder_setopt(enc.ptr, C.GHOSTTY_MOUSE_ENCODER_OPT_SIZE, unsafe.Pointer(&cs)) 167 + } 168 + 169 + // SetOptAnyButtonPressed sets whether any mouse button is currently 170 + // pressed. 171 + func (enc *MouseEncoder) SetOptAnyButtonPressed(pressed bool) { 172 + v := C.bool(pressed) 173 + C.ghostty_mouse_encoder_setopt(enc.ptr, C.GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED, unsafe.Pointer(&v)) 174 + } 175 + 176 + // SetOptTrackLastCell enables or disables motion deduplication by 177 + // last cell. 178 + func (enc *MouseEncoder) SetOptTrackLastCell(track bool) { 179 + v := C.bool(track) 180 + C.ghostty_mouse_encoder_setopt(enc.ptr, C.GHOSTTY_MOUSE_ENCODER_OPT_TRACK_LAST_CELL, unsafe.Pointer(&v)) 181 + } 182 + 183 + // SetOptFromTerminal reads the terminal's current mouse tracking mode 184 + // and output format and applies them to the encoder. It does not 185 + // modify size or any-button state. 186 + func (enc *MouseEncoder) SetOptFromTerminal(t *Terminal) { 187 + C.ghostty_mouse_encoder_setopt_from_terminal(enc.ptr, t.ptr) 188 + } 189 + 190 + // Reset clears internal encoder state such as motion deduplication 191 + // (last tracked cell). 192 + func (enc *MouseEncoder) Reset() { 193 + C.ghostty_mouse_encoder_reset(enc.ptr) 194 + } 195 + 196 + // Encode encodes a mouse event into a terminal escape sequence and 197 + // returns the result as a byte slice. Not all mouse events produce 198 + // output; in that case, a nil slice and nil error are returned. 199 + func (enc *MouseEncoder) Encode(event *MouseEvent) ([]byte, error) { 200 + // Most mouse escape sequences fit in 128 bytes. Try with a stack 201 + // buffer first; fall back to a larger heap allocation if needed. 202 + var buf [128]byte 203 + var outLen C.size_t 204 + result := C.ghostty_mouse_encoder_encode( 205 + enc.ptr, 206 + event.ptr, 207 + (*C.char)(unsafe.Pointer(&buf[0])), 208 + C.size_t(len(buf)), 209 + &outLen, 210 + ) 211 + 212 + if result == C.GHOSTTY_SUCCESS { 213 + if outLen == 0 { 214 + return nil, nil 215 + } 216 + out := make([]byte, outLen) 217 + copy(out, buf[:outLen]) 218 + return out, nil 219 + } 220 + 221 + if result == C.GHOSTTY_OUT_OF_SPACE { 222 + // outLen contains the required buffer size. 223 + dynBuf := make([]byte, outLen) 224 + var written C.size_t 225 + if err := resultError(C.ghostty_mouse_encoder_encode( 226 + enc.ptr, 227 + event.ptr, 228 + (*C.char)(unsafe.Pointer(&dynBuf[0])), 229 + outLen, 230 + &written, 231 + )); err != nil { 232 + return nil, err 233 + } 234 + if written == 0 { 235 + return nil, nil 236 + } 237 + return dynBuf[:written], nil 238 + } 239 + 240 + return nil, &Error{Result: Result(result)} 241 + }
+132
mouse_event.go
··· 1 + package libghostty 2 + 3 + // Mouse event representation and manipulation. 4 + // Wraps the C APIs from mouse/event.h. 5 + 6 + /* 7 + #include <ghostty/vt.h> 8 + */ 9 + import "C" 10 + 11 + // MouseEvent is an opaque handle representing a normalized mouse 12 + // input event containing action, button, modifiers, and surface-space 13 + // position. 14 + // 15 + // C: GhosttyMouseEvent 16 + type MouseEvent struct { 17 + ptr C.GhosttyMouseEvent 18 + } 19 + 20 + // MouseAction represents the type of mouse event (press, release, 21 + // or motion). 22 + // 23 + // C: GhosttyMouseAction 24 + type MouseAction int 25 + 26 + const ( 27 + // MouseActionPress indicates a mouse button was pressed. 28 + MouseActionPress MouseAction = C.GHOSTTY_MOUSE_ACTION_PRESS 29 + 30 + // MouseActionRelease indicates a mouse button was released. 31 + MouseActionRelease MouseAction = C.GHOSTTY_MOUSE_ACTION_RELEASE 32 + 33 + // MouseActionMotion indicates the mouse moved. 34 + MouseActionMotion MouseAction = C.GHOSTTY_MOUSE_ACTION_MOTION 35 + ) 36 + 37 + // MouseButton identifies which mouse button was involved in an event. 38 + // 39 + // C: GhosttyMouseButton 40 + type MouseButton int 41 + 42 + const ( 43 + MouseButtonUnknown MouseButton = C.GHOSTTY_MOUSE_BUTTON_UNKNOWN 44 + MouseButtonLeft MouseButton = C.GHOSTTY_MOUSE_BUTTON_LEFT 45 + MouseButtonRight MouseButton = C.GHOSTTY_MOUSE_BUTTON_RIGHT 46 + MouseButtonMiddle MouseButton = C.GHOSTTY_MOUSE_BUTTON_MIDDLE 47 + MouseButtonFour MouseButton = C.GHOSTTY_MOUSE_BUTTON_FOUR 48 + MouseButtonFive MouseButton = C.GHOSTTY_MOUSE_BUTTON_FIVE 49 + MouseButtonSix MouseButton = C.GHOSTTY_MOUSE_BUTTON_SIX 50 + MouseButtonSeven MouseButton = C.GHOSTTY_MOUSE_BUTTON_SEVEN 51 + MouseButtonEight MouseButton = C.GHOSTTY_MOUSE_BUTTON_EIGHT 52 + MouseButtonNine MouseButton = C.GHOSTTY_MOUSE_BUTTON_NINE 53 + MouseButtonTen MouseButton = C.GHOSTTY_MOUSE_BUTTON_TEN 54 + MouseButtonEleven MouseButton = C.GHOSTTY_MOUSE_BUTTON_ELEVEN 55 + ) 56 + 57 + // MousePosition represents a mouse position in surface-space pixels. 58 + // 59 + // C: GhosttyMousePosition 60 + type MousePosition struct { 61 + X float32 62 + Y float32 63 + } 64 + 65 + // NewMouseEvent creates a new mouse event with default values. The 66 + // event must be freed with Close when no longer needed. 67 + func NewMouseEvent() (*MouseEvent, error) { 68 + var ptr C.GhosttyMouseEvent 69 + if err := resultError(C.ghostty_mouse_event_new(nil, &ptr)); err != nil { 70 + return nil, err 71 + } 72 + return &MouseEvent{ptr: ptr}, nil 73 + } 74 + 75 + // Close frees the underlying mouse event handle. After this call, 76 + // the mouse event must not be used. 77 + func (e *MouseEvent) Close() { 78 + C.ghostty_mouse_event_free(e.ptr) 79 + } 80 + 81 + // SetAction sets the mouse action (press, release, motion). 82 + func (e *MouseEvent) SetAction(action MouseAction) { 83 + C.ghostty_mouse_event_set_action(e.ptr, C.GhosttyMouseAction(action)) 84 + } 85 + 86 + // Action returns the mouse action (press, release, motion). 87 + func (e *MouseEvent) Action() MouseAction { 88 + return MouseAction(C.ghostty_mouse_event_get_action(e.ptr)) 89 + } 90 + 91 + // SetButton sets the mouse button for the event. 92 + func (e *MouseEvent) SetButton(button MouseButton) { 93 + C.ghostty_mouse_event_set_button(e.ptr, C.GhosttyMouseButton(button)) 94 + } 95 + 96 + // ClearButton clears the event button, setting it to "none". 97 + // Use this for motion events with no button pressed. 98 + func (e *MouseEvent) ClearButton() { 99 + C.ghostty_mouse_event_clear_button(e.ptr) 100 + } 101 + 102 + // Button returns the mouse button and whether one is set. If no 103 + // button is set (e.g. a motion-only event), ok is false. 104 + func (e *MouseEvent) Button() (button MouseButton, ok bool) { 105 + var b C.GhosttyMouseButton 106 + ok = bool(C.ghostty_mouse_event_get_button(e.ptr, &b)) 107 + return MouseButton(b), ok 108 + } 109 + 110 + // SetMods sets the keyboard modifiers held during the mouse event. 111 + func (e *MouseEvent) SetMods(mods Mods) { 112 + C.ghostty_mouse_event_set_mods(e.ptr, C.GhosttyMods(mods)) 113 + } 114 + 115 + // Mods returns the keyboard modifiers held during the mouse event. 116 + func (e *MouseEvent) Mods() Mods { 117 + return Mods(C.ghostty_mouse_event_get_mods(e.ptr)) 118 + } 119 + 120 + // SetPosition sets the event position in surface-space pixels. 121 + func (e *MouseEvent) SetPosition(pos MousePosition) { 122 + C.ghostty_mouse_event_set_position(e.ptr, C.GhosttyMousePosition{ 123 + x: C.float(pos.X), 124 + y: C.float(pos.Y), 125 + }) 126 + } 127 + 128 + // Position returns the event position in surface-space pixels. 129 + func (e *MouseEvent) Position() MousePosition { 130 + p := C.ghostty_mouse_event_get_position(e.ptr) 131 + return MousePosition{X: float32(p.x), Y: float32(p.y)} 132 + }
+165
mouse_test.go
··· 1 + package libghostty 2 + 3 + import ( 4 + "testing" 5 + ) 6 + 7 + func TestMouseEventNewClose(t *testing.T) { 8 + ev, err := NewMouseEvent() 9 + if err != nil { 10 + t.Fatal(err) 11 + } 12 + defer ev.Close() 13 + } 14 + 15 + func TestMouseEventAction(t *testing.T) { 16 + ev, err := NewMouseEvent() 17 + if err != nil { 18 + t.Fatal(err) 19 + } 20 + defer ev.Close() 21 + 22 + ev.SetAction(MouseActionPress) 23 + if got := ev.Action(); got != MouseActionPress { 24 + t.Fatalf("expected MouseActionPress, got %d", got) 25 + } 26 + 27 + ev.SetAction(MouseActionRelease) 28 + if got := ev.Action(); got != MouseActionRelease { 29 + t.Fatalf("expected MouseActionRelease, got %d", got) 30 + } 31 + 32 + ev.SetAction(MouseActionMotion) 33 + if got := ev.Action(); got != MouseActionMotion { 34 + t.Fatalf("expected MouseActionMotion, got %d", got) 35 + } 36 + } 37 + 38 + func TestMouseEventButton(t *testing.T) { 39 + ev, err := NewMouseEvent() 40 + if err != nil { 41 + t.Fatal(err) 42 + } 43 + defer ev.Close() 44 + 45 + ev.SetButton(MouseButtonLeft) 46 + btn, ok := ev.Button() 47 + if !ok { 48 + t.Fatal("expected button to be set") 49 + } 50 + if btn != MouseButtonLeft { 51 + t.Fatalf("expected MouseButtonLeft, got %d", btn) 52 + } 53 + 54 + ev.ClearButton() 55 + _, ok = ev.Button() 56 + if ok { 57 + t.Fatal("expected button to be cleared") 58 + } 59 + } 60 + 61 + func TestMouseEventMods(t *testing.T) { 62 + ev, err := NewMouseEvent() 63 + if err != nil { 64 + t.Fatal(err) 65 + } 66 + defer ev.Close() 67 + 68 + ev.SetMods(ModCtrl | ModShift) 69 + if got := ev.Mods(); got != ModCtrl|ModShift { 70 + t.Fatalf("expected Ctrl|Shift, got %d", got) 71 + } 72 + } 73 + 74 + func TestMouseEventPosition(t *testing.T) { 75 + ev, err := NewMouseEvent() 76 + if err != nil { 77 + t.Fatal(err) 78 + } 79 + defer ev.Close() 80 + 81 + pos := MousePosition{X: 10.5, Y: 20.75} 82 + ev.SetPosition(pos) 83 + got := ev.Position() 84 + if got.X != pos.X || got.Y != pos.Y { 85 + t.Fatalf("expected %+v, got %+v", pos, got) 86 + } 87 + } 88 + 89 + func TestMouseEncoderNewClose(t *testing.T) { 90 + enc, err := NewMouseEncoder() 91 + if err != nil { 92 + t.Fatal(err) 93 + } 94 + defer enc.Close() 95 + } 96 + 97 + func TestMouseEncoderEncodeNormalSGR(t *testing.T) { 98 + enc, err := NewMouseEncoder() 99 + if err != nil { 100 + t.Fatal(err) 101 + } 102 + defer enc.Close() 103 + 104 + // Configure for normal tracking with SGR format. 105 + enc.SetOptTrackingMode(MouseTrackingNormal) 106 + enc.SetOptFormat(MouseFormatSGR) 107 + enc.SetOptSize(MouseEncoderSize{ 108 + ScreenWidth: 640, 109 + ScreenHeight: 480, 110 + CellWidth: 8, 111 + CellHeight: 16, 112 + }) 113 + 114 + ev, err := NewMouseEvent() 115 + if err != nil { 116 + t.Fatal(err) 117 + } 118 + defer ev.Close() 119 + 120 + // Left button press at cell (1,1) — position (0,0) in pixels. 121 + ev.SetAction(MouseActionPress) 122 + ev.SetButton(MouseButtonLeft) 123 + ev.SetPosition(MousePosition{X: 0, Y: 0}) 124 + 125 + out, err := enc.Encode(ev) 126 + if err != nil { 127 + t.Fatal(err) 128 + } 129 + if len(out) == 0 { 130 + t.Fatal("expected non-empty output for mouse press") 131 + } 132 + // SGR press format: \x1b[<0;1;1M 133 + expected := "\x1b[<0;1;1M" 134 + if string(out) != expected { 135 + t.Fatalf("expected %q, got %q", expected, string(out)) 136 + } 137 + } 138 + 139 + func TestMouseEncoderReset(t *testing.T) { 140 + enc, err := NewMouseEncoder() 141 + if err != nil { 142 + t.Fatal(err) 143 + } 144 + defer enc.Close() 145 + 146 + // Should not panic. 147 + enc.Reset() 148 + } 149 + 150 + func TestMouseEncoderSetOptFromTerminal(t *testing.T) { 151 + term, err := NewTerminal(WithSize(80, 24)) 152 + if err != nil { 153 + t.Fatal(err) 154 + } 155 + defer term.Close() 156 + 157 + enc, err := NewMouseEncoder() 158 + if err != nil { 159 + t.Fatal(err) 160 + } 161 + defer enc.Close() 162 + 163 + // Should not panic. 164 + enc.SetOptFromTerminal(term) 165 + }
-31
terminal.go
··· 367 367 Len uint64 368 368 } 369 369 370 - // KittyKeyFlags holds the current Kitty keyboard protocol flags. 371 - // These can be combined using bitwise OR. 372 - // C: GhosttyKittyKeyFlags (uint8_t) 373 - type KittyKeyFlags uint8 374 - 375 - // Kitty keyboard protocol flag constants. 376 - const ( 377 - // KittyKeyDisabled means the Kitty keyboard protocol is disabled. 378 - KittyKeyDisabled KittyKeyFlags = C.GHOSTTY_KITTY_KEY_DISABLED 379 - 380 - // KittyKeyDisambiguate enables disambiguating escape codes. 381 - KittyKeyDisambiguate KittyKeyFlags = C.GHOSTTY_KITTY_KEY_DISAMBIGUATE 382 - 383 - // KittyKeyReportEvents enables reporting key press and release events. 384 - KittyKeyReportEvents KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_EVENTS 385 - 386 - // KittyKeyReportAlternates enables reporting alternate key codes. 387 - KittyKeyReportAlternates KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ALTERNATES 388 - 389 - // KittyKeyReportAll enables reporting all key events including those 390 - // normally handled by the terminal. 391 - KittyKeyReportAll KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ALL 392 - 393 - // KittyKeyReportAssociated enables reporting associated text with 394 - // key events. 395 - KittyKeyReportAssociated KittyKeyFlags = C.GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED 396 - 397 - // KittyKeyAll has all Kitty keyboard protocol flags enabled. 398 - KittyKeyAll KittyKeyFlags = C.GHOSTTY_KITTY_KEY_ALL 399 - ) 400 - 401 370 // KittyGraphics returns the Kitty graphics image storage for the 402 371 // terminal's active screen. The returned handle is borrowed from 403 372 // the terminal and remains valid until the next mutating call