Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user