Go bindings for libghostty-vt.
0
fork

Configure Feed

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

Merge pull request #1 from mitchellh/push-mznpmxruukmo

sys: add log callback configuration

authored by

Mitchell Hashimoto and committed by
GitHub
90eeccdc feba420c

+199 -1
+1 -1
CMakeLists.txt
··· 4 4 include(FetchContent) 5 5 FetchContent_Declare(ghostty 6 6 GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git 7 - GIT_TAG 48a01b8bd51b0cf4ba3ed281a6662ae131ee8239 7 + GIT_TAG c34901dddbc36b63f33bbb1a47d62f6911584d65 8 8 ) 9 9 FetchContent_MakeAvailable(ghostty)
+140
sys.go
··· 1 + package libghostty 2 + 3 + // System-level configuration wrapping ghostty_sys_set(). 4 + // These are process-global settings that must be configured at startup. 5 + 6 + /* 7 + #include <ghostty/vt.h> 8 + #include <ghostty/vt/sys.h> 9 + 10 + // Forward declaration for the Go log trampoline so we can take its 11 + // address on the C side. Uses compatible types (no const, int for enum) 12 + // to match what cgo generates for the //export function. 13 + extern void goSysLogTrampoline( 14 + void* userdata, 15 + int level, 16 + uint8_t* scope, 17 + size_t scope_len, 18 + uint8_t* message, 19 + size_t message_len); 20 + 21 + // Helper to install the Go log trampoline via ghostty_sys_set. 22 + // We need this because cgo cannot take the address of a Go-exported 23 + // function directly as a C function pointer. 24 + static inline GhosttyResult sys_set_log_go(void) { 25 + return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, (const void*)goSysLogTrampoline); 26 + } 27 + 28 + // Helper to install the built-in stderr log callback. 29 + static inline GhosttyResult sys_set_log_stderr(void) { 30 + return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, (const void*)ghostty_sys_log_stderr); 31 + } 32 + 33 + // Helper to clear the log callback. 34 + static inline GhosttyResult sys_clear_log(void) { 35 + return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, NULL); 36 + } 37 + */ 38 + import "C" 39 + 40 + import "unsafe" 41 + 42 + // SysLogLevel represents the severity level of a log message from the 43 + // library. Maps directly to the C enum values. 44 + // C: GhosttySysLogLevel 45 + type SysLogLevel int 46 + 47 + const ( 48 + // SysLogLevelError is the error log level. 49 + SysLogLevelError SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_ERROR 50 + 51 + // SysLogLevelWarning is the warning log level. 52 + SysLogLevelWarning SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_WARNING 53 + 54 + // SysLogLevelInfo is the info log level. 55 + SysLogLevelInfo SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_INFO 56 + 57 + // SysLogLevelDebug is the debug log level. 58 + SysLogLevelDebug SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_DEBUG 59 + ) 60 + 61 + // String returns a human-readable name for the log level. 62 + func (l SysLogLevel) String() string { 63 + switch l { 64 + case SysLogLevelError: 65 + return "error" 66 + case SysLogLevelWarning: 67 + return "warning" 68 + case SysLogLevelInfo: 69 + return "info" 70 + case SysLogLevelDebug: 71 + return "debug" 72 + default: 73 + return "unknown" 74 + } 75 + } 76 + 77 + // SysLogFn is the Go callback type for log messages from the library. 78 + // The scope identifies the subsystem (e.g. "osc", "kitty"); it is 79 + // empty for unscoped (default) log messages. The message and scope 80 + // are only valid for the duration of the call. 81 + // C: GhosttySysLogFn 82 + type SysLogFn func(level SysLogLevel, scope string, message string) 83 + 84 + // sysLogFn is the currently installed Go log callback. 85 + var sysLogFn SysLogFn 86 + 87 + // SysSetLog installs a Go callback that receives internal library log 88 + // messages. Pass nil to clear the callback and discard log messages. 89 + // 90 + // Which log levels are emitted depends on the build mode of the 91 + // library. Debug builds emit all levels; release builds emit info 92 + // and above. 93 + // 94 + // This function is not safe for concurrent use. Callers must ensure 95 + // that log configuration is not modified while log messages may be 96 + // delivered (e.g. configure at startup before creating terminals). 97 + func SysSetLog(fn SysLogFn) error { 98 + sysLogFn = fn 99 + if fn == nil { 100 + return resultError(C.sys_clear_log()) 101 + } 102 + return resultError(C.sys_set_log_go()) 103 + } 104 + 105 + // SysSetLogStderr installs the built-in stderr log callback provided 106 + // by libghostty. Each message is formatted as "[level](scope): message\n" 107 + // and written to stderr in a thread-safe manner. 108 + // 109 + // This function is not safe for concurrent use. See [SysSetLog]. 110 + func SysSetLogStderr() error { 111 + sysLogFn = nil 112 + return resultError(C.sys_set_log_stderr()) 113 + } 114 + 115 + //export goSysLogTrampoline 116 + func goSysLogTrampoline( 117 + _ unsafe.Pointer, 118 + level C.int, 119 + scopePtr *C.uint8_t, 120 + scopeLen C.size_t, 121 + messagePtr *C.uint8_t, 122 + messageLen C.size_t, 123 + ) { 124 + fn := sysLogFn 125 + if fn == nil { 126 + return 127 + } 128 + 129 + var scope string 130 + if scopeLen > 0 { 131 + scope = C.GoStringN((*C.char)(unsafe.Pointer(scopePtr)), C.int(scopeLen)) 132 + } 133 + 134 + var message string 135 + if messageLen > 0 { 136 + message = C.GoStringN((*C.char)(unsafe.Pointer(messagePtr)), C.int(messageLen)) 137 + } 138 + 139 + fn(SysLogLevel(level), scope, message) 140 + }
+58
sys_test.go
··· 1 + package libghostty 2 + 3 + import "testing" 4 + 5 + func TestSysLogLevelString(t *testing.T) { 6 + tests := []struct { 7 + level SysLogLevel 8 + want string 9 + }{ 10 + {SysLogLevelError, "error"}, 11 + {SysLogLevelWarning, "warning"}, 12 + {SysLogLevelInfo, "info"}, 13 + {SysLogLevelDebug, "debug"}, 14 + {SysLogLevel(99), "unknown"}, 15 + } 16 + 17 + for _, tt := range tests { 18 + if got := tt.level.String(); got != tt.want { 19 + t.Errorf("SysLogLevel(%d).String() = %q, want %q", tt.level, got, tt.want) 20 + } 21 + } 22 + } 23 + 24 + func TestSysSetLogNil(t *testing.T) { 25 + // Clearing with nil should always succeed. 26 + if err := SysSetLog(nil); err != nil { 27 + t.Fatalf("SysSetLog(nil) = %v, want nil", err) 28 + } 29 + } 30 + 31 + func TestSysSetLogStderr(t *testing.T) { 32 + if err := SysSetLogStderr(); err != nil { 33 + t.Fatalf("SysSetLogStderr() = %v, want nil", err) 34 + } 35 + 36 + // Clean up. 37 + if err := SysSetLog(nil); err != nil { 38 + t.Fatalf("SysSetLog(nil) = %v, want nil", err) 39 + } 40 + } 41 + 42 + func TestSysSetLogCallback(t *testing.T) { 43 + // Install a Go callback and verify it can be set and cleared. 44 + called := false 45 + if err := SysSetLog(func(level SysLogLevel, scope string, message string) { 46 + called = true 47 + }); err != nil { 48 + t.Fatalf("SysSetLog(fn) = %v, want nil", err) 49 + } 50 + 51 + // We can't easily trigger an internal log message, but we can 52 + // verify the callback was installed by clearing it. 53 + _ = called 54 + 55 + if err := SysSetLog(nil); err != nil { 56 + t.Fatalf("SysSetLog(nil) = %v, want nil", err) 57 + } 58 + }