Descovered a but on Slint where if the reported sizes differ preferences would not rport. HOpefully fixed. This could probably affect more than just Slint, just happened to catch it there.
This commit is contained in:
1 parent
7591f65b58
commit
d0fdce7285
9 files changed
+90
No files matched your search
@@ -47,3 +47,31 @@ check pitch and volume with the quick speech keys, then save and reopen the
|
||||
settings. Values such as 4.5 must remain 4.5 in announcements and TOML rather
|
||||
than exposing floating-point tails. Check all voice types and custom key echo;
|
||||
untouched inherited values must remain unset.
|
||||
|
||||
## Preferences focus with differing window bounds
|
||||
|
||||
On Slint/i3, GTK can report a frame at (0,0), sized 1916×994, while X11
|
||||
reports its client at (2,18), sized 1912×974. Rejecting that frame by geometry
|
||||
silences control announcements even though Tab still moves focus. The reader
|
||||
registers the PID of its own preferences helper until the helper exits. That
|
||||
helper can bypass the geometry comparison when X11 identifies the same
|
||||
foreground process and GTK reports an active, showing, non-iconified window.
|
||||
Other applications retain the normal foreground correlation checks.
|
||||
|
||||
The adapter dropdown regression also checks that preferences activation and
|
||||
focused-control events are accepted:
|
||||
|
||||
```sh
|
||||
go test ./internal/atspi ./internal/app -count=1
|
||||
CTHULHU_BINARY=/tmp/cthulhu-speech-preferences CTHULHU_PREFS_ADAPTER_ONLY=1 python3 tests/native_preferences.py
|
||||
```
|
||||
|
||||
On the affected Slint desktop, use the rebuilt reader to open preferences.
|
||||
Tab and Shift+Tab through controls, switch notebook pages with Ctrl+PageUp and
|
||||
Ctrl+PageDown, open a dropdown, change its selection, and close it with Escape.
|
||||
Confirm each control's name and value is announced. Apply a change, close and
|
||||
reopen preferences, and confirm it persists. Switch to another application
|
||||
while preferences remains open: speech and Braille must follow that application,
|
||||
then resume preferences announcements when returning. Repeat after closing and
|
||||
reopening preferences and after changing window size. Private X11 tests do not
|
||||
establish physical-key, audible-speech or Braille acceptance on Slint.
|
||||
@@ -313,6 +313,7 @@ func Run(ctx context.Context, options Options) error {
|
||||
preferences.speechWorker = speechMessages
|
||||
preferences.braille = brailleOutput
|
||||
preferences.documentReview = objectReview
|
||||
preferences.preferencesProcess, _ = adapter.(interface{ SetPreferencesProcess(int) })
|
||||
objectReview.SetDocumentBrailleEnabled(settings.EnableBraille)
|
||||
if updater, ok := sounds.(soundSettingsUpdater); ok {
|
||||
preferences.sound = updater
|
||||
|
||||
@@ -65,6 +65,7 @@ type preferencesWorker struct {
|
||||
refreshHistory func()
|
||||
sound soundSettingsUpdater
|
||||
geometry soundGeometryCaptureConfigurer
|
||||
preferencesProcess interface{ SetPreferencesProcess(int) }
|
||||
textAttributes textAttributeCaptureConfigurer
|
||||
bindings interface {
|
||||
SetKeyBindings(context.Context, []input.BindingOverride) error
|
||||
|
||||
@@ -69,6 +69,10 @@ func (worker *preferencesWorker) window(ctx context.Context) (result error) {
|
||||
worker.live.preferencesPID.Store(int64(command.Process.Pid))
|
||||
defer worker.live.preferencesPID.Store(0)
|
||||
}
|
||||
if worker.preferencesProcess != nil {
|
||||
worker.preferencesProcess.SetPreferencesProcess(command.Process.Pid)
|
||||
defer worker.preferencesProcess.SetPreferencesProcess(0)
|
||||
}
|
||||
defer func() {
|
||||
stop()
|
||||
in.Close()
|
||||
|
||||
@@ -121,6 +121,7 @@ type Adapter struct {
|
||||
foregroundReader *xorg.ForegroundReader // GLib thread only
|
||||
foregroundProcesses map[int]bool // GLib thread; provider PID matches for current client
|
||||
foreground atomic.Pointer[ForegroundState]
|
||||
preferencesPID atomic.Int64 // Owned preferences helper; zero when closed.
|
||||
echoFocus atomic.Pointer[echoFocus]
|
||||
eventListener *C.AtspiEventListener // GLib owner; released with subscriptions.
|
||||
foregroundChanges chan struct{}
|
||||
|
||||
@@ -129,6 +129,14 @@ func (adapter *Adapter) matchForeground(window *C.AtspiAccessible, id ObjectID,
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Tiling window managers can report GTK's frame and X11's client at
|
||||
// different origins and sizes. Our own active preferences child is known
|
||||
// by its launched PID; require X11 to confirm that process is foreground.
|
||||
if preferences_foreground_matches(state, pid, adapter.preferencesPID.Load()) {
|
||||
adapter.foregroundWindow = id
|
||||
adapter.foregroundRecovery = 0
|
||||
return true
|
||||
}
|
||||
component := C.atspi_accessible_get_component_iface(window)
|
||||
if component == nil {
|
||||
if check != nil {
|
||||
|
||||
@@ -26,6 +26,17 @@ type ForegroundState struct {
|
||||
Epoch uint64
|
||||
}
|
||||
|
||||
// SetPreferencesProcess scopes geometry-independent foreground matching to the
|
||||
// UI child launched by this reader. Clear it when that child exits.
|
||||
func (adapter *Adapter) SetPreferencesProcess(pid int) {
|
||||
adapter.preferencesPID.Store(int64(pid))
|
||||
}
|
||||
|
||||
func preferences_foreground_matches(client xorg.Foreground, pid int, preferencesPID int64) bool {
|
||||
return client.Known && client.Window != 0 && preferencesPID > 0 &&
|
||||
int64(pid) == preferencesPID && client.PID == pid
|
||||
}
|
||||
|
||||
func foregroundWindowMatches(client xorg.Foreground, pid int, bounds Rectangle) bool {
|
||||
if client.PID > 0 && pid > 0 && client.PID != pid {
|
||||
return false
|
||||
|
||||
@@ -44,6 +44,37 @@ func TestForegroundWindowCorrelationSteamBigPictureScale(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
@@ -77,6 +77,8 @@ func TestNativePreferencesDropdownEscape(t *testing.T) {
|
||||
if err := command.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
adapter.SetPreferencesProcess(command.Process.Pid)
|
||||
defer adapter.SetPreferencesProcess(0)
|
||||
helperDone := make(chan error, 1)
|
||||
go func() { helperDone <- command.Wait() }()
|
||||
helperJoined := false
|
||||
@@ -118,6 +120,9 @@ func TestNativePreferencesDropdownEscape(t *testing.T) {
|
||||
select {
|
||||
case event := <-adapter.Events():
|
||||
t.Logf("%s %s %q capture=%s", event.Type, event.Source.Role, event.Source.Name, event.SnapshotDuration)
|
||||
if (event.Type == WindowActivatedEvent || event.Type == FocusedStateEvent && event.Detail1 == 1) && event.ForegroundMismatch {
|
||||
t.Fatalf("owned preferences focus rejected: %+v", event)
|
||||
}
|
||||
if event.Type == FocusedStateEvent && event.Detail1 == 1 {
|
||||
// Match the running reader's browser-mode classification.
|
||||
// Without these fresh ancestry queries, stale cached focus
|
||||
|
||||
Reference in new issue
Block a user