···11+package libghostty
22+33+// System-level configuration wrapping ghostty_sys_set().
44+// These are process-global settings that must be configured at startup.
55+66+/*
77+#include <ghostty/vt.h>
88+#include <ghostty/vt/sys.h>
99+1010+// Forward declaration for the Go log trampoline so we can take its
1111+// address on the C side. Uses compatible types (no const, int for enum)
1212+// to match what cgo generates for the //export function.
1313+extern void goSysLogTrampoline(
1414+ void* userdata,
1515+ int level,
1616+ uint8_t* scope,
1717+ size_t scope_len,
1818+ uint8_t* message,
1919+ size_t message_len);
2020+2121+// Helper to install the Go log trampoline via ghostty_sys_set.
2222+// We need this because cgo cannot take the address of a Go-exported
2323+// function directly as a C function pointer.
2424+static inline GhosttyResult sys_set_log_go(void) {
2525+ return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, (const void*)goSysLogTrampoline);
2626+}
2727+2828+// Helper to install the built-in stderr log callback.
2929+static inline GhosttyResult sys_set_log_stderr(void) {
3030+ return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, (const void*)ghostty_sys_log_stderr);
3131+}
3232+3333+// Helper to clear the log callback.
3434+static inline GhosttyResult sys_clear_log(void) {
3535+ return ghostty_sys_set(GHOSTTY_SYS_OPT_LOG, NULL);
3636+}
3737+*/
3838+import "C"
3939+4040+import "unsafe"
4141+4242+// SysLogLevel represents the severity level of a log message from the
4343+// library. Maps directly to the C enum values.
4444+// C: GhosttySysLogLevel
4545+type SysLogLevel int
4646+4747+const (
4848+ // SysLogLevelError is the error log level.
4949+ SysLogLevelError SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_ERROR
5050+5151+ // SysLogLevelWarning is the warning log level.
5252+ SysLogLevelWarning SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_WARNING
5353+5454+ // SysLogLevelInfo is the info log level.
5555+ SysLogLevelInfo SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_INFO
5656+5757+ // SysLogLevelDebug is the debug log level.
5858+ SysLogLevelDebug SysLogLevel = C.GHOSTTY_SYS_LOG_LEVEL_DEBUG
5959+)
6060+6161+// String returns a human-readable name for the log level.
6262+func (l SysLogLevel) String() string {
6363+ switch l {
6464+ case SysLogLevelError:
6565+ return "error"
6666+ case SysLogLevelWarning:
6767+ return "warning"
6868+ case SysLogLevelInfo:
6969+ return "info"
7070+ case SysLogLevelDebug:
7171+ return "debug"
7272+ default:
7373+ return "unknown"
7474+ }
7575+}
7676+7777+// SysLogFn is the Go callback type for log messages from the library.
7878+// The scope identifies the subsystem (e.g. "osc", "kitty"); it is
7979+// empty for unscoped (default) log messages. The message and scope
8080+// are only valid for the duration of the call.
8181+// C: GhosttySysLogFn
8282+type SysLogFn func(level SysLogLevel, scope string, message string)
8383+8484+// sysLogFn is the currently installed Go log callback.
8585+var sysLogFn SysLogFn
8686+8787+// SysSetLog installs a Go callback that receives internal library log
8888+// messages. Pass nil to clear the callback and discard log messages.
8989+//
9090+// Which log levels are emitted depends on the build mode of the
9191+// library. Debug builds emit all levels; release builds emit info
9292+// and above.
9393+//
9494+// This function is not safe for concurrent use. Callers must ensure
9595+// that log configuration is not modified while log messages may be
9696+// delivered (e.g. configure at startup before creating terminals).
9797+func SysSetLog(fn SysLogFn) error {
9898+ sysLogFn = fn
9999+ if fn == nil {
100100+ return resultError(C.sys_clear_log())
101101+ }
102102+ return resultError(C.sys_set_log_go())
103103+}
104104+105105+// SysSetLogStderr installs the built-in stderr log callback provided
106106+// by libghostty. Each message is formatted as "[level](scope): message\n"
107107+// and written to stderr in a thread-safe manner.
108108+//
109109+// This function is not safe for concurrent use. See [SysSetLog].
110110+func SysSetLogStderr() error {
111111+ sysLogFn = nil
112112+ return resultError(C.sys_set_log_stderr())
113113+}
114114+115115+//export goSysLogTrampoline
116116+func goSysLogTrampoline(
117117+ _ unsafe.Pointer,
118118+ level C.int,
119119+ scopePtr *C.uint8_t,
120120+ scopeLen C.size_t,
121121+ messagePtr *C.uint8_t,
122122+ messageLen C.size_t,
123123+) {
124124+ fn := sysLogFn
125125+ if fn == nil {
126126+ return
127127+ }
128128+129129+ var scope string
130130+ if scopeLen > 0 {
131131+ scope = C.GoStringN((*C.char)(unsafe.Pointer(scopePtr)), C.int(scopeLen))
132132+ }
133133+134134+ var message string
135135+ if messageLen > 0 {
136136+ message = C.GoStringN((*C.char)(unsafe.Pointer(messagePtr)), C.int(messageLen))
137137+ }
138138+139139+ fn(SysLogLevel(level), scope, message)
140140+}
+58
sys_test.go
···11+package libghostty
22+33+import "testing"
44+55+func TestSysLogLevelString(t *testing.T) {
66+ tests := []struct {
77+ level SysLogLevel
88+ want string
99+ }{
1010+ {SysLogLevelError, "error"},
1111+ {SysLogLevelWarning, "warning"},
1212+ {SysLogLevelInfo, "info"},
1313+ {SysLogLevelDebug, "debug"},
1414+ {SysLogLevel(99), "unknown"},
1515+ }
1616+1717+ for _, tt := range tests {
1818+ if got := tt.level.String(); got != tt.want {
1919+ t.Errorf("SysLogLevel(%d).String() = %q, want %q", tt.level, got, tt.want)
2020+ }
2121+ }
2222+}
2323+2424+func TestSysSetLogNil(t *testing.T) {
2525+ // Clearing with nil should always succeed.
2626+ if err := SysSetLog(nil); err != nil {
2727+ t.Fatalf("SysSetLog(nil) = %v, want nil", err)
2828+ }
2929+}
3030+3131+func TestSysSetLogStderr(t *testing.T) {
3232+ if err := SysSetLogStderr(); err != nil {
3333+ t.Fatalf("SysSetLogStderr() = %v, want nil", err)
3434+ }
3535+3636+ // Clean up.
3737+ if err := SysSetLog(nil); err != nil {
3838+ t.Fatalf("SysSetLog(nil) = %v, want nil", err)
3939+ }
4040+}
4141+4242+func TestSysSetLogCallback(t *testing.T) {
4343+ // Install a Go callback and verify it can be set and cleared.
4444+ called := false
4545+ if err := SysSetLog(func(level SysLogLevel, scope string, message string) {
4646+ called = true
4747+ }); err != nil {
4848+ t.Fatalf("SysSetLog(fn) = %v, want nil", err)
4949+ }
5050+5151+ // We can't easily trigger an internal log message, but we can
5252+ // verify the callback was installed by clearing it.
5353+ _ = called
5454+5555+ if err := SysSetLog(nil); err != nil {
5656+ t.Fatalf("SysSetLog(nil) = %v, want nil", err)
5757+ }
5858+}