102 lines
3.0 KiB
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")
|
|
}
|
|
}
|