Review and strengthen LibreOffice code so far.
This commit is contained in:
1 parent
f885333520
commit
04e6c08fff
8 files changed
+186
-78
No files matched your search
+47
-50
@@ -47,6 +47,12 @@ reorganizing unrelated reader code or building a speculative plugin framework.
|
||||
|
||||
- Calc active-cell events establish logical focus, including after editing
|
||||
and when the sheet changes.
|
||||
- LibreOffice frame activation and X11 foreground recovery establish window
|
||||
ownership without the generic focused-descendant search. Calc's following
|
||||
cell/editor events establish document focus. Searching the previous view
|
||||
during workbook activation caused stale-object queries and timeouts on return;
|
||||
leaving activation on the frame avoids that work. Dialogs retain normal
|
||||
focused-control resolution.
|
||||
- Cell contents and addresses are separate. Normal speech gives the value,
|
||||
then the address; an empty value gives “blank.” Only-displayed-text mode
|
||||
omits the address and the synthetic blank label.
|
||||
@@ -185,17 +191,21 @@ checks. These slices do not establish complete Office support.
|
||||
## Verification
|
||||
|
||||
```sh
|
||||
GOCACHE=/tmp/cthulhu-go-cache go test -race ./internal/atspi ./internal/app ./internal/presentation ./internal/input ./internal/platform/xorg -count=1
|
||||
dbus-run-session -- env GOCACHE=/tmp/cthulhu-go-cache go test -race ./... -count=1
|
||||
dbus-run-session -- env GOCACHE=/tmp/cthulhu-go-cache go test -tags brlapi ./... -count=1
|
||||
python3 tests/check_speech.py
|
||||
/usr/bin/python3 tests/native_libreoffice.py
|
||||
python3 tests/native_atspi.py --table-only
|
||||
GOCACHE=/tmp/cthulhu-go-cache go build -o /tmp/cthulhu-libreoffice ./cmd/cthulhu
|
||||
GOCACHE=/tmp/cthulhu-go-cache go test -c -o /tmp/cthulhu-libreoffice-xorg.test ./internal/platform/xorg
|
||||
CTHULHU_BINARY=/tmp/cthulhu-libreoffice python3 tests/native_x11.py
|
||||
CTHULHU_BINARY=/tmp/cthulhu-libreoffice CTHULHU_XRECORD_TEST=/tmp/cthulhu-libreoffice-xorg.test python3 tests/native_x11.py
|
||||
xvfb-run -a env CTHULHU_NATIVE_X11=1 /tmp/cthulhu-libreoffice-xorg.test -test.run '^TestNativeDispatchBeforeRelease$' -test.v
|
||||
```
|
||||
|
||||
The Calc harness launches installed LibreOffice with a disposable profile,
|
||||
document, virtual X11 display, and private session/accessibility buses.
|
||||
The Calc harness requires i3 as well as LibreOffice, Xvfb, xdotool, xprop,
|
||||
the Python AT-SPI bindings and the normal native build dependencies. It launches
|
||||
installed LibreOffice with a disposable profile, document, private i3 window
|
||||
manager, virtual X11 display, and private session/accessibility buses.
|
||||
It exercises the production native adapter and event loop; recorded speech
|
||||
and Braille are asserted for cell movement and blanks, F2 typing, Escape
|
||||
cancellation, Enter commit, caret movement inside an existing numeric cell,
|
||||
@@ -210,8 +220,11 @@ The native listener tests check the desktop formula shortcut before key release;
|
||||
the Calc harness sends semantic requests through the production event loop.
|
||||
Dynamic-header checks exercise both axes, automatic axis-change speech,
|
||||
Where Am I, complete Braille context, heading edits, independent clearing,
|
||||
sheet round trips, and a second workbook with matching sheet names. Returning
|
||||
to the original workbook is a separate pending acceptance check below.
|
||||
sheet round trips, and both return directions between workbooks with matching
|
||||
sheet names and different headings, using normal navigation without Ctrl+F6
|
||||
recovery. Recorded speech, Where Am I and Braille must retain each workbook's
|
||||
own headings. Find and Replace typing and
|
||||
cancellation check the ordinary focused-control path and return to the sheet.
|
||||
Header command bindings are checked through
|
||||
the shared binding resolver in both layouts.
|
||||
No active reader is replaced and no normal Office profile is changed.
|
||||
@@ -260,49 +273,31 @@ physical Braille. Before submission, review the diff and test a rebuilt binary:
|
||||
Human review and testing remain required by `CONTRIBUTING.md`. Passing this
|
||||
fixture alone is not sufficient for submission or live accessibility acceptance.
|
||||
|
||||
Initial verification against LibreOffice 26.2.6.3 passed the Calc harness,
|
||||
affected-package race tests, vet/build, GTK table/multiselection checks, and
|
||||
Xorg input checks. Browser tab and table checks also passed. Browser editing
|
||||
failed on an extra title announcement both here and on untouched commit
|
||||
`8e76d23`. Browser modes initially failed to focus a heading after reload,
|
||||
then passed on the baseline and on a rerun here; the cause of that first
|
||||
failure was not established. Physical acceptance remains pending.
|
||||
The tightening pass against LibreOffice 26.2.6.3 verifies the existing cell,
|
||||
editor, formula, selection and dynamic-header slices and adds workbook return
|
||||
coverage. Returning to the original workbook previously reproduced stale-object
|
||||
timeouts through the production adapter. Keeping LibreOffice frame activation
|
||||
out of the generic descendant search makes the recorded return checks pass;
|
||||
header choices remain independent in both workbooks. Foreground recovery uses
|
||||
the same policy, preventing raw sheet-state chatter before the first cell event.
|
||||
The virtual-display driver uses window-manager activation and waits for the
|
||||
heading cell before editing it.
|
||||
|
||||
Formula-slice verification passed the isolated real Calc harness, focused
|
||||
race tests, vet/build, ordinary GTK table/multiselection checks, and the rebuilt
|
||||
reader's Xorg smoke test. A separate private X11 listener test verified the
|
||||
formula shortcut with both release orders. The broader app race run exposed
|
||||
`TestRunReachesSoundStopAfterEarlierServiceStopError`: it can cancel during
|
||||
startup and receive `context canceled` instead of its expected shutdown error.
|
||||
The same failure reproduced on unchanged commit `e0c8c61` with
|
||||
`GOMAXPROCS=8 go test -race ./internal/app -run '^TestRunReachesSoundStopAfterEarlierServiceStopError$' -count=100`.
|
||||
The remaining app race tests passed with that one test excluded.
|
||||
The broader native listener suite also has an existing C helper link failure
|
||||
for missing xkbcommon symbols; the formula shortcut was tested directly using
|
||||
the command above. Audible formula speech, physical input/Braille, and real
|
||||
workbook acceptance remain pending.
|
||||
Full default, race and BrlAPI-tagged Go suites, the speech regression gate,
|
||||
vet/build, parity-ledger validation, ordinary GTK table/multiselection checks,
|
||||
and the isolated X11 reader/listener harness pass. The former shutdown test
|
||||
failure is fixed by waiting for completed runtime startup before cancellation.
|
||||
A process-cleanup assertion also now accepts ESRCH when a killed task disappears
|
||||
during a procfs read. Neither correction skips the underlying shutdown checks.
|
||||
The earlier native probe linker failure no longer reproduces with the current
|
||||
listener harness.
|
||||
|
||||
Selection-slice verification passed the isolated real Calc harness (including
|
||||
disjoint and capped selections), focused race tests, vet/build, parity-ledger
|
||||
validation, and ordinary GTK table/multiselection checks. The full affected-package
|
||||
race run again hit the startup timing test above; all remaining affected-package
|
||||
race tests passed with
|
||||
`go test -race ./internal/atspi ./internal/app ./internal/presentation -skip '^TestRunReachesSoundStopAfterEarlierServiceStopError$' -count=1`.
|
||||
The whole-row/column contraction event gaps described above were independently
|
||||
observed without the reader. Audible output, physical input/Braille, and
|
||||
independent-workbook acceptance remain pending.
|
||||
|
||||
Dynamic-header verification passed focused race checks, the full affected-package
|
||||
race run in a private D-Bus session, vet/build, parity-ledger validation and
|
||||
ordinary GTK table/multiselection checks. Recorded Calc checks cover both axes,
|
||||
heading edits, independent clearing, sheet round trips and isolation in a second
|
||||
workbook with identical sheet names. A synthetic return to the original workbook
|
||||
produced stale/timed-out accessibility objects after window activation, even
|
||||
with Ctrl+F6 and explicit window raising. Its cause was not established, and
|
||||
baseline behavior was not compared. This is not evidence of working workbook
|
||||
return; verify that workflow separately. Tab rename/add/remove event delivery,
|
||||
physical speech, physical input/Braille and independent real-workbook acceptance
|
||||
remain pending.
|
||||
These results establish recorded private-session behavior. Audible output,
|
||||
physical input/Braille, normal-profile workbook return, tab rename/add/remove
|
||||
event delivery, merged/structurally edited headings, large-sheet responsiveness
|
||||
and independent real-workbook acceptance still need their own checks. Earlier
|
||||
browser checks recorded an editing-title failure and one non-reproducible mode
|
||||
failure; this pass does not establish current live browser acceptance.
|
||||
|
||||
## Calc startup focus diagnosis
|
||||
|
||||
@@ -323,9 +318,11 @@ LibreOffice documents this shortcut in its
|
||||
Automatic focus stealing would interfere with intentional toolbar use and
|
||||
is not part of the reader's handling.
|
||||
|
||||
The native Calc test focuses its window once and uses ordinary key sequences
|
||||
afterward. It also checks F6 followed by Ctrl+F6 and resumed cell speech/Braille.
|
||||
Its fresh-profile virtual display does not cover this normal-profile startup
|
||||
The native Calc test activates its initial window, establishes document focus
|
||||
with F6 followed by Ctrl+F6, and uses ordinary key sequences within each document.
|
||||
Explicit window-manager activation switches workbooks and focuses the dialog.
|
||||
It also checks later F6/Ctrl+F6 recovery and resumed cell speech/Braille.
|
||||
Its fresh-profile private i3 display does not cover this normal-profile startup
|
||||
failure. The same production test also passed under private i3 with the copied
|
||||
normal profile after Ctrl+F6 recovery, asserting recorded cell speech and
|
||||
Braille along with editing and sheet switching. Verify startup and recovery
|
||||
|
||||
@@ -130,12 +130,16 @@ func TestNativeLibreOfficeCalc(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
activate := func(window string) {
|
||||
t.Helper()
|
||||
if err := exec.CommandContext(ctx, "xdotool", "windowactivate", "--sync", window).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Focus once. Refocusing before every key sequence can hide failures
|
||||
// to retain or recover Calc's document focus during normal keyboard use.
|
||||
if err := exec.CommandContext(ctx, "xdotool", "windowfocus", window).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
press("Escape", "ctrl+Home", "Right")
|
||||
activate(window)
|
||||
press("Escape", "F6", "ctrl+F6", "ctrl+Home", "Right")
|
||||
waitSpeech("42, B1")
|
||||
waitFrame("42 B1")
|
||||
press("Down")
|
||||
@@ -277,7 +281,9 @@ func TestNativeLibreOfficeCalc(t *testing.T) {
|
||||
press("ctrl+Page_Up", "ctrl+Home", "Right", "Down")
|
||||
waitSpeech("y row header, Beta, B2")
|
||||
// A heading edit is visible on the next cell snapshot, without a cache.
|
||||
press("Up", "8", "Return")
|
||||
press("Up")
|
||||
waitSpeech("Alpha row header, 42, B1")
|
||||
press("8", "Return")
|
||||
waitSpeech("y row header, Beta, B2")
|
||||
waitFrame("y 8 Beta B2")
|
||||
commands <- input.WhereAmI
|
||||
@@ -320,10 +326,7 @@ func TestNativeLibreOfficeCalc(t *testing.T) {
|
||||
if secondWindow == "" {
|
||||
t.Fatal("second private workbook window missing")
|
||||
}
|
||||
// A virtual display without a window manager does not focus new windows.
|
||||
if err := exec.CommandContext(ctx, "xdotool", "windowfocus", secondWindow).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
activate(secondWindow)
|
||||
press("ctrl+F6", "ctrl+Page_Down", "ctrl+Home", "Down")
|
||||
waitSpeech("blank, A2")
|
||||
commands <- input.WhereAmI
|
||||
@@ -338,4 +341,55 @@ func TestNativeLibreOfficeCalc(t *testing.T) {
|
||||
waitSpeech("blank, A2")
|
||||
commands <- input.WhereAmI
|
||||
waitSpeech("d column header, blank, A2")
|
||||
// Return to the first workbook, then back again: identical sheet names
|
||||
// must retain independent choices and fresh cells in both directions.
|
||||
// Use normal navigation here, without the Ctrl+F6 recovery shortcut.
|
||||
activate(window)
|
||||
press("ctrl+Home", "Down")
|
||||
waitSpeech("blank, A2")
|
||||
commands <- input.WhereAmI
|
||||
waitSpeech("Gamma column header, blank, A2")
|
||||
waitFrame("Gamma A2")
|
||||
activate(secondWindow)
|
||||
press("ctrl+Home", "Down")
|
||||
waitSpeech("blank, A2")
|
||||
commands <- input.WhereAmI
|
||||
waitSpeech("d column header, blank, A2")
|
||||
waitFrame("d A2")
|
||||
// Dialog activation still resolves its focused control. Cancelling it
|
||||
// must leave the document navigable with the same header choices.
|
||||
press("ctrl+h")
|
||||
dialogWindow := ""
|
||||
deadline = time.Now().Add(5 * time.Second)
|
||||
for dialogWindow == "" && time.Now().Before(deadline) {
|
||||
output, _ := exec.CommandContext(ctx, "xdotool", "search", "--onlyvisible", "--name", "^Find and Replace$").Output()
|
||||
if candidates := strings.Fields(string(output)); len(candidates) == 1 {
|
||||
dialogWindow = candidates[0]
|
||||
} else {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
if dialogWindow == "" {
|
||||
t.Fatal("private Find and Replace dialog window missing")
|
||||
}
|
||||
activate(dialogWindow)
|
||||
press("x")
|
||||
waitSpeech("x")
|
||||
press("Escape")
|
||||
deadline = time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
output, _ := exec.CommandContext(ctx, "xdotool", "search", "--onlyvisible", "--name", "^Find and Replace$").Output()
|
||||
if len(strings.Fields(string(output))) == 0 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("private Find and Replace dialog did not close")
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
activate(secondWindow)
|
||||
press("ctrl+F6", "ctrl+Home", "Down")
|
||||
waitSpeech("blank, A2")
|
||||
commands <- input.WhereAmI
|
||||
waitSpeech("d column header, blank, A2")
|
||||
}
|
||||
@@ -174,13 +174,19 @@ func TestRunReachesSoundStopAfterEarlierServiceStopError(t *testing.T) {
|
||||
}
|
||||
sounds := &recordingSound{started: make(chan struct{})}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
completed := make(chan error, 1)
|
||||
// Sound starts first; cancellation there races later service startup.
|
||||
// The startup callback runs only once the complete runtime is ready.
|
||||
runtimeStarted := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
completed <- Run(ctx, Options{
|
||||
Logger: slog.New(slog.DiscardHandler),
|
||||
Settings: &settings,
|
||||
newSound: func() soundService { return sounds },
|
||||
Logger: slog.New(slog.DiscardHandler),
|
||||
Settings: &settings,
|
||||
OpenPreferences: true,
|
||||
onPreferences: func() { close(runtimeStarted) },
|
||||
newSound: func() soundService { return sounds },
|
||||
newCommands: func() commandListener {
|
||||
return silentCommands{}
|
||||
},
|
||||
@@ -190,9 +196,9 @@ func TestRunReachesSoundStopAfterEarlierServiceStopError(t *testing.T) {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-sounds.started:
|
||||
case <-runtimeStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("sound service did not start")
|
||||
t.Fatal("runtime did not finish startup")
|
||||
}
|
||||
cancel()
|
||||
select {
|
||||
@@ -217,13 +223,17 @@ func TestRunReportsSoundStopError(t *testing.T) {
|
||||
soundError := errors.New("stop cue failed")
|
||||
sounds := &recordingSound{started: make(chan struct{}), stopError: soundError}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
completed := make(chan error, 1)
|
||||
runtimeStarted := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
completed <- Run(ctx, Options{
|
||||
Logger: slog.New(slog.DiscardHandler),
|
||||
Settings: &settings,
|
||||
newSound: func() soundService { return sounds },
|
||||
Logger: slog.New(slog.DiscardHandler),
|
||||
Settings: &settings,
|
||||
OpenPreferences: true,
|
||||
onPreferences: func() { close(runtimeStarted) },
|
||||
newSound: func() soundService { return sounds },
|
||||
newCommands: func() commandListener {
|
||||
return silentCommands{}
|
||||
},
|
||||
@@ -235,9 +245,9 @@ func TestRunReportsSoundStopError(t *testing.T) {
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-sounds.started:
|
||||
case <-runtimeStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("sound service did not start")
|
||||
t.Fatal("runtime did not finish startup")
|
||||
}
|
||||
cancel()
|
||||
select {
|
||||
|
||||
@@ -184,7 +184,11 @@ func (adapter *Adapter) recoverForeground() {
|
||||
continue
|
||||
}
|
||||
if state.Epoch == adapter.Foreground().Epoch && adapter.matchesForeground(window, nativeObjectID(window)) {
|
||||
target := C.cthulhu_focused_descendant(window)
|
||||
target := adapter.libreOfficeFrameTarget(window)
|
||||
officeFrame := target != nil
|
||||
if target == nil {
|
||||
target = C.cthulhu_focused_descendant(window)
|
||||
}
|
||||
if target == nil {
|
||||
target = (*C.AtspiAccessible)(C.g_object_ref(C.gpointer(window)))
|
||||
}
|
||||
@@ -202,6 +206,9 @@ func (adapter *Adapter) recoverForeground() {
|
||||
target = child
|
||||
}
|
||||
snapshot := snapshotFromNativeWithCaret(target, true, false, false, adapter.captureSound.Load(), adapter.captureTextAttributes.Load())
|
||||
if officeFrame {
|
||||
snapshot.ApplicationProcess = adapter.applicationProcess(target)
|
||||
}
|
||||
event := NewEvent(WindowActivatedEvent, snapshot)
|
||||
event.Window = nativeObjectID(window)
|
||||
name := C.atspi_accessible_get_name(window, nil)
|
||||
|
||||
@@ -11,15 +11,19 @@ import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// libreOfficeFocusTarget resolves a Calc editor's direct text children and
|
||||
// preserves the sheet source for selection events. F2 focuses its temporary
|
||||
// panel; the paragraph can appear later, on typing.
|
||||
// libreOfficeFocusTarget keeps Office frame activation separate from document
|
||||
// focus, resolves a Calc editor's direct text children, and preserves the sheet
|
||||
// source for selection events. F2 focuses its temporary panel; the paragraph
|
||||
// can appear later, on typing.
|
||||
// The callback owns and releases the returned reference. No sheet is walked.
|
||||
func (adapter *Adapter) libreOfficeFocusTarget(event *C.AtspiEvent, eventType string) *C.AtspiAccessible {
|
||||
if eventType != FocusEvent && eventType != FocusedStateEvent && eventType != SelectionChangedEvent ||
|
||||
if eventType != FocusEvent && eventType != FocusedStateEvent && eventType != SelectionChangedEvent && eventType != WindowActivatedEvent ||
|
||||
!IsLibreOfficeProcess(adapter.applicationProcess(event.source)) {
|
||||
return nil
|
||||
}
|
||||
if eventType == WindowActivatedEvent {
|
||||
return adapter.libreOfficeFrameTarget(event.source)
|
||||
}
|
||||
role := C.atspi_accessible_get_role(event.source, nil)
|
||||
if eventType == SelectionChangedEvent {
|
||||
// Keep the sheet as the selection source. Its first selected cell
|
||||
@@ -79,6 +83,18 @@ func (adapter *Adapter) libreOfficeFocusTarget(event *C.AtspiEvent, eventType st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Both real activation events and X11 foreground recovery must avoid searching
|
||||
// Calc's previous view while it rebuilds controls on workbook return. Own the
|
||||
// frame and let the following cell/editor events establish document focus.
|
||||
// Dialogs retain ordinary focused-control resolution. The caller owns the ref.
|
||||
func (adapter *Adapter) libreOfficeFrameTarget(source *C.AtspiAccessible) *C.AtspiAccessible {
|
||||
if !IsLibreOfficeProcess(adapter.applicationProcess(source)) ||
|
||||
C.atspi_accessible_get_role(source, nil) != C.ATSPI_ROLE_FRAME {
|
||||
return nil
|
||||
}
|
||||
return (*C.AtspiAccessible)(C.g_object_ref(C.gpointer(source)))
|
||||
}
|
||||
|
||||
func captureLibreOfficeContext(source *C.AtspiAccessible, snapshot *Snapshot) {
|
||||
if !IsLibreOfficeProcess(snapshot.ApplicationProcess) || snapshot.Defunct {
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -540,13 +541,14 @@ func TestDisableKillsGroupDescendantsAndJoinsPipes(t *testing.T) {
|
||||
wait_signal(t, session.Done())
|
||||
// SIGKILL delivery/reaping is scheduled by the kernel; wait for /proc to
|
||||
// report disappearance or zombie state, not an arbitrary sleep duration.
|
||||
// A task that exits during ReadFile can return ESRCH rather than ENOENT.
|
||||
deadline := time.NewTimer(2 * time.Second)
|
||||
defer deadline.Stop()
|
||||
ticker := time.NewTicker(time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
data, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
||||
if os.IsNotExist(err) {
|
||||
if os.IsNotExist(err) || errors.Is(err, syscall.ESRCH) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
Vendored
+1
-1
@@ -54,6 +54,6 @@ web-browse-selection,src/cthulhu/scripts/web/script.py;src/cthulhu/script_utilit
|
||||
web-browse-braille-line,src/cthulhu/caret_navigation.py;src/cthulhu/scripts/web/script.py;src/cthulhu/scripts/web/braille_generator.py,braille,Follow accepted browse-caret and structural navigation with the current native text line and virtual cursor,,internal/atspi internal/review internal/app,implemented,"go test -race ./internal/app ./internal/review ./internal/atspi ./internal/braille -count=1; CTHULHU_NATIVE_BROWSER_TESTS=modes,tables,keys python3 tests/native_browser.py",Private Brave/Xvfb mode/table/key checks pass on 2026-10-02; physical Xorg pending.,,Existing browse speech assertions pass in the private fixture.,Queued native line and cursor frames pass; physical HumanWare NLS eReader acceptance pending.,Reads run on the bounded navigation worker only while braille is enabled; preserve spaces and Unicode offsets. Table frames retain existing cell presentation. Complete cross-object visual-line assembly and virtual-caret routing remain open. See docs/testing/braille.md.
|
||||
web-explicit-link-names,src/cthulhu/scripts/web/script_utilities.py:_treatObjectAsWhole,web,Read explicitly named links as whole objects without losing descendant badges or ordinary link navigation,Storm Dragon,internal/atspi internal/review internal/app,in-progress,"go test -race ./...; CTHULHU_NATIVE_BROWSER_TESTS=namedlinks,browser,live,keys python3 tests/native_browser.py",Private Brave/Xorg named-link and key checks passed,,Fixture speech verified; real Steam listening pending,Named-link browse braille uses the full accessible label; physical check pending,docs/testing/steam-browser.md; actual Steam explicit-name exposure and game-card acceptance remain live gates
|
||||
steam-notifications,src/cthulhu/scripts/apps/steamwebhelper/script.py;tests/test_steam_notification_regressions.py,web,Coalesce verified Steam background toasts and present once through speech braille and notification history,Storm Dragon,internal/webscripts internal/atspi internal/app,in-progress,go test -race ./...; python3 tests/native_webscripts.py,Private GTK/AT-SPI process matching and alert-root capture passed,,Event-loop background delivery and Chromium isolation passed; real Steam toast pending,Event-loop flash verified; physical Steam toast display pending,docs/testing/steam-browser.md; independent root and bus queues deliberately prevent unrelated toast timestamps from merging
|
||||
libreoffice-calc-basic,Orca src/orca/scripts/apps/soffice/script.py; Orca src/orca/ax_utilities_table.py; Orca src/orca/speech_generator.py,libreoffice,Present Calc cells and recover logical focus around editing and sheet changes,Storm Dragon,internal/app internal/atspi internal/presentation,in-progress,GOCACHE=/tmp/cthulhu-go-cache go test -race ./internal/atspi ./internal/app ./internal/presentation -count=1; /usr/bin/python3 tests/native_libreoffice.py,Private real LibreOffice Calc 26.2.6.3 on Xvfb and a private AT-SPI bus validates cell movement blanks F2 typing Escape Enter and sheet changes.,,Recorded speech assertions pass; audible and physical-input acceptance pending.,Recorded value and coordinate frames pass; physical Braille and editor routing acceptance pending.,Current Orca is the primary Office reference. Dedicated application policy follows verified process identity after foreground/security gates; native capture remains bounded. Formula presence and explicit formula/basic Where Am I speech are implemented with bounded uncached cell attributes and recorded real-Calc checks; physical acceptance remains pending. Bounded selection deltas for small ranges and row/column selections plus count summaries for large selections and Select All are implemented. Real Calc checks cover disjoint cells and huge selections; physical speech/input/Braille acceptance remains pending. Dynamic row and column headers now use session choices scoped by provider bus document view window and exact sheet name; native reads are bounded to two cells. Four assignable commands preserve existing shortcuts. Real Calc checks cover heading edits independent axis clearing and sheet round trips; physical speech/input/Braille acceptance remains pending. Full-row reading Writer workflows and full topology reporting for large selections remain open. See LIBREOFFICE_SUPPORT.md.
|
||||
libreoffice-calc-basic,Orca src/orca/scripts/apps/soffice/script.py; Orca src/orca/ax_utilities_table.py; Orca src/orca/speech_generator.py,libreoffice,Present Calc cells and recover logical focus around editing and sheet changes,Storm Dragon,internal/app internal/atspi internal/presentation,in-progress,GOCACHE=/tmp/cthulhu-go-cache go test -race ./internal/atspi ./internal/app ./internal/presentation -count=1; /usr/bin/python3 tests/native_libreoffice.py,Private real LibreOffice Calc 26.2.6.3 on Xvfb and a private AT-SPI bus validates cell movement blanks F2 typing Escape Enter and sheet changes.,,Recorded speech assertions pass; audible and physical-input acceptance pending.,Recorded value and coordinate frames pass; physical Braille and editor routing acceptance pending.,Current Orca is the primary Office reference. Dedicated application policy follows verified process identity after foreground/security gates; native capture remains bounded. Formula presence and explicit formula/basic Where Am I speech are implemented with bounded uncached cell attributes and recorded real-Calc checks; physical acceptance remains pending. Bounded selection deltas for small ranges and row/column selections plus count summaries for large selections and Select All are implemented. Real Calc checks cover disjoint cells and huge selections; physical speech/input/Braille acceptance remains pending. Dynamic row and column headers now use session choices scoped by provider bus document view window and exact sheet name; native reads are bounded to two cells. Four assignable commands preserve existing shortcuts. Private i3 Calc checks cover heading edits independent axis clearing sheet round trips both workbook-return directions with distinct headings and Find and Replace typing/cancellation. Frame activation and X11 foreground recovery avoid stale descendant searches; physical speech/input/Braille acceptance remains pending. Full-row reading Writer workflows and full topology reporting for large selections remain open. See LIBREOFFICE_SUPPORT.md.
|
||||
steam-shader-progress,Steam shader dialog AT-SPI events,web,Report Steam Vulkan shader percentages through shared progress output,,internal/webscripts internal/app,in-progress,go test ./internal/app ./internal/webscripts ./internal/presentation ./internal/sound -count=1; python3 tests/native_progress.py; CTHULHU_NATIVE_BROWSER_TESTS=progress python3 tests/native_browser.py,Private GTK and Brave HTML/ARIA progress checks passed.,,Tone requests verified; live Steam listening pending.,Unrelated Braille ownership covered by unit tests; physical check pending.,English static shader-status name changes only; verified steamwebhelper identity; see PROGRESS_BARS.md.
|
||||
wayland-shared-keyboard-access,src/cthulhu/input_event_manager.py,input,Monitor keys and grab reader shortcuts through the shared accessibility keyboard interface,Storm Dragon,internal/platform/wayland internal/app internal/sessionbus,in-progress,Private dbus-run-session race tests cover wire protocol runtime dispatch configuration rollback secure transitions and cleanup; go test ./... -count=1; python3 tests/check_speech.py,Existing native_x11.py keyboard harness passed; browser tabs and editing passed; browser modes passed on isolated rerun after one failure whose cause was not established.,Shared D-Bus fixture passed without an X display. Live niri and physical key suppression remain unverified.,Pending live listening on Wayland,Pending physical Braille on Wayland,No compositor-specific code. See WAYLAND_SUPPORT.md for supported commands limitations and live acceptance. X11 production listeners are unchanged; the suspension probe now links its existing xkbcommon dependency.
|
||||
|
@@ -2,6 +2,7 @@
|
||||
"""Real Calc through the production reader, in a private X11/AT-SPI session."""
|
||||
|
||||
import os
|
||||
from contextlib import ExitStack
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import signal
|
||||
@@ -26,8 +27,29 @@ def run_inside():
|
||||
gi.require_version("Atspi", "2.0")
|
||||
from gi.repository import Atspi
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="cthulhu-libreoffice-") as temporary:
|
||||
with tempfile.TemporaryDirectory(prefix="cthulhu-libreoffice-") as temporary, ExitStack() as cleanup:
|
||||
root = Path(temporary)
|
||||
# Workbook and modal-dialog activation need a window manager's focus
|
||||
# protocol, rather than bare XSetInputFocus on a virtual display.
|
||||
wmConfig = root / "i3.config"
|
||||
wmConfig.write_text(
|
||||
'font pango:monospace 10\nfocus_follows_mouse no\n'
|
||||
f'ipc-socket {root / "i3.sock"}\n', encoding="utf-8",
|
||||
)
|
||||
windowManager = subprocess.Popen(
|
||||
["i3", "-a", "-c", str(wmConfig)],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
def stop_window_manager():
|
||||
if windowManager.poll() is None:
|
||||
windowManager.terminate()
|
||||
windowManager.wait(timeout=5)
|
||||
|
||||
cleanup.callback(stop_window_manager)
|
||||
wait_for(lambda: "0x" in subprocess.check_output(
|
||||
["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"], text=True),
|
||||
"Private i3 window manager missing")
|
||||
profile = root / "profile" / "user"
|
||||
profile.mkdir(parents=True)
|
||||
(profile / "registrymodifications.xcu").write_text(
|
||||
@@ -142,7 +164,7 @@ def main():
|
||||
environment = dict(os.environ, XDG_SESSION_TYPE="x11",
|
||||
XDG_CONFIG_HOME=temporary, XDG_CACHE_HOME=temporary,
|
||||
XDG_DATA_HOME=temporary)
|
||||
for name in ("DBUS_SESSION_BUS_ADDRESS", "AT_SPI_BUS_ADDRESS", "WAYLAND_DISPLAY"):
|
||||
for name in ("DBUS_SESSION_BUS_ADDRESS", "AT_SPI_BUS_ADDRESS", "WAYLAND_DISPLAY", "I3SOCK"):
|
||||
environment.pop(name, None)
|
||||
subprocess.run(
|
||||
["xvfb-run", "-a", "dbus-run-session", "--", sys.executable,
|
||||
|
||||
Reference in new issue
Block a user