4 Commits
Author SHA1 Message Date
Storm Dragon 88e8627867 Updated docs. 2026-10-20 13:39:27 -04:00
Storm Dragon dcdeab256e Merge branch 'deedra' into testing
Combine relation-only edit-label capture while retaining bounded text reads,
password-target exclusion, UTF-8 output limits and menu checkability capture.
Keep both label regression fixtures and the existing foreground test dispatch.
Preserve Wine helper diagnostics and recovery documentation, and retain the
current parity records alongside the incoming edit-label entry.

Validation:
- go test ./... -count=1 -timeout=180s
- go vet ./...
- CTHULHU_NATIVE_EDIT_LABELS=1 python3 tests/native_atspi.py
- Isolated TestNativeTextLabelRelations
- python3 tests/native_atspi.py
- Parity ledger validation: 65 rows
- Python syntax and staged diff checks

Native GTK table fixtures emitted ATK warnings; all assertions passed.
Live speech and physical Braille were not retested.
2026-10-20 13:35:29 -04:00
deedra 8ef838e16e Speak relation-only labels for unnamed edit fields
Irctest exposes its settings-field labels through AT-SPI LABELLED_BY
relations while leaving the fields' accessible names empty. Cthulhu
previously captured only the name, so focus speech announced the text
role and contents without Nickname, Username, or Real name.

Resolve direct label targets in the shared native snapshot capture for
unnamed text, entry, and password-text objects. Preserve explicit names,
combine multiple label targets in relation order, and use label/static
text only when a target has no accessible name. Bound relation scans,
target queries, and UTF-8 output; release all native references and avoid
reading password targets. No application-specific handling is required.

Add an isolated GTK fixture and native regression test exercising capture
and speech formatting for single labels, multiple labels, explicit names,
unlabelled fields, and labelled password fields without password contents.
Expose the probe through CTHULHU_NATIVE_EDIT_LABELS=1 in native_atspi.py.
Record verification and installed-reader acceptance in the parity ledger.

Validation:
- go test ./... -count=1
- CTHULHU_NATIVE_EDIT_LABELS=1 python3 tests/native_atspi.py
- go vet ./...
- Build the reader and the brlapi-tagged Braille helper.
- Validate configuration and testdata/parity-ledger.csv.
- Read-only live irctest capture includes all three settings-field labels
  in formatted speech.
- Install both tested binaries and replace the reader in its original
  desktop session; verify service ownership and installed build hashes.
- Maintainer confirms the installed reader now speaks the field labels.

The full suite passes outside the sandbox; its socket and process checks
require access unavailable inside the sandbox. Physical Braille acceptance
remains pending. Preserve cthulhu.log outside the commit.
2026-10-20 09:29:24 -07:00
deedra 538ff88028 Document Go installation and standalone Wine helper recovery
Record the user-local Go installation used by the working reader: install
cthulhu and cthulhu-braille together, validate the configuration, and use
--replace from the original desktop session. Explain that another session
bus can start a second reader instead of replacing the intended instance.

Document the complete five-file standalone wine-access installation and
the legacy helper path supported by the Go supervisor. Older Python
Cthulhu builds can overwrite the launcher and sidecar at that path while
leaving the native hook files behind. Such a helper can connect to D-Bus
without supplying the current native menu and editor support.

Describe recovery by reinstalling the standalone helper and reloading the
reader, including restarting Wine applications when older hook DLLs are
already loaded. Keep the source/build distinction explicit: rebuilding
the Go reader alone does not refresh wine-access.

Validation for these documentation changes:
- Reviewed the complete diff and ran git diff --check.
- Checked the documented shell commands with bash -n.
- Installed both documented Go build outputs into a temporary directory,
  compared their bytes with the builds, and passed --check-config.
- Verified the installed reader, Braille helper, and all five Wine helper
  files match the builds used for the restored installation.

Runtime validation during the preceding repair:
- Cthulhu internal/wine and internal/remote tests passed; Wine-specific
  internal/app tests passed.
- The legacy installed helper failed the isolated 32-bit editor check;
  the standalone helper passed it.
