Files
cthulhu/internal/atspi/window_title_native_test.go

101 lines
3.0 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"bufio"
"context"
"encoding/json"
"os"
"os/exec"
"testing"
"time"
)
func TestNativeForegroundTitlePublication(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_WINDOW_TITLES") != "1" {
t.Skip("private Xvfb and D-Bus fixture only")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
fixture := exec.CommandContext(ctx, "python3", "-u", "../../tests/window_title_fixture.py")
stdin, err := fixture.StdinPipe()
if err != nil {
t.Fatal(err)
}
stdout, err := fixture.StdoutPipe()
if err != nil {
t.Fatal(err)
}
fixture.Stderr = os.Stderr
if err = fixture.Start(); err != nil {
t.Fatal(err)
}
defer func() { stdin.Close(); fixture.Wait() }()
scanner := bufio.NewScanner(stdout)
if !scanner.Scan() {
t.Fatal("fixture did not start")
}
var windows map[string]uint64
if err = json.Unmarshal(scanner.Bytes(), &windows); err != nil {
t.Fatal(err)
}
adapter := New()
if err = adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
waitTitle := func(window, child uint64, text string) ForegroundTitle {
t.Helper()
for {
value := adapter.ForegroundTitle()
if value.Title.Window == window && value.Title.TitleWindow == child && value.Title.Title == text {
return value
}
select {
case <-adapter.TitleChanges():
case <-ctx.Done():
t.Fatalf("missing title %q: %+v", text, value)
}
}
}
command := func(action, window, title string) {
t.Helper()
data, _ := json.Marshal(map[string]string{"action": action, "window": window, "title": title})
if _, err = stdin.Write(append(data, '\n')); err != nil {
t.Fatal(err)
}
if !scanner.Scan() || scanner.Text() != "ready" {
t.Fatal("fixture command failed")
}
}
initial := waitTitle(windows["normal"], windows["normal"], "Example title")
command("title", "normal", "Changed without AT-SPI")
changed := waitTitle(windows["normal"], windows["normal"], "Changed without AT-SPI")
if changed.Epoch != initial.Epoch {
t.Fatal("title change reset foreground ownership")
}
command("activate", "desktop", "")
command("focus", "control", "")
wine := waitTitle(windows["desktop"], windows["game"], "Wine game")
if wine.Epoch == initial.Epoch || wine.Title.Application != "game.exe" {
t.Fatalf("Wine ownership = %+v", wine)
}
if !wine.Title.Wine || !wine.Title.Focused || !wine.Title.Client.Known ||
wine.Title.Client.Window != windows["game"] || wine.Title.Client.Width != 250 || wine.Title.Client.Height != 150 {
t.Fatalf("Wine child geometry/focus missing: %+v", wine)
}
command("focus", "second", "")
dialog := waitTitle(windows["desktop"], windows["second"], "Wine dialog")
if dialog.Epoch != wine.Epoch {
t.Fatal("Wine child change reset desktop ownership")
}
command("destroy", "desktop", "")
empty := waitTitle(0, 0, "")
if empty.Epoch == wine.Epoch {
t.Fatal("lost foreground kept its epoch")
}
}