Files
cthulhu/internal/app/wayland_native_test.go

102 lines
3.0 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"log/slog"
"os"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"github.com/godbus/dbus/v5"
)
type RuntimeKeyboardMonitor struct{ ready chan struct{} }
type RuntimeKeyGrab struct{ Symbol, Modifiers uint32 }
func (*RuntimeKeyboardMonitor) WatchKeyboard() *dbus.Error { return nil }
func (monitor *RuntimeKeyboardMonitor) SetKeyGrabs([]uint32, []RuntimeKeyGrab) *dbus.Error {
select {
case monitor.ready <- struct{}{}:
default:
}
return nil
}
func TestNativeWaylandRuntimeKeyboardPreferencesAndQuit(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_DBUS_TEST") != "1" {
t.Skip("requires private dbus-run-session")
}
t.Setenv("XDG_SESSION_TYPE", "wayland")
t.Setenv("DISPLAY", "")
connection, err := dbus.ConnectSessionBus()
if err != nil {
t.Fatal(err)
}
defer connection.Close()
const managerName = "org.freedesktop.a11y.Manager"
const managerPath = dbus.ObjectPath("/org/freedesktop/a11y/Manager")
const monitorInterface = "org.freedesktop.a11y.KeyboardMonitor"
monitor := &RuntimeKeyboardMonitor{ready: make(chan struct{}, 1)}
if err := connection.Export(monitor, managerPath, monitorInterface); err != nil {
t.Fatal(err)
}
if result, err := connection.RequestName(managerName, dbus.NameFlagDoNotQueue); err != nil || result != dbus.RequestNameReplyPrimaryOwner {
t.Fatalf("keyboard manager: %v %v", result, err)
}
speaker := &fakeSpeaker{spoken: make(chan string, 32)}
adapter := &fakeAdapter{events: make(chan atspi.Event, 1)}
preferences := make(chan struct{}, 1)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
completed := make(chan error, 1)
go func() {
completed <- Run(ctx, Options{
Logger: slog.New(slog.DiscardHandler),
newSpeaker: func() Speaker { return speaker }, newSound: func() soundService { return silentSound{} },
newAdapter: func() Adapter { return adapter }, onPreferences: func() { preferences <- struct{}{} },
})
}()
select {
case <-monitor.ready:
case <-time.After(3 * time.Second):
t.Fatal("runtime did not select shared keyboard backend")
}
key := func(symbol uint32, code uint16, released bool) {
t.Helper()
if err := connection.Emit(managerPath, monitorInterface+".KeyEvent", released, uint32(0), symbol, uint32(0), code); err != nil {
t.Fatal(err)
}
}
key(0xff63, 118, false)
key(' ', 65, false)
key(' ', 65, true)
key(0xff63, 118, true)
select {
case <-preferences:
case err := <-completed:
t.Fatalf("runtime exited before preferences: %v", err)
case <-time.After(3 * time.Second):
t.Fatal("Wayland preferences shortcut was not dispatched")
}
key(0xff63, 118, false)
key('q', 24, false)
key(0xff63, 118, true)
key('q', 24, true)
select {
case err := <-completed:
if err != nil {
t.Fatal(err)
}
case <-time.After(4 * time.Second):
t.Fatal("Wayland quit shortcut did not stop runtime")
}
if !adapter.stopped || !speaker.closed {
t.Fatal("Wayland runtime shutdown leaked services")
}
}