331 lines
21 KiB
Markdown
331 lines
21 KiB
Markdown
# LibreOffice integration
|
|
|
|
Current Orca is the primary behavioral reference for LibreOffice. Python
|
|
Cthulhu remains available on the `legacy` branch for historical comparisons:
|
|
`git show legacy:src/cthulhu/scripts/apps/soffice/script.py`.
|
|
Do not switch the active Go checkout to inspect that reference.
|
|
|
|
## Architecture
|
|
|
|
Application handling runs after screen-lock and foreground validation, before
|
|
generic focus and text filtering. `internal/app/application_behavior.go`
|
|
routes copied accessibility events by verified provider process identity.
|
|
The LibreOffice handler lives in `internal/app/libreoffice.go`.
|
|
It can normalize an event, request focus adoption, or suppress presentation.
|
|
The reader retains window ownership, input policy, pending-output cancellation,
|
|
review, and speech/Braille delivery. Ordinary applications keep their existing
|
|
event path. Steam's existing integration remains in place.
|
|
|
|
`internal/atspi/libreoffice_native.go` captures bounded Calc metadata on the
|
|
native owner thread. It does not enumerate a sheet. The controller and
|
|
presentation code never retain native objects or perform provider queries.
|
|
`internal/presentation/libreoffice.go` owns the corresponding speech
|
|
formatting; LibreOffice Braille formatting stays beside its controller.
|
|
|
|
Logical focus belongs to the existing event loop. Selection comparison keeps
|
|
one bounded snapshot for the focused sheet, keyed by its application bus and
|
|
accessible sheet path and admitted only within the owned document window.
|
|
It is retired on sheet/application focus changes and ordinary owned-focus
|
|
reset, including destruction, foreground loss, and screen lock. There are no
|
|
selection workers, timers, or retained native references. Dynamic header
|
|
choices live in the adapter's bounded, synchronized store of copied identities
|
|
and coordinates. Commands update that store without provider queries; the
|
|
native owner captures header text. Choices use application bus, document/view,
|
|
window and the provider's sheet name. Calc recreates table objects on tab
|
|
switches, so a table path cannot identify a sheet across those switches.
|
|
Focus loss cancels output while preserving document header choices.
|
|
Use the existing input machinery for additional Office commands.
|
|
Application code must not bypass security/foreground gates or manage its own
|
|
speech connection, key grabs, or background event loop.
|
|
|
|
Keep generic table operations and genuinely shared fixes in their existing
|
|
subsystems. Keep verified LibreOffice exceptions in the dedicated files.
|
|
Adding later Office features should extend these boundaries, without
|
|
reorganizing unrelated reader code or building a speculative plugin framework.
|
|
|
|
## First slice
|
|
|
|
- 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.
|
|
- Recorded Braille preserves value spacing followed by the address. A cell
|
|
component is not an editable text route.
|
|
- F2 resolves a temporary editor's focused paragraph when available.
|
|
If the paragraph appears later, its first owned text/caret event establishes
|
|
editor focus before generic filtering.
|
|
- Post-editing sheet focus is adopted silently. Spreadsheet capacity and
|
|
cell/container state chatter are omitted as in Orca.
|
|
- Ancestor focus claims are suppressed when captured ancestry identifies the
|
|
existing focused spreadsheet object.
|
|
- Non-editor caret events do not displace a focused cell. Selection events do
|
|
not promote the first selected cell of a range into logical focus.
|
|
|
|
References inspected: Orca commit `cd58a88ed` (2026-10-14),
|
|
`scripts/apps/soffice/script.py`, `script_utilities.py`,
|
|
`speech_generator.py`, `braille_generator.py`, and shared
|
|
`speech_generator.py` / `ax_utilities_table.py`.
|
|
|
|
## Formula slice
|
|
|
|
- Normal cell speech appends “has formula” after the value and address.
|
|
Only-displayed-text mode continues to omit synthetic metadata.
|
|
- Basic Where Am I includes the formula after the cell information.
|
|
- **Cthulhu+=** presents the current cell's formula in the desktop layout,
|
|
following Orca's `where_am_i_presenter.py:present_cell_formula`. The semantic
|
|
command is `present-cell-formula`; it is listed in preferences and Learn Mode.
|
|
As in current Orca, the laptop layout leaves it unbound; assign a shortcut
|
|
in Key Bindings if desired.
|
|
- A cell without a formula gives its value, or “empty” for an empty cell.
|
|
The command currently applies to Calc cells; other focus reports “Not in a
|
|
table.” Temporary F2 editors are separate objects, so leave editing before
|
|
requesting the cell's formula.
|
|
- Native capture reads the uncached `formula` attribute (or `Formula`, as
|
|
accepted by Orca) alongside the existing bounded cell metadata. Formula
|
|
string-literal spacing is preserved. Expressions are bounded to the native
|
|
snapshot field limit, retaining the beginning of long expressions.
|
|
Commands consume the latest accepted focus snapshot; they never scan a sheet
|
|
or query a provider from the event loop. A provider change without an event
|
|
requires revisiting the cell to refresh its snapshot.
|
|
- Formula speech uses the existing command and speech queues, foreground and
|
|
screen-lock gates. Normal Braille retains the value and address, with no new
|
|
routes or formula suffix. The explicit formula command uses the existing
|
|
system-message flash path and its Braille preferences, as in Orca.
|
|
|
|
## Selection slice
|
|
|
|
- Shift+Arrow selection growth and shrinkage announce selected/unselected
|
|
coordinates. Adjacent cells form compact rectangles; disjoint cells retain
|
|
their gaps. The active anchor is left to ordinary cell presentation.
|
|
- Full rows and columns use row/column labels. Select All says “All cells
|
|
selected”; leaving a selection says “Selection cleared.” Ctrl+A can first
|
|
select the used area; press it again to select the entire sheet.
|
|
These announcements require a provider event. An independent AT-SPI observer
|
|
on LibreOffice 26.2.6.3 confirmed that Shift+Up after extending whole rows,
|
|
and Shift+Left after extending whole columns, change the selection without
|
|
emitting a selection event. Ctrl+Home can also clear those selections without
|
|
an event. Immediate announcements for those cases remain unsupported; the
|
|
next selection event refreshes the snapshot. The reader does not poll or
|
|
infer selection changes from keystrokes.
|
|
- Small selections capture at most 64 cells, rows, or columns. Messages with
|
|
more than eight rectangles use a count. Larger selections use a count and
|
|
two boundary samples for duplicate suppression, without treating endpoints
|
|
as proof of a filled rectangle. Overlapping count-only selections report
|
|
the new total rather than falsely announcing the entire old range unselected.
|
|
- The GTK bridge caps cell counts at 2147483647; partial-sheet selections at
|
|
that limit say “At least … cells selected.” Whole-sheet selection is checked
|
|
separately. Large selections with identical counts and boundary samples
|
|
cannot be distinguished by this bounded snapshot; interior-only changes may
|
|
therefore be suppressed. Full topology/delta reporting remains future work.
|
|
- Native capture uses a fixed number of count queries and at most 64 selected
|
|
positions. It requests no selected-row/column arrays. Ordinary small cell
|
|
selections skip full-row/column counts; LibreOffice itself scans its row
|
|
marks when larger selections need a selected-row count. Fixed RPC counts
|
|
do not guarantee a provider's response time.
|
|
- Selection events keep both logical and retained native focus on the active
|
|
cell. Formula commands, object review, cell Braille frames, and routing
|
|
continue to use that cell. Selection messages use the existing system-message
|
|
Braille flash path and preferences. Only-displayed-text mode suppresses them.
|
|
- Current Orca's selection callbacks and utilities guide the behavior. The
|
|
[LibreOffice spreadsheet provider](https://github.com/LibreOffice/core/blob/libreoffice-26-2/sc/source/ui/Accessibility/AccessibleSpreadsheet.cxx)
|
|
and [GTK selection bridge](https://github.com/LibreOffice/core/blob/libreoffice-26-2/vcl/unx/gtk3/a11y/atkselection.cxx)
|
|
were also inspected for selection ordering, row-count cost, and count limits.
|
|
|
|
## Dynamic headers slice
|
|
|
|
- In **Preferences → Key Bindings**, assign shortcuts to “Use the current row
|
|
as Calc column headers,” “Use the current column as Calc row headers,” and
|
|
the corresponding clear commands. All four are available in both layouts
|
|
and Learn Mode, and are unbound by default. Orca's Cthulhu+Shift+C equivalent
|
|
conflicts with Cthulhu's existing clipboard command; it is preserved.
|
|
Semantic commands are `set-column-headers-row`, `set-row-headers-column`,
|
|
`clear-column-headers-row` and `clear-row-headers-column`.
|
|
- Set column headers from any cell in the heading row, and row headers from
|
|
any cell in the label column. Each axis can be changed or cleared separately.
|
|
This follows Orca's shared `table_navigator.py` and `ax_utilities_table.py`,
|
|
rather than just its soffice script.
|
|
- Automatic speech prefixes the row heading when the row changes and the
|
|
column heading when the column changes. A cell does not repeat itself as
|
|
its own heading. Blank headings add no label. Basic Where Am I includes both
|
|
headings; normal Braille includes both through its existing context path.
|
|
Only-displayed-text suppresses automatic speech metadata. Formula commands,
|
|
active-cell focus, selections and editor routing retain their existing paths.
|
|
- Header text is read afresh with at most two direct cell lookups per snapshot,
|
|
bounded to the normal field limit. There is no row/sheet enumeration, polling,
|
|
worker or native reference retained by the header feature. Provider response
|
|
times still apply. Commands consume the accepted cell snapshot; a provider
|
|
mutation without a new event requires revisiting the cell.
|
|
- Choices survive sheet/workbook switches and lock/unlock for this reader
|
|
session. They are separate per document/view and sheet; they are not saved to
|
|
the workbook or settings. A reader restart or document/window destruction
|
|
clears them. The store accepts up to 64 sheets; clearing both axes frees a slot.
|
|
- Sheet names come from the verified provider's uncached `page-name` attribute,
|
|
preserving exact whitespace. Missing or oversized identity metadata disables
|
|
these commands. This metadata is exposed by LibreOffice's
|
|
[spreadsheet document provider](https://github.com/LibreOffice/core/blob/libreoffice-26-2/sc/source/ui/Accessibility/AccessibleDocument.cxx).
|
|
Tab-list child changes and tab-name events conservatively clear choices for
|
|
that document window. Reassign headers
|
|
after adding, removing or renaming sheets. This depends on provider events;
|
|
silent structural mutations cannot be detected. Tab selection alone preserves
|
|
choices. Merged headings and structural row/column edits remain unverified;
|
|
header choices are coordinates, not references that follow moved content.
|
|
|
|
## Next slices
|
|
|
|
1. Spreadsheet full-row reading needs a bounded,
|
|
explicitly defined extent; the generic setting is not full Calc support.
|
|
2. Writer paragraph navigation, selection, Say All, spell-check presentation,
|
|
and Braille panning. Compare each actual workflow with current Orca.
|
|
|
|
Merged cells, overflow announcements, large-sheet responsiveness, other
|
|
LibreOffice backends, and independent-workbook acceptance remain separate
|
|
checks. These slices do not establish complete Office support.
|
|
|
|
## Verification
|
|
|
|
```sh
|
|
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 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 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,
|
|
and switching between two sheets. Formula assertions cover a numeric result,
|
|
an empty string result, literal spaces, replacement with a number, formula
|
|
and basic Where Am I requests, and non-formula cells on a second sheet.
|
|
Selection checks cover cell-range growth/shrinkage, clearing, row/column extension,
|
|
Select All, disjoint cells made through the provider selection
|
|
API, a 2097150-cell selection, and a partial-sheet selection above GTK's count
|
|
limit. Formula commands during selection verify preservation of the active cell.
|
|
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 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.
|
|
|
|
These automated checks do not establish audible speech, physical input, or
|
|
physical Braille. Before submission, review the diff and test a rebuilt binary:
|
|
|
|
1. In a disposable Calc workbook, populate text, numbers, formulas and blanks.
|
|
Arrow between them and listen for the value followed by its address.
|
|
2. Visit a formula cell: listen for “has formula,” then use **Cthulhu+=**
|
|
and basic Where Am I. With Braille flash messages enabled, verify that the
|
|
formula command flashes its result and restores the cell display. Try a formula returning an empty string, a formula
|
|
containing a string literal with repeated spaces, a number, and a blank.
|
|
Replace a formula with a number and revisit it; the formula cue must clear.
|
|
With only-displayed-text enabled, automatic speech should omit the cue.
|
|
3. Press F2, edit, cancel with Escape, then edit and commit with Enter. Check
|
|
character/word navigation in an existing cell editor and continued arrows.
|
|
4. Switch sheets and switch between two workbooks. Return from menus, dialogs,
|
|
the formula bar, and another application; check logical focus recovery.
|
|
5. Check the cell value/address, spacing, editor cursor and return to cell
|
|
display on physical Braille. Text routing applies inside the editor.
|
|
6. Use Shift+Arrows to grow and shrink a range; check selected/unselected
|
|
addresses and continued active-cell/formula output. Try Ctrl+Space,
|
|
Shift+Space, their Shift+Arrow extensions, and Ctrl+A twice. Clear each
|
|
selection with an arrow. Select disjoint areas with Ctrl+click and a large
|
|
area with Ctrl+Shift+Arrows; check gaps in small selections and count summaries
|
|
for large ones. Enable Braille flash messages and verify selection messages
|
|
restore the cell display. Enable only-displayed-text and verify selection
|
|
chatter is omitted. Repeat on another sheet and in a second workbook.
|
|
7. Assign the four header commands in Key Bindings. In a workbook with headings
|
|
across row 1 and labels down column A, set both axes. Move horizontally and
|
|
vertically; listen for the changed axis's heading, value and coordinate.
|
|
Use Where Am I to hear both headings. Try a blank heading and a formula
|
|
heading, edit a heading and revisit a data cell, then clear each axis
|
|
separately. Check complete Braille context and that command flashes restore
|
|
the updated cell frame. Verify assignment/persistence of shortcuts after
|
|
reopening preferences; header choices themselves last only for this session.
|
|
Switch sheets, switch to a second workbook with the same sheet names, leave
|
|
Calc and return, and lock/unlock. Check independent choices and resumed
|
|
headings. Add, remove and rename a disposable sheet; reassign headers after
|
|
the conservative reset. Test structural row/column edits and merged headings
|
|
separately; these cases have no acceptance claim yet.
|
|
8. Repeat ordinary GTK table movement/multiselection, browser typing and table
|
|
reading, and Steam navigation. Check lock/unlock and stale/background events.
|
|
|
|
Human review and testing remain required by `CONTRIBUTING.md`. Passing this
|
|
fixture alone is not sufficient for submission or live accessibility acceptance.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
A normal-profile startup under i3 can leave Calc accepting arrow keys while
|
|
omitting active-cell accessibility events. In the observed case, cell events
|
|
were followed by focus claims from the floating Form Design and Form Controls
|
|
toolbars; subsequent arrows produced formula-bar text updates but no new cell
|
|
events. A separate AT-SPI observer reproduced the missing events without
|
|
Cthulhu. The affected workbook worked with a fresh profile, and a generic
|
|
workbook reproduced the failure with a disposable copy of the normal profile.
|
|
This establishes a profile-dependent LibreOffice focus/event problem; the
|
|
precise profile setting responsible has not been isolated.
|
|
|
|
Press **Ctrl+F6** to return focus to the document, then retry the arrows.
|
|
This directly restored active-cell events in the isolated reproduction.
|
|
LibreOffice documents this shortcut in its
|
|
[Calc accessibility keyboard guide](https://help.libreoffice.org/latest/en-GB/text/scalc/guide/keyboard.html).
|
|
Automatic focus stealing would interfere with intentional toolbar use and
|
|
is not part of the reader's handling.
|
|
|
|
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
|
|
under the actual window manager, plus audible speech and physical Braille,
|
|
separately.
|