More work on cthulhu recognizing focused items that should be read.

This commit is contained in:
Storm Dragon committed 2026-11-01 18:31:58 -05:00
1 parent 9374b5a2e5
commit 300f4a7504
9 files changed
+456 -9

No files matched your search

+7 -4
View File
@@ -5,8 +5,10 @@ selected descendants or capturing their text, values, attributes and geometry.
The native adapter owns this decision on the GLib thread. It uses the existing
foreground window and epoch; there is no additional window ownership cache.
Events from another provider can be rejected using copied bus identities and
libatspi's cached role and application name, without querying that provider.
Events from another provider first resolve their containing window: GTK socket
children can belong to the foreground host while using a different provider
bus. Unrelated providers are then rejected using the resolved window and copied
bus identities, before any content queries.
Events from another window on the same provider undergo the existing window
correlation before content capture. Lifecycle invalidation still runs, but a
background provider no longer clears another provider's cached document lines.
@@ -16,8 +18,9 @@ their existing handling. Announcement events are explicit requests to speak
and also bypass the filter; see [announcements](announcements.md). Native alerts, notification daemons and supported
Steam toasts retain their notification paths. Menu popups retain existing
foreground checks. Verified active/showing `mate-panel` menu windows may pass
across provider boundaries, including over an empty desktop; ordinary panel
controls and hidden menus remain filtered. See [MATE desktop](mate-desktop.md).
across provider boundaries, including over an empty desktop. Embedded panel
controls use their host's normal X11 correlation; unrelated panel windows and
hidden menus remain filtered. See [MATE desktop](mate-desktop.md).
Same-provider autocomplete relations and tooltip candidates
continue through the existing event-loop ownership checks; they do not acquire
focus merely by passing the early filter.
+34
View File
@@ -18,6 +18,7 @@ Reviewed the local `orca/` checkout at revision `a8a851800`:
| Marco window cycling | `script_manager.py`, `scripts/switcher/script.py`, `scripts/switcher/script_utilities.py` | Already implemented; see [window switcher checks](window-switcher.md). |
| MATE notification daemon | `script_manager.py`, `scripts/apps/notification-daemon/script.py` | Already routed through notification capture, speech, Braille and history; added an explicit MATE daemon regression. |
| Unnamed panel icons | `generator.py:_generate_accessible_name` | Adapted the containing panel's name/description when the icon supplies neither. Own names and descriptions retain precedence. |
| Embedded panel applets | `script_utilities.py:frame_and_dialog`, `script_utilities.py:top_level_object`, `scripts/default.py:_on_focused_changed` | Follow the accessible parent chain through a GTK plug/socket to its host window before normal foreground validation. |
Orca has no dedicated `mate-panel` script in this checkout. Its current app
script catalog also contains no Caja, Pluma, Atril, Engrampa or Eye of MATE
@@ -39,10 +40,32 @@ hidden menu items retain normal filtering. Native actions and review use the
same window validation. The correlated X11 application is preserved; no popup
cache, native references, timer or extra window owner is introduced.
## Embedded applet ownership
The newer `kendell/mate.log`, recorded with revision `702894ee`, shows a
different failure: Bottom Panel controls are accepted, but focused icons from
other provider buses are rejected as outside the X11 foreground. The reader
announces the host's generic "panel" while discarding the embedded icon.
A real GTK socket/plug reproduction exposes the same structure. The applet
publishes its own frame, whose accessible parent chain crosses the socket's
provider and reaches the panel frame. Window resolution now follows that chain
to the host instead of stopping at the plug frame. The background filter also
resolves this window before rejecting a different provider bus. The source
object retains its own bus, path and process identity for focus and actions.
This is shared window ancestry handling: it requires no MATE process-name
exception. Normal X11 process, state and geometry checks still apply to the
host. Same-provider nested frames retain their existing nearest-window
behavior. An unembedded or detached applet cannot borrow the host's identity,
and a background host cannot admit applet focus. Parent links beyond the plug
frame are refreshed on the GLib owner; no embedding cache is retained.
## Automated checks
```sh
python3 tests/native_test_session.py env CTHULHU_NATIVE_PANEL_MENU=1 CTHULHU_NATIVE_WINDOW_SWITCHER=1 CTHULHU_NATIVE_BACKGROUND_EVENTS=1 go test -race ./internal/atspi ./internal/app -run '^TestNative(MatePanelMenu|MateWindowSwitcherOutsideForeground|BackgroundEventFiltering)' -count=1 -p=1 -timeout=90s -v
python3 tests/native_test_session.py env CTHULHU_NATIVE_PANEL_APPLET=1 go test -race ./internal/atspi ./internal/app -run '^TestNativeEmbeddedPanelApplet' -count=1 -p=1 -timeout=90s -v
go test ./internal/app -run '^TestNotificationProvidersAndHistoryMatchReferenceBehavior$' -count=1
```
@@ -55,6 +78,14 @@ inherited labels, own-name/description precedence and unrelated-container
rejection. The event-loop check verifies queued speech and Braille frames for
navigation, dismissal, editor return and reopening. It emits no audible speech.
The socket/plug fixture uses separate host, applet and editor processes. It
checks the shared containing-window identity, preserved applet identity, icon
label inheritance, Tab/Shift+Tab within the applet, host controls on either
side, and editor restoration. It also checks a non-MATE host, background applet
focus, an unrelated applet claiming the MATE application name, and stale focus
after socket destruction. The event-loop check verifies speech requests and
Braille frames through two panel/editor round trips.
The existing background flood regression checks that ordinary same-provider
and other-provider updates still cause zero content queries. The Marco
regression checks switcher insertions and showing events while an editor
@@ -87,6 +118,9 @@ MATE session. After starting the candidate through the normal launcher:
typing and ordinary Tab navigation. Check terminal-reader handoff if used.
3. Use Control+Alt+Tab to reach the panel and navigate its applets. Confirm
applet icons use supplied labels without repeating unrelated container text.
Traverse both directions through launchers, clock, notification area and
configured external applets. Open and dismiss applet controls, then return
to the previous application. Background applet updates must stay silent.
4. Cycle windows with Alt+Tab and Shift+Alt+Tab, then cancel with Escape.
Check a MATE notification and its notification-history entry.
5. Return to browser heading navigation and address-bar suggestions, and open
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/braille"
)
func TestNativeEmbeddedPanelAppletPresentation(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_PANEL_APPLET") != "1" {
t.Skip("requires private Xorg/AT-SPI session")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
start := func(args ...string) (io.WriteCloser, string, string) {
t.Helper()
command := exec.CommandContext(ctx, "/usr/bin/python3", append([]string{"../../tests/gtk_panel_applet.py"}, args...)...)
input, err := command.StdinPipe()
if err != nil {
t.Fatal(err)
}
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
command.Stderr = os.Stderr
if err := command.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { input.Close(); command.Process.Kill(); command.Wait() })
line, err := bufio.NewReader(output).ReadString('\n')
fields := strings.Fields(line)
if err != nil || len(fields) != 3 || fields[0] != "ready" {
t.Fatalf("fixture readiness: %q, %v", line, err)
}
return input, fields[1], fields[2]
}
host, xid, socketID := start("panel")
start("applet", socketID)
editor, editorXID, _ := start("editor")
key := func(args ...string) {
t.Helper()
if output, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
t.Fatalf("xdotool: %s, %v", output, err)
}
}
speech := &fakeSpeechQueue{spoken: make(chan string, 128)}
frames := make(chan braille.Frame, 128)
done := make(chan struct{})
go func() {
defer close(done)
processEventsWithInput(ctx, slog.New(slog.DiscardHandler),
outputQueue{speech: speech, braille: browserBrailleProbe{frames: frames}},
adapter, nil, nil, 0, nil, nil, nil, nil)
}()
defer func() { cancel(); <-done }()
wait := func(name string) {
t.Helper()
spoken, displayed := false, false
var messages []string
for !spoken || !displayed {
select {
case text := <-speech.spoken:
messages = append(messages, text)
spoken = spoken || strings.Contains(text, name)
case frame := <-frames:
displayed = displayed || strings.Contains(frame.Text, name)
case <-ctx.Done():
t.Fatalf("missing presentation %q: speech=%v braille=%v messages=%q", name, spoken, displayed, messages)
}
}
}
for iteration := 0; iteration < 2; iteration++ {
key("windowfocus", "--sync", xid)
fmt.Fprintln(host, "first")
wait("Before applet")
key("key", "Tab")
wait("Network applet")
key("key", "Tab")
wait("Applet settings")
key("key", "shift+Tab")
wait("Network applet")
key("key", "Tab")
wait("Applet settings")
key("key", "Tab")
wait("After applet")
for _, name := range []string{"Applet settings", "Network applet", "Before applet"} {
key("key", "shift+Tab")
wait(name)
}
key("windowfocus", "--sync", editorXID)
text := fmt.Sprintf("Returned to editor %d", iteration)
fmt.Fprintln(editor, "edit "+text)
wait(text)
}
}
+10 -2
View File
@@ -32,7 +32,7 @@ func (adapter *Adapter) filterNativeBackgroundEvent(event *C.AtspiEvent, eventTy
if C.cthulhu_may_be_background_notification(event.source) != 0 {
return false
}
if (foreground.Client.Window == 0 || adapter.foregroundWindow.Valid() && origin.BusName != adapter.foregroundWindow.BusName) &&
if foreground.Client.Window == 0 &&
adapter.applicationProcess(event.source) != "mate-panel" {
return true
}
@@ -43,6 +43,14 @@ func (adapter *Adapter) filterNativeBackgroundEvent(event *C.AtspiEvent, eventTy
return false
}
defer C.g_object_unref(C.gpointer(window))
// An embedded GTK applet has its own provider bus but belongs to the
// socket host's window. Resolve that ancestry before the cross-bus fast
// rejection; the host still has to pass normal X11 foreground checks.
windowID := nativeObjectID(window)
if adapter.foregroundWindow.Valid() && origin.BusName != adapter.foregroundWindow.BusName &&
windowID.BusName != adapter.foregroundWindow.BusName && adapter.applicationProcess(event.source) != "mate-panel" {
return true
}
focusedMenuItem := false
if eventType == FocusEvent || eventType == FocusedStateEvent && event.detail1 != 0 {
switch C.atspi_accessible_get_role(event.source, nil) {
@@ -50,7 +58,7 @@ func (adapter *Adapter) filterNativeBackgroundEvent(event *C.AtspiEvent, eventTy
focusedMenuItem = true
}
}
if adapter.matchForeground(window, nativeObjectID(window), nil, focusedMenuItem) {
if adapter.matchForeground(window, windowID, nil, focusedMenuItem) {
return false
}
if origin.BusName == adapter.foregroundWindow.BusName {
+34 -3
View File
@@ -609,37 +609,68 @@ gboolean cthulhu_may_be_background_notification(AtspiAccessible *source) {
// Return the containing top-level window. An embedded web dialog is part of
// its frame; a standalone dialog directly below the application owns itself.
// GTK plugs also publish a frame, but its parent chain crosses the socket's
// provider before reaching the host window. Keep that host as the owner while
// retaining the source object's provider identity. Same-provider nested frames
// retain their existing nearest-window behavior.
AtspiAccessible *cthulhu_window_ancestor(AtspiAccessible *source) {
if (source == NULL) return NULL;
AtspiAccessible *current = g_object_ref(source);
AtspiAccessible *window = NULL;
for (gint depth = 0; current != NULL && depth < 256; depth++) {
GError *error = NULL;
AtspiRole role = atspi_accessible_get_role(current, &error);
if (error != NULL) {
g_clear_error(&error);
g_object_unref(current);
if (window != NULL) g_object_unref(window);
return NULL;
}
if (role == ATSPI_ROLE_FRAME || role == ATSPI_ROLE_WINDOW) {
return current;
if (role == ATSPI_ROLE_APPLICATION) {
g_object_unref(current);
return window;
}
if ((role == ATSPI_ROLE_FRAME || role == ATSPI_ROLE_WINDOW) &&
(window == NULL || g_strcmp0(cthulhu_accessible_bus_name(window),
cthulhu_accessible_bus_name(current)) != 0)) {
if (window != NULL) g_object_unref(window);
window = g_object_ref(current);
}
AtspiAccessible *parent = atspi_accessible_get_parent(current, &error);
// Only a frame with parents below the application can be a plug.
// Refresh that chain to reject detached sockets, without invalidating
// every ordinary top-level window for every accessibility event.
if (window != NULL && parent != NULL && error == NULL &&
atspi_accessible_get_role(parent, NULL) != ATSPI_ROLE_APPLICATION) {
g_object_unref(parent);
atspi_accessible_clear_cache_single(current);
parent = atspi_accessible_get_parent(current, &error);
}
// Qt can expose a top-level QWidget as a filler rather than a frame.
// Python's Qt utilities use the application-child boundary here.
if (parent != NULL && error == NULL && role != ATSPI_ROLE_APPLICATION &&
atspi_accessible_get_role(parent, NULL) == ATSPI_ROLE_APPLICATION) {
g_object_unref(parent);
return current;
if (window == NULL || g_strcmp0(cthulhu_accessible_bus_name(window),
cthulhu_accessible_bus_name(current)) != 0) {
if (window != NULL) g_object_unref(window);
return current;
}
g_object_unref(current);
return window;
}
g_object_unref(current);
if (error != NULL) {
g_clear_error(&error);
if (parent != NULL) g_object_unref(parent);
if (window != NULL) g_object_unref(window);
return NULL;
}
current = parent;
}
if (current != NULL) g_object_unref(current);
if (current == NULL) return window;
if (window != NULL) g_object_unref(window);
return NULL;
}
+159
View File
@@ -0,0 +1,159 @@
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"
"testing"
"time"
)
func TestNativeEmbeddedPanelApplet(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_PANEL_APPLET") != "1" {
t.Skip("requires private Xorg/AT-SPI session")
}
// Embedding belongs to the window ancestry contract, not a process-name
// exception. Both MATE and an ordinary GTK host must retain ownership.
for _, hostName := range []string{"panel", "other-host"} {
t.Run(hostName, func(t *testing.T) { check_native_embedded_applet(t, hostName) })
}
}
func check_native_embedded_applet(t *testing.T, hostName string) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
adapter := New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
start := func(args ...string) (io.WriteCloser, string, string) {
t.Helper()
command := exec.CommandContext(ctx, "/usr/bin/python3", append([]string{"../../tests/gtk_panel_applet.py"}, args...)...)
input, err := command.StdinPipe()
if err != nil {
t.Fatal(err)
}
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
command.Stderr = os.Stderr
if err := command.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { input.Close(); command.Process.Kill(); command.Wait() })
line, err := bufio.NewReader(output).ReadString('\n')
fields := strings.Fields(line)
if err != nil || len(fields) != 3 || fields[0] != "ready" {
t.Fatalf("fixture readiness: %q, %v", line, err)
}
return input, fields[1], fields[2]
}
host, xid, socketID := start(hostName)
applet, _, _ := start("applet", socketID)
editor, editorXID, _ := start("editor")
wait := func(predicate func(Event) bool) Event {
t.Helper()
var last Event
for {
select {
case event := <-adapter.Events():
last = event
if predicate(event) {
return event
}
case <-ctx.Done():
t.Fatalf("native event missing; filtered=%d last=%s %q window=%+v mismatch=%v", adapter.backgroundEventsFiltered.Load(), last.Type, last.Source.Name, last.Window, last.ForegroundMismatch)
}
}
}
key := func(args ...string) {
t.Helper()
if output, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
t.Fatalf("xdotool %v: %s, %v", args, output, err)
}
}
key("windowfocus", "--sync", xid)
fmt.Fprintln(host, "first")
owned := wait(func(event Event) bool {
return event.Source.Name == "Before applet" && event.Source.Focused && !event.ForegroundMismatch
})
key("key", "Tab")
icon := wait(func(event Event) bool { return event.Source.Role == "icon" && event.Source.Focused })
if icon.ForegroundMismatch || icon.Window != owned.Window || icon.Source.BusName == owned.Source.BusName || icon.Foreground != owned.Foreground {
t.Fatalf("embedded icon lost host ownership: host=%+v icon=%+v source=%+v mismatch=%v foreground=%+v want=%+v", owned.Window, icon.Window, icon.Source.ID(), icon.ForegroundMismatch, icon.Foreground, owned.Foreground)
}
if icon.Source.Name != "Network applet" || icon.Source.ApplicationProcess != "applet" {
t.Fatalf("embedded icon lost its label or provider identity: %+v", icon)
}
key("key", "Tab")
wait(func(event Event) bool {
return event.Source.Name == "Applet settings" && event.Source.Focused && event.Window == owned.Window && !event.ForegroundMismatch
})
key("key", "shift+Tab")
wait(func(event Event) bool {
return event.Source.Role == "icon" && event.Source.Focused && !event.ForegroundMismatch
})
key("key", "Tab")
wait(func(event Event) bool {
return event.Source.Name == "Applet settings" && event.Source.Focused && !event.ForegroundMismatch
})
key("key", "Tab")
wait(func(event Event) bool {
return event.Source.Name == "After applet" && event.Source.Focused && !event.ForegroundMismatch
})
for _, name := range []string{"Applet settings", "Network applet", "Before applet"} {
key("key", "shift+Tab")
wait(func(event Event) bool {
return event.Source.Name == name && event.Source.Focused && !event.ForegroundMismatch
})
}
key("windowfocus", "--sync", editorXID)
fmt.Fprintln(editor, "edit Foreground restored")
wait(func(event Event) bool { return event.Payload == "Foreground restored" && !event.ForegroundMismatch })
fmt.Fprintln(applet, "stale")
wait(func(event Event) bool {
if event.Source.Role == "icon" && !event.ForegroundMismatch {
t.Fatalf("background applet stole foreground: %+v", event)
}
return event.Payload == "Applet barrier"
})
// A separately published applet window cannot borrow the host's identity.
standalone, standaloneXID, _ := start("standalone")
key("windowfocus", "--sync", standaloneXID)
fmt.Fprintln(standalone, "first")
wait(func(event Event) bool {
return event.Source.Role == "icon" && event.Source.Focused && !event.ForegroundMismatch
})
key("windowfocus", "--sync", xid)
fmt.Fprintln(host, "first")
wait(func(event Event) bool {
return event.Source.Name == "Before applet" && event.Source.Focused && !event.ForegroundMismatch
})
fmt.Fprintln(standalone, "stale")
wait(func(event Event) bool {
if event.Source.Role == "icon" && !event.ForegroundMismatch {
t.Fatalf("unembedded applet borrowed host ownership: %+v", event)
}
return event.Payload == "Applet barrier"
})
fmt.Fprintln(host, "detach")
wait(func(event Event) bool { return event.Payload == "Detached host barrier" })
fmt.Fprintln(applet, "stale")
wait(func(event Event) bool {
if event.Source.Role == "icon" && !event.ForegroundMismatch {
t.Fatalf("detached applet retained host ownership: %+v", event)
}
return event.Payload == "Applet barrier"
})
}
+1
View File
@@ -1,4 +1,5 @@
id,python_source,subsystem,behavior,owner,go_destination,status,automated_evidence,x11_evidence,wayland_evidence,speech_evidence,braille_evidence,notes
embedded-panel-applet,Orca src/orca/script_utilities.py:frame_and_dialog;top_level_object;src/orca/scripts/default.py:_on_focused_changed,focus,Follow embedded GTK applet focus through the socket host window while preserving the source provider,,internal/atspi internal/app,in-progress,Private native regression reproduces rejection before the fix; socket and plug tests cover MATE and ordinary hosts; see docs/testing/mate-desktop.md,Separate host/applet/editor providers cover icon labels; Tab and Shift+Tab; editor return; background and unrelated providers; socket detachment.,,Queued speech verified; real MATE listening pending.,Native event-loop frames verified; physical Braille acceptance pending.,Refresh parent links through embedded frames before normal X11 host validation. No process-name bypass or retained embedding state. Human review and live panel traversal required.
submission-clear-deletion,src/cthulhu/scripts/default.py:onTextDeleted,speech,Avoid announcing an entry clear after plain Enter before the submitted chat message,,internal/app/submitted_text.go;internal/input/intent.go,in-progress,Submission replay fails against the original presenter and passes with the fix; editing regression matrix and focused race checks pass.,Private GTK submission and selected-text replacement checks use native snapshots and the real X11 input listener.,,Speech requests verified; live Pidgin listening pending.,Braille update path retained; physical display acceptance pending.,Requires focused text or entry plus fresh plain Enter and known empty line at offset zero. Selection replacement and modified Enter retain existing feedback. No Thunderbird or word-echo changes. See docs/testing/messaging.md.
mate-panel-menu,Orca src/orca/scripts/default.py;src/orca/ax_utilities.py;src/orca/generator.py,focus,Read active MATE panel menus across X11 application boundaries and label unnamed panel icons,,internal/atspi internal/app,in-progress,Private native and event-loop regressions; see docs/testing/mate-desktop.md,Separate GTK providers cover editor and empty desktop menus; submenus; reverse navigation; Escape; hidden/impostor rejection; icon label precedence.,,Queued presentation verified; actual Fedora MATE listening pending.,Native event-loop frames verified; physical display acceptance pending.,Fresh active/showing native menu structure and verified mate-panel process only. Preserves X11 correlation without retained popup state. Existing Marco and notification paths retained. Human review and live MATE acceptance required.
mate-window-switcher,Orca src/orca/scripts/switcher/script.py;src/orca/scripts/switcher/script_utilities.py,focus,Present Marco switcher selections while the old application retains X11 foreground,,internal/atspi internal/app,in-progress,go test ./internal/app -run '^TestMateWindowSwitcher' -count=1; see docs/testing/window-switcher.md,Private GTK provider checks repeated insertions and showing events outside foreground without taking ownership.,,Actual Marco listening and physical key acceptance pending.,Transient flash requests tested; physical display acceptance pending.,Verified Marco process and application plus visible status-bar container only. Preserves application focus and review. No name-signal duplication or general background bypass. Original log lacks later cycling insertions; real cycling and Escape remain live gates.
1 id python_source subsystem behavior owner go_destination status automated_evidence x11_evidence wayland_evidence speech_evidence braille_evidence notes
2 embedded-panel-applet Orca src/orca/script_utilities.py:frame_and_dialog;top_level_object;src/orca/scripts/default.py:_on_focused_changed focus Follow embedded GTK applet focus through the socket host window while preserving the source provider internal/atspi internal/app in-progress Private native regression reproduces rejection before the fix; socket and plug tests cover MATE and ordinary hosts; see docs/testing/mate-desktop.md Separate host/applet/editor providers cover icon labels; Tab and Shift+Tab; editor return; background and unrelated providers; socket detachment. Queued speech verified; real MATE listening pending. Native event-loop frames verified; physical Braille acceptance pending. Refresh parent links through embedded frames before normal X11 host validation. No process-name bypass or retained embedding state. Human review and live panel traversal required.
3 submission-clear-deletion src/cthulhu/scripts/default.py:onTextDeleted speech Avoid announcing an entry clear after plain Enter before the submitted chat message internal/app/submitted_text.go;internal/input/intent.go in-progress Submission replay fails against the original presenter and passes with the fix; editing regression matrix and focused race checks pass. Private GTK submission and selected-text replacement checks use native snapshots and the real X11 input listener. Speech requests verified; live Pidgin listening pending. Braille update path retained; physical display acceptance pending. Requires focused text or entry plus fresh plain Enter and known empty line at offset zero. Selection replacement and modified Enter retain existing feedback. No Thunderbird or word-echo changes. See docs/testing/messaging.md.
4 mate-panel-menu Orca src/orca/scripts/default.py;src/orca/ax_utilities.py;src/orca/generator.py focus Read active MATE panel menus across X11 application boundaries and label unnamed panel icons internal/atspi internal/app in-progress Private native and event-loop regressions; see docs/testing/mate-desktop.md Separate GTK providers cover editor and empty desktop menus; submenus; reverse navigation; Escape; hidden/impostor rejection; icon label precedence. Queued presentation verified; actual Fedora MATE listening pending. Native event-loop frames verified; physical display acceptance pending. Fresh active/showing native menu structure and verified mate-panel process only. Preserves X11 correlation without retained popup state. Existing Marco and notification paths retained. Human review and live MATE acceptance required.
5 mate-window-switcher Orca src/orca/scripts/switcher/script.py;src/orca/scripts/switcher/script_utilities.py focus Present Marco switcher selections while the old application retains X11 foreground internal/atspi internal/app in-progress go test ./internal/app -run '^TestMateWindowSwitcher' -count=1; see docs/testing/window-switcher.md Private GTK provider checks repeated insertions and showing events outside foreground without taking ownership. Actual Marco listening and physical key acceptance pending. Transient flash requests tested; physical display acceptance pending. Verified Marco process and application plus visible status-bar container only. Preserves application focus and review. No name-signal duplication or general background bypass. Original log lacks later cycling insertions; real cycling and Escape remain live gates.
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Storm Dragon
# Authors: Storm Dragon, Angus Kola
# SPDX-License-Identifier: GPL-3.0-or-later
"""Real GTK socket/plug embedding on separate providers for private tests."""
import ctypes
import os
import sys
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Atk", "1.0")
gi.require_version("GdkX11", "3.0")
from gi.repository import Atk, GdkX11, GLib, Gtk
mode = sys.argv[1]
processName = "mate-panel" if mode == "panel" else "applet" if mode == "standalone" else mode
GLib.set_prgname(processName)
if ctypes.CDLL(None).prctl(15, processName.encode(), 0, 0, 0) != 0:
raise RuntimeError("cannot set fixture process name")
window = Gtk.Plug.new(int(sys.argv[2])) if mode == "applet" else Gtk.Window()
if mode == "applet":
window.connect("delete-event", lambda *_args: True)
if mode == "standalone":
Atk.get_root().set_name("mate-panel")
window.set_title("Embedded applet fixture " + mode)
window.set_default_size(400, 120)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
window.add(box)
socket = None
if mode in ("applet", "standalone"):
box.get_accessible().set_role(Atk.Role.PANEL)
box.get_accessible().set_name("Network applet")
first = Gtk.Button(label="Network")
first.get_accessible().set_role(Atk.Role.ICON)
first.get_accessible().set_name("")
second = Gtk.Button(label="Applet settings")
box.add(first)
box.add(second)
elif mode == "editor":
first = Gtk.Entry()
first.get_accessible().set_name("Editor entry")
box.add(first)
else:
first = Gtk.Button(label="Before applet")
second = Gtk.Button(label="After applet")
socket = Gtk.Socket()
box.add(first)
box.add(socket)
box.add(second)
barrier = Gtk.Label()
barrier.get_accessible().set_role(Atk.Role.ALERT)
box.add(barrier)
window.show_all()
pendingInput = ""
def receive_command(_source, _condition):
global pendingInput
content = os.read(sys.stdin.fileno(), 4096)
if not content:
Gtk.main_quit()
return False
pendingInput += content.decode()
while "\n" in pendingInput:
command, pendingInput = pendingInput.split("\n", 1)
if command == "first":
first.grab_focus()
elif command == "second":
second.grab_focus()
elif command == "stale":
first.get_accessible().notify_state_change(Atk.StateType.FOCUSED, True)
barrier.set_text("")
barrier.set_text("Applet barrier")
elif command == "detach":
socket.destroy()
barrier.set_text("Detached host barrier")
elif command.startswith("edit "):
first.set_text(command.removeprefix("edit "))
first.grab_focus()
elif command == "quit":
Gtk.main_quit()
return False
return True
GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_HUP, receive_command)
GLib.idle_add(lambda: print(
"ready", GdkX11.X11Window.get_xid(window.get_window()),
socket.get_id() if socket is not None else 0, flush=True) or False)
Gtk.main()
+3
View File
@@ -24,6 +24,9 @@ if providerName == "marco":
raise RuntimeError("cannot set fixture process name")
window = Gtk.Window(title="Switcher fixture " + providerName)
# Label updates must not resize the foreground window while the test compares
# its full X11 snapshot to check that the switcher has not taken ownership.
window.set_default_size(400, 100)
if providerName == "marco":
window.set_accept_focus(False)
window.set_focus_on_map(False)