Cthulhu is no longer a fork. Now it is a screen reader written in Golang.

This commit is contained in:
Storm Dragon committed 2026-10-14 00:16:34 -04:00
1 parent a998fe67de
commit 069fc674dc
708 files changed
+112482 -532

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
// Package sessionbus selects the same desktop session bus as GLib clients,
// including Python Cthulhu and I38lock, without changing the process environment.
package sessionbus
/*
#cgo pkg-config: gio-2.0
#include <gio/gio.h>
*/
import "C"
import (
"context"
"fmt"
"log/slog"
"time"
"unsafe"
"github.com/godbus/dbus/v5"
)
func sessionAddress() (string, error) {
var nativeError *C.GError
address := C.g_dbus_address_get_for_bus_sync(C.G_BUS_TYPE_SESSION, nil, &nativeError)
if nativeError != nil {
defer C.g_error_free(nativeError)
return "", fmt.Errorf("resolve desktop session bus: %s", C.GoString(nativeError.message))
}
if address == nil {
return "", fmt.Errorf("resolve desktop session bus: no address")
}
defer C.g_free(C.gpointer(unsafe.Pointer(address)))
return C.GoString(address), nil
}
// Connect uses GLib's session address discovery and godbus's private connection.
// Bus IDs let debug logs identify mismatched buses without exposing addresses.
func Connect(logger *slog.Logger, component string) (*dbus.Conn, error) {
address, err := sessionAddress()
if err != nil {
return nil, err
}
connection, err := dbus.Connect(address)
if err != nil {
return nil, err
}
if logger != nil && logger.Enabled(context.Background(), slog.LevelDebug) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
var busID string
if err := connection.BusObject().CallWithContext(ctx, "org.freedesktop.DBus.GetId", 0).Store(&busID); err != nil {
logger.Debug("query session bus identity", "component", component, "error", err)
} else {
logger.Debug("connected to session bus", "component", component, "bus_id", busID)
}
}
return connection, nil
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package sessionbus
import (
"bufio"
"bytes"
"context"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/godbus/dbus/v5"
)
func TestSessionBusHelper(t *testing.T) {
expectedID := os.Getenv("CTHULHU_SESSIONBUS_EXPECTED_ID")
if expectedID == "" {
t.Skip("subprocess helper")
}
before, present := os.LookupEnv("DBUS_SESSION_BUS_ADDRESS")
var output bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&output, &slog.HandlerOptions{Level: slog.LevelDebug}))
connection, err := Connect(logger, "test")
if err != nil {
t.Fatal(err)
}
defer connection.Close()
var busID string
if err := connection.BusObject().Call("org.freedesktop.DBus.GetId", 0).Store(&busID); err != nil {
t.Fatal(err)
}
if busID != expectedID {
t.Fatalf("selected bus %q, want %q", busID, expectedID)
}
if after, exists := os.LookupEnv("DBUS_SESSION_BUS_ADDRESS"); after != before || exists != present {
t.Fatal("session discovery changed process environment")
}
if !strings.Contains(output.String(), `"bus_id":"`+expectedID+`"`) {
t.Fatal("missing bus identity diagnostic")
}
if strings.Contains(output.String(), os.Getenv("XDG_RUNTIME_DIR")) {
t.Fatal("diagnostic exposes runtime directory")
}
}
func TestNativeDesktopSessionBusDiscovery(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_DBUS_TEST") != "1" {
t.Skip("requires private session bus sockets")
}
startBus := func() (string, string, string) {
runtimeDirectory := t.TempDir()
process := exec.Command("dbus-daemon", "--session", "--nofork", "--address=unix:path="+filepath.Join(runtimeDirectory, "bus"), "--print-address=1")
stdout, err := process.StdoutPipe()
if err != nil {
t.Fatal(err)
}
if err := process.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { process.Process.Kill(); process.Wait() })
address, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
connection, err := dbus.Connect(strings.TrimSpace(address))
if err != nil {
t.Fatal(err)
}
defer connection.Close()
var busID string
if err := connection.BusObject().CallWithContext(context.Background(), "org.freedesktop.DBus.GetId", 0).Store(&busID); err != nil {
t.Fatal(err)
}
return runtimeDirectory, strings.TrimSpace(address), busID
}
runtimeDirectory, _, runtimeID := startBus()
_, explicitAddress, explicitID := startBus()
executable, err := os.Executable()
if err != nil {
t.Fatal(err)
}
for _, explicit := range []bool{false, true} {
expectedID := runtimeID
environment := make([]string, 0)
for _, value := range os.Environ() {
if !strings.HasPrefix(value, "DBUS_SESSION_BUS_ADDRESS=") && !strings.HasPrefix(value, "XDG_RUNTIME_DIR=") {
environment = append(environment, value)
}
}
if explicit {
environment = append(environment, "DBUS_SESSION_BUS_ADDRESS="+explicitAddress)
expectedID = explicitID
}
environment = append(environment, "XDG_RUNTIME_DIR="+runtimeDirectory, "CTHULHU_SESSIONBUS_EXPECTED_ID="+expectedID)
process := exec.Command(executable, "-test.run=^TestSessionBusHelper$", "-test.v")
process.Env = environment
if output, err := process.CombinedOutput(); err != nil {
t.Fatalf("explicit=%v: %v\n%s", explicit, err, output)
}
}
}