- Standalone controller, 32-bit late-attachment/restart, and hook-safety
  checks passed on private displays and session buses.
- The user confirmed MUSHclient reading was restored. Physical Braille
  was not checked.

These are documentation changes; the installed behavior already comes
from the existing source revisions, with no new runtime source patch.
2026-10-20 02:44:48 -07:00
8 changed files with 258 additions and 24 deletions

No files matched your search

+8
View File
@@ -4,6 +4,14 @@
Cthulhu must never depend on a specific Linux init system or service manager. This applies to the reader, Tentacles, process supervision, installation, packaging, and required tests. Do not require systemd, `systemd-run`, `systemctl`, or a systemd user manager. Use init-independent Linux interfaces and detect the capabilities actually available. Cthulhu and its Tentacles must work on non-systemd distributions, including Artix.
## Before Starting a Fix or Feature
Inspect `git status --short --branch`, confirm the intended branch and upstream, and complete `git pull --ff-only` before starting implementation. If local and upstream commits have diverged, resolve that divergence with a normal merge before proceeding; never discard local work to make the pull succeed. If a merge or rebase is already in progress, finish it first. Preserve uncommitted changes and diagnostic files; do not automatically stash, reset, or clean them.
On every branch other than `testing` or `master`, also fetch the maintained repository's latest `testing` branch and merge it into the working branch. With the maintained repository configured as `origin`, use `git fetch origin` followed by `git merge origin/testing`. Resolve conflicts, run the relevant checks, and finish any merge commit before starting the new work. An unresolved or failed synchronization blocks implementation. Do not merge `testing` into `master` as part of this preparation; release integration is separate.
After synchronization, check whether the reported issue still exists and inspect recent changes before implementing a fix. Another contributor may already have fixed it. See [the contributor synchronization procedure](CONTRIBUTING.md#update-your-branch-before-starting-work), including instructions for a new branch without an upstream.
## Project Structure & Module Organization
Cthulhu is a Go Linux screen reader. `cmd/` contains executables; `internal/` owns reader subsystems. Embedded audio lives in `assets/sounds/`, Tentacle libraries in `sdk/`, native harnesses in `tests/`, and parity records in `testdata/`. Go tests sit beside implementation files. Arch packaging lives in `distro-packages/Arch-Linux/cthulhu-git/`.
+50
View File
@@ -2,6 +2,27 @@
Read [README.md](README.md) and [AGENTS.md](AGENTS.md) first. Start with a user-visible behavior and reproduce the issue in the current code. The maintained repository is `git.stormux.org/storm/cthulhu`; confirm the intended target branch with the maintainer. The `legacy` branch preserves Python Cthulhu; the current checkout is the Go implementation.
## Update Your Branch Before Starting Work
Update your checkout before starting each fix or feature. This helps avoid fixing an issue that another contributor has already resolved.
1. Run `git status --short --branch` and confirm you are on the intended branch. If a merge or rebase is in progress, finish it first. Preserve any uncommitted work and diagnostic files before updating; do not discard them or automatically stash them.
2. Run `git pull --ff-only` to update the current branch from its configured upstream. A successful pull saying the branch is already up to date also satisfies this step. If Git reports divergent branches, merge the intended upstream into your branch and resolve the conflicts before continuing. Ask for help if you are unsure; do not use a reset or force push to make the error disappear.
3. If your branch is neither `testing` nor `master`, also run:
```sh
git fetch origin
git merge origin/testing
```
These commands bring the latest shared development changes into your contributor branch. They assume `origin` points to the maintained repository. If `origin` is your fork, use the remote for the maintained repository in both commands instead. If you are unsure which remote that is, ask before merging.
4. Resolve any conflicts, run the relevant checks, and complete any merge commit before starting implementation. A failed pull or an unfinished merge means the update is not complete.
5. Reproduce the reported issue again and review recent changes. If it is already fixed, verify the existing fix rather than writing a second implementation.
For a new local branch without an upstream, first update `testing` with `git pull --ff-only`, then create your contributor branch from that updated `testing`. Do this before making changes. Existing contributor branches still need the merge from the maintained repository's latest `testing` described above.
Do not merge `testing` into `master` during this preparation. Updating `master` from its own upstream and integrating a release are separate operations. The historical Python `legacy` branch is not a target for Go changes.
## Proposing a Change
Keep each change to one complete behavior. Describe the reproduction, expected result, affected subsystem, and plausible regressions. A reported application or page is a reproduction case: fix the shared layer when it explains the defect. Use an application-specific script only when a shared fix would be wrong; document and test the exception.
@@ -57,6 +78,35 @@ Preserve event order, GLib/cgo ownership, and native lifetimes. Balance key grab
Tentacles already use an external process protocol: preserve existing host safeguards and author APIs. Keep runnable implementations in external repositories and leave a new installation's catalog empty until repositories are added. Tentacles must not block input, focus, or speech. Use init-independent process supervision for Tentacles by default, with optional explicitly configured cgroup-v2 resource limits. Do not require a particular init system, a service manager, or administrator setup for normal Tentacle use. Preserve descendant cleanup on disable and reader exit. Treat cgroup-v2 containment as resource control, not a same-user security sandbox. Preserve HTTPS artifact and SHA-256 verification before atomic package installation.
### Local installation and reload
After building, install both Go binaries beside one another so the reader can
find its Braille helper:
```sh
install -Dm755 build/cthulhu "$HOME/.local/bin/cthulhu"
install -Dm755 build/cthulhu-braille "$HOME/.local/bin/cthulhu-braille"
"$HOME/.local/bin/cthulhu" --check-config
```
When replacement of the running reader is authorized, reload it from a terminal
in the same desktop session:
```sh
"$HOME/.local/bin/cthulhu" --replace
```
The replacement command finds the running reader through its session D-Bus
service. A terminal using a different session bus can start another reader
instead of replacing the intended one. Preserve the original reader's desktop
session environment when restarting it from automation.
Wine accessibility also needs the separate standalone `wine-access`
installation described in the [README](README.md#wine-access). Building the Go
reader does not build or refresh that helper. After restoring an overwritten
helper, check MUSHclient menus, command typing, and incoming plugin speech;
automated event recordings alone do not establish audible speech acceptance.
## Behavioral References and User-Facing Messages
Inspect relevant settings, bindings, speech, Braille, and toolkit behavior before changing user-visible behavior. Read Python reference files without changing branches, for example:
+16
View File
@@ -206,6 +206,22 @@ Wine/application stderr is ignored. Capture does not log accessible control
contents or change speech delivery; output already present in debug speech
logs can still contain private text. Review debug logs before sharing them.
The standalone helper currently installs under the compatible name
`~/.local/libexec/cthulhu/wine/cthulhu-wine-access.exe`. Its complete installation
contains the launcher, `cthulhu-wine-access.exe.so`, `wine-access-menu.dll`,
`wine-access-menu32.dll`, and `wine-access-menu-host32.exe`. The 32-bit host
supplies native menu and editor feedback for applications such as MUSHclient.
Older Python Cthulhu builds can overwrite the helper launcher and sidecar in
that same directory while leaving the standalone hook files behind. The old
helper can still run and connect to Cthulhu without providing the standalone
menu and editor hooks. If Wine reading disappears after installing an older
Cthulhu build, rebuild and reinstall the separate `wine-access` project, then
reload Cthulhu from the same desktop session. If a Wine application already
loaded a previous hook DLL, save its work and restart it to load the replacement.
See [local installation and reload](CONTRIBUTING.md#local-installation-and-reload)
for the Go reader commands.
Wine typing feedback in standard Edit controls follows the current key echo
settings, including the master switch, character echo, and alphabetic, numeric,
punctuation, space and diacritical categories. Tabs and line breaks follow
+33 -24
View File
@@ -8,44 +8,53 @@ static gchar *cthulhu_capture_text(AtspiAccessible *source, gchar **beforeCaret,
static void cthulhu_capture_document_metrics(AtspiAccessible *source, cthulhu_snapshot *snapshot);
static gchar *cthulhu_table_cell_label(AtspiAccessible *source, guint depth, guint *remaining);
// Some native entries leave Name empty and expose their label only through
// LABELLED_BY. Follow explicit relationships, never nearby text or field values.
// Some toolkits expose an edit field's label only as a relation, leaving its
// accessible name empty. Follow direct targets only, with a shared query and
// output budget; never infer a label from neighbouring controls or field text.
static gchar *cthulhu_text_control_label(AtspiAccessible *source) {
GArray *relations = atspi_accessible_get_relation_set(source, NULL);
if (relations == NULL) return NULL;
GString *result = g_string_new("");
GString *label = g_string_new("");
guint remaining = 16;
for (guint i = 0; i < relations->len && remaining > 0; i++) {
for (guint i = 0; i < MIN(relations->len, 32) && remaining > 0 && label->len < 4096; i++) {
AtspiRelation *relation = g_array_index(relations, AtspiRelation *, i);
if (atspi_relation_get_relation_type(relation) != ATSPI_RELATION_LABELLED_BY) continue;
gint targets = atspi_relation_get_n_targets(relation);
for (gint j = 0; j < targets && remaining > 0; j++, remaining--) {
AtspiAccessible *label = atspi_relation_get_target(relation, j);
if (label == NULL) continue;
gchar *name = atspi_accessible_get_name(label, NULL);
if ((name == NULL || *name == '\0') &&
atspi_accessible_get_role(label, NULL) == ATSPI_ROLE_LABEL) {
AtspiText *text = atspi_accessible_get_text(label);
if (text != NULL) {
gint count = atspi_text_get_character_count(text, NULL);
g_free(name);
name = count > 0 ? atspi_text_get_text(text, 0, MIN(count, 1024), NULL) : NULL;
g_object_unref(text);
gint count = atspi_relation_get_n_targets(relation);
for (gint j = 0; j < count && remaining > 0 && label->len < 4096; j++) {
remaining--;
AtspiAccessible *target = atspi_relation_get_target(relation, j);
if (target == NULL) continue;
AtspiRole role = atspi_accessible_get_role(target, NULL);
gchar *text = NULL;
if (role != ATSPI_ROLE_PASSWORD_TEXT) {
text = atspi_accessible_get_name(target, NULL);
if ((text == NULL || *text == '\0') &&
(role == ATSPI_ROLE_LABEL || role == ATSPI_ROLE_STATIC)) {
g_free(text);
AtspiText *labelText = atspi_accessible_get_text(target);
if (labelText != NULL) {
gint characterCount = atspi_text_get_character_count(labelText, NULL);
text = characterCount > 0 ? atspi_text_get_text(labelText, 0, MIN(characterCount, 1024), NULL) : NULL;
g_object_unref(labelText);
} else {
text = NULL;
}
}
}
if (name != NULL && *g_strstrip(name) != '\0' &&
result->len + strlen(name) + 1 <= 4096) {
if (result->len > 0) g_string_append_c(result, ' ');
g_string_append(result, name);
if (text != NULL && *g_strstrip(text) != '\0') {
if (label->len != 0) g_string_append_c(label, ' ');
const gchar *end = NULL;
g_utf8_validate(text, MIN(strlen(text), 4096 - label->len), &end);
g_string_append_len(label, text, end - text);
}
g_free(name);
g_object_unref(label);
g_free(text);
g_object_unref(target);
}
}
for (guint i = 0; i < relations->len; i++)
g_object_unref(g_array_index(relations, AtspiRelation *, i));
g_array_unref(relations);
return g_string_free(result, FALSE);
return g_string_free(label, FALSE);
}
// Collect only the selected row. Walking an entire table on every arrow key
+91
View File
@@ -0,0 +1,91 @@
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi_test
import (
"context"
"os"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/presentation"
)
// Missing LABELLED_BY capture must fail even when the toolkit leaves Name empty.
func TestNativeEditFieldLabels(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_EDIT_LABELS") != "1" {
t.Skip("requires isolated edit label fixture")
}
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer func() {
stopCtx, stopCancel := context.WithTimeout(context.Background(), 3*time.Second)
defer stopCancel()
if err := adapter.Stop(stopCtx); err != nil {
t.Error(err)
}
}()
tree, err := adapter.CaptureDesktop(ctx)
if err != nil {
t.Fatal(err)
}
root, err := tree.Root()
if err != nil {
t.Fatal(err)
}
fields := map[string]atspi.Snapshot{}
var walk func(atspi.ObjectReference)
walk = func(ref atspi.ObjectReference) {
snapshot, err := tree.Resolve(ref)
if err != nil {
t.Fatal(err)
}
if snapshot.Description != "" {
fields[snapshot.Description] = snapshot
}
child, ok, err := tree.FirstChild(ref)
if err != nil {
t.Fatal(err)
}
for ok {
walk(child)
child, ok, err = tree.NextSibling(child)
if err != nil {
t.Fatal(err)
}
}
}
walk(root)
for _, test := range []struct{ id, name string }{
{"related", "Nickname"},
{"multiple", "Account identifier"},
{"named", "Explicit name"},
{"unlabelled", ""},
{"protected", "Password"},
} {
t.Run(test.id, func(t *testing.T) {
snapshot, ok := fields[test.id]
if !ok {
t.Fatal("missing edit field")
}
if snapshot.Name != test.name {
t.Errorf("field label = %q, want %q", snapshot.Name, test.name)
}
spoken := presentation.FocusText(snapshot)
if test.name != "" && !strings.Contains(spoken, test.name) {
t.Errorf("speech %q omits label %q", spoken, test.name)
}
if test.id == "protected" && (snapshot.Text != "" || strings.Contains(spoken, "secret value")) {
t.Fatal("password contents exposed")
}
})
}
}
+1
View File
@@ -63,3 +63,4 @@ mozilla-leaf-text-reading,Orca src/orca/scripts/web/script_utilities.py,web,Read
thunderbird-local-mail-composition,Orca src/orca/scripts/apps/Thunderbird/script.py;src/orca/script_utilities.py,application,Navigate local folders and message tabs; select messages; compose replies and forwards; save and add attachments,,internal/atspi internal/app tests/native_thunderbird.py,in-progress,go test -race ./internal/atspi ./internal/app ./internal/review ./internal/presentation -count=1 -timeout=120s; CTHULHU_BINARY=/tmp/cthulhu-mozilla-review /usr/bin/python3 tests/native_thunderbird.py,Private Thunderbird 158.0 HTML and plain-text local mailbox checks cover message-tab return; Subject and body editing; reply and reply-all recipients with Cc; inline forwarding; exact selected rows after Shift and Ctrl navigation; received attachment saving with content verification; and adding a local file to an unsent draft.,,SSIP verifies focus handoff and row roles; selection toggling; Subject and body echo; reply and forward editing; attachment menus and picker return; audible acceptance pending.,Event-loop regression checks paragraph cursor and routing metadata; physical display acceptance pending.,Foreground replacement requires a registered application and fresh complete window membership. Editable paragraph events belong only to their focused editable document. Title echoes require owned editable focus and recent editing plus unchanged title prefix/suffix; unrelated status titles still announce. See docs/testing/thunderbird-workflows.md; no sending remote mailbox or page-load-option acceptance claimed.
flatpak-foreground-identity,src/cthulhu/script_utilities.py:canBeActiveWindow,focus,Correlate Flatpak accessibility providers with the X11 foreground across PID namespaces and D-Bus proxies,,internal/platform/xorg internal/atspi,in-progress,go test -race ./internal/platform/xorg ./internal/atspi ./internal/app -count=1; CTHULHU_FOREGROUND_PID=1 python3 tests/native_atspi.py,Private XRes lookup and fallback; real proxy with matching and different synthetic instance metadata; GTK Qt and Brave ownership checks,,Actual Flatpak Steam speech pending,Physical Braille pending; original report also has helper geometry errors,docs/testing/flatpak-foreground.md; preserves active state geometry and sibling ownership checks; no application name exception
present-window-title,Orca src/orca/where_am_i_presenter.py;src/orca/where_am_i_presenter_command_definitions.py,command,Read the foreground window title with Cthulhu+Numpad Enter on desktop or Cthulhu+Slash on laptop,,internal/input internal/app internal/platform/xorg,in-progress,go test ./internal/input ./internal/app ./internal/platform/xorg -count=1; focused title command race checks; python3 tests/check_speech.py,Private Xvfb command dispatch verifies both layout bindings and both release orders. Private X11 and AT-SPI event loop repeats the current title with automatic changes disabled.,,Speech queue assertions passed; physical keyboard and audible acceptance pending.,Braille flash queue assertion passed; physical display acceptance pending.,Reuses copied foreground title metadata and the owned accessible title. Explicit requests bypass automatic-title policy and duplicate suppression without moving focus. Stale native epochs and unrelated native windows cannot supply a title. Lock and terminal handoff remain enforced. Double-press status-bar reading is outside this change. See tests/window_titles.md.
edit-field-relation-labels,src/cthulhu/generator.py:_generateLabel;src/cthulhu/script_utilities.py:displayedLabel,speech,Announce relation-only labels for unnamed edit fields,,internal/atspi internal/presentation,implemented,CTHULHU_NATIVE_EDIT_LABELS=1 python3 tests/native_atspi.py; go test ./... -count=1; go vet ./...,Private GTK/Xvfb verifies single and multiple labels plus existing names and password exclusion. Read-only live irctest capture verifies all three settings labels. Maintainer confirmed installed-reader label speech on physical Xorg on 2026-10-20.,,Formatted speech includes each label. Maintainer confirmed the installed reader speaks irctest settings field labels on 2026-10-20.,Physical Braille check pending.,Direct LABELLED_BY targets supply an empty text or entry or password-field name. Queries and output are bounded; explicit names are preserved. Human diff review remains required before submission.
1 id python_source subsystem behavior owner go_destination status automated_evidence x11_evidence wayland_evidence speech_evidence braille_evidence notes
63 thunderbird-local-mail-composition Orca src/orca/scripts/apps/Thunderbird/script.py;src/orca/script_utilities.py application Navigate local folders and message tabs; select messages; compose replies and forwards; save and add attachments internal/atspi internal/app tests/native_thunderbird.py in-progress go test -race ./internal/atspi ./internal/app ./internal/review ./internal/presentation -count=1 -timeout=120s; CTHULHU_BINARY=/tmp/cthulhu-mozilla-review /usr/bin/python3 tests/native_thunderbird.py Private Thunderbird 158.0 HTML and plain-text local mailbox checks cover message-tab return; Subject and body editing; reply and reply-all recipients with Cc; inline forwarding; exact selected rows after Shift and Ctrl navigation; received attachment saving with content verification; and adding a local file to an unsent draft. SSIP verifies focus handoff and row roles; selection toggling; Subject and body echo; reply and forward editing; attachment menus and picker return; audible acceptance pending. Event-loop regression checks paragraph cursor and routing metadata; physical display acceptance pending. Foreground replacement requires a registered application and fresh complete window membership. Editable paragraph events belong only to their focused editable document. Title echoes require owned editable focus and recent editing plus unchanged title prefix/suffix; unrelated status titles still announce. See docs/testing/thunderbird-workflows.md; no sending remote mailbox or page-load-option acceptance claimed.
64 flatpak-foreground-identity src/cthulhu/script_utilities.py:canBeActiveWindow focus Correlate Flatpak accessibility providers with the X11 foreground across PID namespaces and D-Bus proxies internal/platform/xorg internal/atspi in-progress go test -race ./internal/platform/xorg ./internal/atspi ./internal/app -count=1; CTHULHU_FOREGROUND_PID=1 python3 tests/native_atspi.py Private XRes lookup and fallback; real proxy with matching and different synthetic instance metadata; GTK Qt and Brave ownership checks Actual Flatpak Steam speech pending Physical Braille pending; original report also has helper geometry errors docs/testing/flatpak-foreground.md; preserves active state geometry and sibling ownership checks; no application name exception
65 present-window-title Orca src/orca/where_am_i_presenter.py;src/orca/where_am_i_presenter_command_definitions.py command Read the foreground window title with Cthulhu+Numpad Enter on desktop or Cthulhu+Slash on laptop internal/input internal/app internal/platform/xorg in-progress go test ./internal/input ./internal/app ./internal/platform/xorg -count=1; focused title command race checks; python3 tests/check_speech.py Private Xvfb command dispatch verifies both layout bindings and both release orders. Private X11 and AT-SPI event loop repeats the current title with automatic changes disabled. Speech queue assertions passed; physical keyboard and audible acceptance pending. Braille flash queue assertion passed; physical display acceptance pending. Reuses copied foreground title metadata and the owned accessible title. Explicit requests bypass automatic-title policy and duplicate suppression without moving focus. Stale native epochs and unrelated native windows cannot supply a title. Lock and terminal handoff remain enforced. Double-press status-bar reading is outside this change. See tests/window_titles.md.
66 edit-field-relation-labels src/cthulhu/generator.py:_generateLabel;src/cthulhu/script_utilities.py:displayedLabel speech Announce relation-only labels for unnamed edit fields internal/atspi internal/presentation implemented CTHULHU_NATIVE_EDIT_LABELS=1 python3 tests/native_atspi.py; go test ./... -count=1; go vet ./... Private GTK/Xvfb verifies single and multiple labels plus existing names and password exclusion. Read-only live irctest capture verifies all three settings labels. Maintainer confirmed installed-reader label speech on physical Xorg on 2026-10-20. Formatted speech includes each label. Maintainer confirmed the installed reader speaks irctest settings field labels on 2026-10-20. Physical Braille check pending. Direct LABELLED_BY targets supply an empty text or entry or password-field name. Queries and output are bounded; explicit names are preserved. Human diff review remains required before submission.
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Edit fields whose labels are exposed only through AT-SPI relations."""
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Atk", "1.0")
from gi.repository import Atk, Gtk
window = Gtk.Window(title="Edit label probe")
window.connect("destroy", Gtk.main_quit)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
window.add(box)
def field(identifier, labels, name="", password=False):
entry = Gtk.Entry()
entry.set_text("secret value" if password else "sample value")
entry.set_visibility(not password)
accessible = entry.get_accessible()
accessible.set_name(name)
accessible.set_description(identifier)
for text in labels:
label = Gtk.Label(label=text)
box.pack_start(label, False, False, 0)
accessible.add_relationship(Atk.RelationType.LABELLED_BY, label.get_accessible())
box.pack_start(entry, False, False, 0)
field("related", ["Nickname"])
field("multiple", ["Account", "identifier"])
field("named", ["Other label"], name="Explicit name")
field("unlabelled", [])
field("protected", ["Password"], password=True)
window.show_all()
Gtk.main()
+24
View File
@@ -23,6 +23,9 @@ def wait_for(check, message, timeout=15):
def run_inside():
if os.environ.get("CTHULHU_NATIVE_EDIT_LABELS") == "1":
run_edit_label_probe()
return
if os.environ.get("CTHULHU_FOREGROUND_PID") == "1":
if os.environ.get("CTHULHU_FOREGROUND_PROXY") == "1":
run_proxy_probe()
@@ -52,6 +55,27 @@ def run_inside():
run_thunar_probe()
def run_edit_label_probe():
fixture = subprocess.Popen([sys.executable, str(Path(__file__).with_name("gtk_edit_labels.py"))])
try:
import pyatspi
def find_window():
return any(window.name == "Edit label probe"
for app in pyatspi.Registry.getDesktop(0) for window in app)
wait_for(find_window, "edit label probe missing")
environment = dict(os.environ, GOCACHE="/tmp/cthulhu-go-cache", GOPROXY="off")
subprocess.run(
["go", "test", "./internal/atspi", "-run", "^TestNativeEditFieldLabels$",
"-count=1", "-timeout=25s", "-v"],
cwd=Path(__file__).resolve().parents[1], env=environment, check=True,
)
finally:
fixture.terminate()
fixture.wait(timeout=3)
def run_thunar_probe():
with tempfile.TemporaryDirectory(prefix="cthulhu-thunar-") as temporary:
home = Path(temporary)