// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package atspi import ( "git.stormux.org/storm/cthulhu/internal/platform/xorg" "testing" ) func TestForegroundWindowCorrelation(t *testing.T) { client := xorg.Foreground{Known: true, Window: 45, PID: 123, X: 10, Y: 30, Width: 400, Height: 300, OuterX: 8, OuterY: 5, OuterWidth: 404, OuterHeight: 327} for _, test := range []struct { name string pid int bounds Rectangle want bool }{ {"client", 123, Rectangle{10, 30, 400, 300}, true}, {"decorated", 123, Rectangle{8, 5, 404, 327}, true}, {"other process", 124, Rectangle{10, 30, 400, 300}, false}, {"same process other window", 123, Rectangle{420, 30, 400, 300}, false}, {"same process scaled window", 123, Rectangle{10, 30, 200, 150}, true}, {"scaled window shifted", 123, Rectangle{11, 30, 200, 150}, false}, {"scaled window wrong aspect", 123, Rectangle{10, 30, 200, 120}, false}, {"scaled window too small", 123, Rectangle{10, 30, 100, 75}, false}, {"other process scaled window", 124, Rectangle{10, 30, 200, 150}, false}, {"dialog", 123, Rectangle{40, 50, 200, 100}, false}, {"missing bounds", 123, Rectangle{}, false}, } { t.Run(test.name, func(t *testing.T) { if got := foregroundWindowMatches(client, test.pid, test.bounds); got != test.want { t.Fatalf("match = %v, want %v", got, test.want) } }) } } func TestForegroundUnpositionedGTK4Correlation(t *testing.T) { // GTK 4 on X11 (seen on i3) reports its frame at 0,0 with the client's size. client := xorg.Foreground{Known: true, Window: 35651592, PID: 215379, X: 700, Y: 449, Width: 520, Height: 322, OuterX: 698, OuterY: 427, OuterWidth: 524, OuterHeight: 346, FrameX: 698, FrameY: 427, FrameWidth: 524, FrameHeight: 346} for _, test := range []struct { name string client xorg.Foreground pid int bounds Rectangle want bool }{ {"gtk4 frame at origin", client, 215379, Rectangle{0, 0, 520, 322}, true}, {"other process", client, 215380, Rectangle{0, 0, 520, 322}, false}, {"unknown accessible process", client, 0, Rectangle{0, 0, 520, 322}, false}, {"unknown X11 process", xorg.Foreground{Known: true, Window: 35651592, X: 700, Y: 449, Width: 520, Height: 322}, 215379, Rectangle{0, 0, 520, 322}, false}, {"same process different size", client, 215379, Rectangle{0, 0, 400, 300}, false}, {"same process scaled size", client, 215379, Rectangle{0, 0, 260, 161}, false}, {"same process outer size at origin", client, 215379, Rectangle{0, 0, 524, 346}, false}, {"same size elsewhere", client, 215379, Rectangle{10, 0, 520, 322}, false}, } { t.Run(test.name, func(t *testing.T) { if got := foregroundWindowMatches(test.client, test.pid, test.bounds); got != test.want { t.Fatalf("match = %v, want %v", got, test.want) } }) } } func TestForegroundWindowCorrelationSteamBigPictureScale(t *testing.T) { client := xorg.Foreground{Known: true, Window: 83886130, PID: 111, X: 0, Y: 0, Width: 1920, Height: 1080, OuterX: 0, OuterY: 0, OuterWidth: 1920, OuterHeight: 1080} if !foregroundWindowMatches(client, 111, Rectangle{X: 0, Y: 0, Width: 1353, Height: 761}) { t.Fatal("same-process logical-pixel frame did not match X11 foreground") } } func TestForegroundReparentedFrameCorrelation(t *testing.T) { client := xorg.Foreground{Known: true, Window: 45, PID: 123, X: 2, Y: 18, Width: 1912, Height: 974, OuterX: 2, OuterY: 18, OuterWidth: 1912, OuterHeight: 974, FrameX: 0, FrameY: 0, FrameWidth: 1916, FrameHeight: 994} for _, test := range []struct { name string pid int bounds Rectangle want bool }{ {"actual WM frame", 123, Rectangle{0, 0, 1916, 994}, true}, {"client still accepted", 123, Rectangle{2, 18, 1912, 974}, true}, {"other process same frame size", 124, Rectangle{0, 0, 1916, 994}, false}, {"same process other frame", 123, Rectangle{1920, 0, 1916, 994}, false}, {"same process smaller dialog", 123, Rectangle{20, 30, 400, 300}, false}, {"scaled frame same origin", 123, Rectangle{0, 0, 958, 497}, true}, {"scaled frame wrong origin", 123, Rectangle{1, 0, 958, 497}, false}, {"scaled frame wrong aspect", 123, Rectangle{0, 0, 958, 400}, false}, } { t.Run(test.name, func(t *testing.T) { if got := foregroundWindowMatches(client, test.pid, test.bounds); got != test.want { t.Fatalf("match = %v, want %v", got, test.want) } }) } } func TestPreferencesForegroundIdentity(t *testing.T) { // Slint/i3 reports GTK's frame and the X11 client with different borders. client := xorg.Foreground{Known: true, Window: 45, PID: 123, X: 2, Y: 18, Width: 1912, Height: 974, OuterX: 2, OuterY: 18, OuterWidth: 1912, OuterHeight: 974} if foregroundWindowMatches(client, 123, Rectangle{Width: 1916, Height: 994}) { t.Fatal("fixture must reproduce the reported bounds mismatch") } for _, test := range []struct { name string client xorg.Foreground pid int owned int64 want bool }{ {"owned foreground helper", client, 123, 123, true}, {"helper closed", client, 123, 0, false}, {"another reader's helper", client, 123, 124, false}, {"other provider", client, 124, 123, false}, {"helper in background", xorg.Foreground{Known: true, Window: 46, PID: 124}, 123, 123, false}, {"missing foreground PID", xorg.Foreground{Known: true, Window: 45}, 123, 123, false}, {"empty workspace", xorg.Foreground{Known: true, PID: 123}, 123, 123, false}, {"unknown foreground", xorg.Foreground{Window: 45, PID: 123}, 123, 123, false}, } { t.Run(test.name, func(t *testing.T) { if got := preferences_foreground_matches(test.client, test.pid, test.owned); got != test.want { t.Fatalf("match = %v, want %v", got, test.want) } }) } } func TestForegroundMenuPopupCorrelation(t *testing.T) { client := xorg.Foreground{Known: true, Window: 45, PID: 123} main := NewObjectID(":1.40", "/main") popup := NewObjectID(":1.40", "/popup") for _, test := range []struct { name string client xorg.Foreground main ObjectID popup ObjectID pid int menu bool want bool }{ {"active menu in foreground application", client, main, popup, 123, true, true}, {"inactive menu", client, main, popup, 123, false, false}, {"other application", client, main, NewObjectID(":1.41", "/popup"), 123, true, false}, {"other process", client, main, popup, 124, true, false}, {"ordinary sibling window", client, main, popup, 123, false, false}, {"no foreground client", xorg.Foreground{Known: true}, main, popup, 123, true, false}, {"no owned main window", client, ObjectID{}, popup, 123, true, false}, } { t.Run(test.name, func(t *testing.T) { if got := foregroundMenuPopupMatches(test.client, test.main, test.popup, test.pid, test.menu); got != test.want { t.Fatalf("match = %v, want %v", got, test.want) } }) } } func TestForegroundWindowReplacement(t *testing.T) { previous := NewObjectID(":1.40", "/gtk_frame") candidate := NewObjectID(":1.40", "/gecko_frame") sibling := NewObjectID(":1.40", "/other_window") for _, test := range []struct { name string previous, candidate ObjectID windows []ObjectID want bool }{ {"replaced application root", previous, candidate, []ObjectID{candidate, sibling}, true}, {"existing sibling cannot take ownership", previous, candidate, []ObjectID{previous, candidate}, false}, {"unregistered candidate", previous, candidate, []ObjectID{sibling}, false}, {"missing window list", previous, candidate, nil, false}, {"other provider", previous, NewObjectID(":1.41", "/frame"), []ObjectID{NewObjectID(":1.41", "/frame")}, false}, {"no previous owner", ObjectID{}, candidate, []ObjectID{candidate}, false}, {"unchanged owner", candidate, candidate, []ObjectID{candidate}, false}, } { t.Run(test.name, func(t *testing.T) { if got := foregroundWindowReplaced(test.previous, test.candidate, test.windows); got != test.want { t.Fatalf("replacement = %v, want %v", got, test.want) } }) } }