Add a description column to the reading settings table under speech.
This commit is contained in:
1 parent
ffcea2efea
commit
41283a5d8d
7 files changed
+219
-46
No files matched your search
@@ -130,6 +130,18 @@ changes the running reader, and saved choices survive restart and can be set
|
||||
per application. Older capitalization styles are imported into the corresponding
|
||||
beep or “cap” checkbox.
|
||||
|
||||
## Reading settings descriptions
|
||||
|
||||
Under Speech, the Reading settings table has three columns: Enabled, Setting
|
||||
and Description. Each description explains what the checkbox changes, with
|
||||
examples where useful. Use Left and Right to read individual cells and Space
|
||||
to toggle the selected setting from any column. With full-row reading enabled,
|
||||
moving between rows reads the state, title and description together. Further
|
||||
input interrupts speech as usual.
|
||||
|
||||
See [reading settings verification](docs/testing/reading-settings.md) for
|
||||
automated checks and live listening steps.
|
||||
|
||||
## Flat review spelling and phonetic hints
|
||||
|
||||
Press Numpad 2 twice to hear the current character phonetically. Press Numpad 5
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Reading settings descriptions
|
||||
|
||||
Build a separate test binary and run the focused checks:
|
||||
|
||||
```sh
|
||||
go build -o /tmp/cthulhu-reading-descriptions ./cmd/cthulhu
|
||||
go test ./internal/preferences ./internal/presentation -count=1
|
||||
go test ./internal/app -run 'GUITable|PauseChunks' -count=1
|
||||
CTHULHU_BINARY=/tmp/cthulhu-reading-descriptions CTHULHU_PREFS_READING_ONLY=1 /usr/bin/python3 tests/native_preferences.py
|
||||
```
|
||||
|
||||
The native check uses private Xvfb, D-Bus and AT-SPI services. It checks all
|
||||
three column headers, a description for every setting, checkbox states,
|
||||
Left/Right and Up/Down navigation, Space from the description column,
|
||||
Tab/Shift+Tab, Apply/reload and Cancel. The reader tests check full-row speech
|
||||
including a third cell, individual-cell speech and interruption.
|
||||
|
||||
Before submission, review the diff and check the rebuilt reader in a live
|
||||
session:
|
||||
|
||||
1. Open Preferences, select Speech and enter Reading settings. With Speak full
|
||||
row in GUI tables enabled, use Up and Down and confirm the state, setting
|
||||
name and explanation are spoken in that order.
|
||||
2. Press another navigation key before an explanation finishes. Confirm the
|
||||
earlier speech stops and the new row is announced promptly.
|
||||
3. Use Left and Right to reach all three cells. Confirm the description is
|
||||
read in full, without repeating the setting title or checkbox state.
|
||||
4. Press Space from each column and confirm it toggles only the selected
|
||||
setting. Apply, close and reopen preferences to check saved state. Change a
|
||||
checkbox again and Cancel to confirm the change is discarded.
|
||||
5. Check Tab and Shift+Tab into and out of the table. Disable full-row reading
|
||||
and confirm arrows read individual cells. Restore the preferred setting.
|
||||
6. Check the modifier-key table and an application table to confirm their
|
||||
existing row and cell reading still works. Check the new descriptions on a
|
||||
physical Braille display when available.
|
||||
|
||||
Automated checks do not establish audible speech, physical keyboard or Braille
|
||||
acceptance, or behavior of the installed reader.
|
||||
@@ -64,6 +64,7 @@ func flatReviewCommand(command input.Command) (review.FlatUnit, int) {
|
||||
|
||||
func presentFlatReviewResult(logger *slog.Logger, speech speechEnqueuer, result review.Result) {
|
||||
text := ""
|
||||
boundary := false
|
||||
if result.Err != nil {
|
||||
logger.Warn("flat review failed", "error", result.Err)
|
||||
switch {
|
||||
@@ -75,16 +76,24 @@ func presentFlatReviewResult(logger *slog.Logger, speech speechEnqueuer, result
|
||||
text = "Could not find current location."
|
||||
}
|
||||
} else if !result.Moved && (result.FlatStep != 0 || result.FlatUnit == review.WordAbove || result.FlatUnit == review.WordBelow) {
|
||||
// Python's sequential review stops silently at window boundaries.
|
||||
return
|
||||
if result.FlatUnit != review.Line {
|
||||
return
|
||||
}
|
||||
boundary = true
|
||||
if result.FlatStep < 0 {
|
||||
text = "Top of screen."
|
||||
} else {
|
||||
text = "Bottom of screen."
|
||||
}
|
||||
} else {
|
||||
text = presentation.FlatReviewText(result.FlatPosition.Text, result.FlatUnit == review.Character || result.FlatUnit == review.LineEnd)
|
||||
}
|
||||
request := speechRequest{Text: text, Role: "text", Cause: "flat-review", Character: result.Err == nil && (result.FlatUnit == review.Character || result.FlatUnit == review.LineEnd)}
|
||||
if result.Err == nil && result.FlatUnit == review.Line {
|
||||
hasContent := result.Err == nil && !boundary
|
||||
request := speechRequest{Text: text, Role: "text", Cause: "flat-review", Character: hasContent && (result.FlatUnit == review.Character || result.FlatUnit == review.LineEnd)}
|
||||
if hasContent && result.FlatUnit == review.Line {
|
||||
request.Line = &speechLine{Object: result.FlatPosition.Object, Raw: result.FlatPosition.Text}
|
||||
}
|
||||
if result.Err == nil {
|
||||
if hasContent {
|
||||
set_review_spelling(&request, result.FlatPosition.Text, result.FlatSpeech)
|
||||
}
|
||||
speech.Enqueue(request)
|
||||
|
||||
@@ -76,6 +76,14 @@ func TestProcessEventsPresentsFlatReviewThroughSpeechQueue(t *testing.T) {
|
||||
expect("Review field, text")
|
||||
worker.ReadFlat(review.Line, 0)
|
||||
expect("a 猫!oo")
|
||||
worker.ReadFlat(review.Line, -1)
|
||||
expect("Top of screen.")
|
||||
worker.ReadFlat(review.Line, 0)
|
||||
expect("a 猫!oo")
|
||||
worker.ReadFlat(review.Line, 1)
|
||||
expect("Bottom of screen.")
|
||||
worker.ReadFlat(review.Line, 0)
|
||||
expect("a 猫!oo")
|
||||
worker.ReadFlat(review.Character, 1)
|
||||
expect("space")
|
||||
worker.ReadFlat(review.Character, 1)
|
||||
@@ -122,11 +130,47 @@ func TestFlatReviewResultUsesNativeFocusAndRejectsOldPage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatReviewLineBoundaryFeedback(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
step int
|
||||
want string
|
||||
}{
|
||||
{"top", -1, "Top of screen."},
|
||||
{"bottom", 1, "Bottom of screen."},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
queue := &autocompleteRequestQueue{requests: make(chan speechRequest, 1)}
|
||||
result := review.Result{
|
||||
FlatUnit: review.Line, FlatStep: tc.step, FlatSpeech: review.FlatSpell,
|
||||
FlatPosition: review.FlatPosition{Text: " Edge line"},
|
||||
}
|
||||
for range 2 {
|
||||
presentFlatReviewResult(slog.New(slog.DiscardHandler), queue, result)
|
||||
select {
|
||||
case request := <-queue.requests:
|
||||
if request.Text != tc.want || request.Cause != "flat-review" {
|
||||
t.Fatalf("boundary request = %+v", request)
|
||||
}
|
||||
if request.Line != nil || request.Character || len(request.Spelling) != 0 {
|
||||
t.Fatalf("boundary retained content speech metadata: %+v", request)
|
||||
}
|
||||
default:
|
||||
t.Fatal("boundary was silent")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatReviewErrorsAndSilentBoundary(t *testing.T) {
|
||||
queue := &fakeSpeechQueue{spoken: make(chan string, 2)}
|
||||
logger := slog.New(slog.DiscardHandler)
|
||||
for _, result := range []review.Result{
|
||||
{FlatUnit: review.Line, FlatStep: 1},
|
||||
{FlatUnit: review.Word, FlatStep: -1},
|
||||
{FlatUnit: review.Word, FlatStep: 1},
|
||||
{FlatUnit: review.Character, FlatStep: -1},
|
||||
{FlatUnit: review.Character, FlatStep: 1},
|
||||
{FlatUnit: review.WordAbove},
|
||||
{FlatUnit: review.WordBelow},
|
||||
} {
|
||||
|
||||
@@ -79,17 +79,25 @@ func TestGUITableNormalizedFocusRetainsOwnerAndTitle(t *testing.T) {
|
||||
other.Source.BusName = ":1.99"
|
||||
adapter.events <- other
|
||||
expect_speech(second, 6, "checked, Break speech into chunks between pauses")
|
||||
// A third description cell participates in full-row speech, while
|
||||
// horizontal movement reads just that cell.
|
||||
description := "Reads numbers one digit at a time."
|
||||
rowText := "unchecked, Speak numbers as digits, " + description
|
||||
third := cell_event("/reading/2/state", "unchecked", rowText, 2, 0, atspi.SelectionChangedEvent)
|
||||
expect_speech(third, 7, rowText)
|
||||
descriptionCell := cell_event("/reading/2/description", description, rowText, 2, 2, atspi.ActiveDescendantEvent)
|
||||
expect_speech(descriptionCell, 8, description)
|
||||
button := atspi.NewSnapshot("/preferences/apply", "Apply", "button", "", "")
|
||||
button.BusName = window.BusName
|
||||
focus := atspi.NewEvent(atspi.FocusEvent, button)
|
||||
focus.Window = window
|
||||
expect_speech(focus, 7, "Apply, button")
|
||||
expect_speech(focus, 9, "Apply, button")
|
||||
adapter.events <- up // A table selection after leaving the table is noise.
|
||||
// Returning through a cell focus event establishes ownership and speaks
|
||||
// the table name again, without changing the user's row-reading option.
|
||||
first.OriginPath = first.Source.Path
|
||||
expect_speech(first, 8, "Reading settings, unchecked, Only speak displayed text")
|
||||
expect_speech(second, 9, "checked, Break speech into chunks between pauses")
|
||||
expect_speech(first, 10, "Reading settings, unchecked, Only speak displayed text")
|
||||
expect_speech(second, 11, "checked, Break speech into chunks between pauses")
|
||||
cancel()
|
||||
<-completed
|
||||
}
|
||||
@@ -4,46 +4,80 @@
|
||||
#include "native_internal.h"
|
||||
|
||||
static GtkListStore *reading_store;
|
||||
static const struct { const char *id; const char *label; } reading_rows[] = {
|
||||
{"only_speak_displayed_text", "Only speak displayed text"},
|
||||
{"automatically_read_title_changes", "Automatically read title changes"},
|
||||
{"automatically_read_clipboard_changes", "Automatically read clipboard changes"},
|
||||
{"phonetic_character_hints", "Phonetic character hints"},
|
||||
{"enable_pause_breaks", "Break speech into chunks between pauses"},
|
||||
{"speak_numbers_as_digits", "Speak numbers as digits"},
|
||||
{"speak_multicase_strings_as_words", "Speak multicase strings as words"},
|
||||
{"enable_indentation", "Enable indentation presentation"},
|
||||
{"speak_blank_lines", "Speak blank lines"},
|
||||
{"enable_mnemonic_speaking", "Speak object mnemonics"},
|
||||
{"enable_position_speaking", "Speak child position"},
|
||||
{"enable_tutorial_messages", "Speak tutorial messages"},
|
||||
{"speak_description", "Speak object descriptions"},
|
||||
{"messages_are_detailed", "System messages are detailed"},
|
||||
{"use_color_names", "Speak colors as names"},
|
||||
{"speak_misspelled_indicator", "Speak misspelled-word indicator"},
|
||||
{"speak_context_blockquote", "Announce block quotes during navigation"},
|
||||
{"speak_context_list", "Announce lists during navigation"},
|
||||
{"speak_context_table", "Announce tables during navigation"},
|
||||
{"speak_context_panel", "Announce panels during navigation"},
|
||||
{"speak_context_landmark", "Announce landmarks during navigation"},
|
||||
{"speak_context_non_landmark_form", "Announce forms during navigation"},
|
||||
{"read_full_row_in_gui_table", "Speak full row in GUI tables"},
|
||||
{"read_full_row_in_document_table", "Speak full row in document tables"},
|
||||
{"read_full_row_in_spreadsheet", "Speak full row in spreadsheets"},
|
||||
{"present_tooltips", "Present tooltips"},
|
||||
{"enable_mouse_review", "Speak object under mouse"},
|
||||
{"say_all_context_blockquote", "Announce block quotes in Say All"},
|
||||
{"say_all_context_list", "Announce lists in Say All"},
|
||||
{"say_all_context_table", "Announce tables in Say All"},
|
||||
{"say_all_context_panel", "Announce panels in Say All"},
|
||||
{"say_all_context_non_landmark_form", "Announce forms in Say All"},
|
||||
{"say_all_context_landmark", "Announce landmarks in Say All"},
|
||||
static const struct { const char *id; const char *label; const char *description; } reading_rows[] = {
|
||||
{"only_speak_displayed_text", "Only speak displayed text",
|
||||
"Limits reading to displayed names, text and values. Omits extra information such as control types, states and container announcements."},
|
||||
{"automatically_read_title_changes", "Automatically read title changes",
|
||||
"Announces changes to the active window's title, such as a new page title in a browser."},
|
||||
{"automatically_read_clipboard_changes", "Automatically read clipboard changes",
|
||||
"Automatically reads new text when the clipboard contents change, such as after copying text."},
|
||||
{"phonetic_character_hints", "Phonetic character hints",
|
||||
"When navigating characters with Left or Right, adds a phonetic name after a 300 ms pause, for example, a then alpha. Further input cancels the hint."},
|
||||
{"enable_pause_breaks", "Break speech into chunks between pauses",
|
||||
"Separates parts of an announcement with pauses, such as between a control's name, type and state."},
|
||||
{"speak_numbers_as_digits", "Speak numbers as digits",
|
||||
"Reads numbers one digit at a time, for example, 123 as one two three."},
|
||||
{"speak_multicase_strings_as_words", "Speak multicase strings as words",
|
||||
"Separates words where a lowercase letter is followed by an uppercase letter, for example, saveFile as save File."},
|
||||
{"enable_indentation", "Enable indentation presentation",
|
||||
"Reports changes in a line's leading spaces and tabs using the selected indentation presentation mode."},
|
||||
{"speak_blank_lines", "Speak blank lines",
|
||||
"Says blank when navigating to an empty line, instead of leaving it silent."},
|
||||
{"enable_mnemonic_speaking", "Speak object mnemonics",
|
||||
"Announces a control's access key when the application provides one, such as Alt+O for an Open button."},
|
||||
{"enable_position_speaking", "Speak child position",
|
||||
"Announces an item's position within a group when available, for example, 2 of 5."},
|
||||
{"enable_tutorial_messages", "Speak tutorial messages",
|
||||
"Adds brief instructions for using the focused control, such as Press Space to toggle for a checkbox."},
|
||||
{"speak_description", "Speak object descriptions",
|
||||
"Reads additional descriptive text supplied by the application for a control or window."},
|
||||
{"messages_are_detailed", "System messages are detailed",
|
||||
"Reads a notification's full message when available. When disabled, reads just its summary when one is provided."},
|
||||
{"use_color_names", "Speak colors as names",
|
||||
"Uses color names when reporting text colors, instead of their numeric color values."},
|
||||
{"speak_misspelled_indicator", "Speak misspelled-word indicator",
|
||||
"Says misspelled when reading text that the application's spell checker has marked as incorrect."},
|
||||
{"speak_context_blockquote", "Announce block quotes during navigation",
|
||||
"Announces when you enter or leave a block quote while navigating text."},
|
||||
{"speak_context_list", "Announce lists during navigation",
|
||||
"Announces when you enter or leave a list while navigating."},
|
||||
{"speak_context_table", "Announce tables during navigation",
|
||||
"Announces when you enter or leave a data table while navigating."},
|
||||
{"speak_context_panel", "Announce panels during navigation",
|
||||
"Announces when you enter or leave a panel or group of controls while navigating."},
|
||||
{"speak_context_landmark", "Announce landmarks during navigation",
|
||||
"Announces when you enter or leave a landmark, such as a page's main content or navigation region."},
|
||||
{"speak_context_non_landmark_form", "Announce forms during navigation",
|
||||
"Announces when you enter or leave a form that is not identified as a landmark."},
|
||||
{"read_full_row_in_gui_table", "Speak full row in GUI tables",
|
||||
"Reads all cells in a row when moving between rows in application tables, including this settings table. Left and Right read individual cells."},
|
||||
{"read_full_row_in_document_table", "Speak full row in document tables",
|
||||
"Reads the whole row when navigating rows in a document or web page table. Moving between columns reads individual cells."},
|
||||
{"read_full_row_in_spreadsheet", "Speak full row in spreadsheets",
|
||||
"Reads the whole row when navigating rows in a spreadsheet. Moving between columns reads individual cells."},
|
||||
{"present_tooltips", "Present tooltips",
|
||||
"Reads tooltip text when the application displays it."},
|
||||
{"enable_mouse_review", "Speak object under mouse",
|
||||
"Reads the object under the mouse pointer as you move the mouse."},
|
||||
{"say_all_context_blockquote", "Announce block quotes in Say All",
|
||||
"Announces entry into and exit from block quotes during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
{"say_all_context_list", "Announce lists in Say All",
|
||||
"Announces entry into and exit from lists during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
{"say_all_context_table", "Announce tables in Say All",
|
||||
"Announces entry into and exit from data tables during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
{"say_all_context_panel", "Announce panels in Say All",
|
||||
"Announces entry into and exit from panels or groups of controls during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
{"say_all_context_non_landmark_form", "Announce forms in Say All",
|
||||
"Announces entry into and exit from forms that are not landmarks during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
{"say_all_context_landmark", "Announce landmarks in Say All",
|
||||
"Announces entry into and exit from landmarks, such as main content or navigation regions, during continuous reading with Say All. This is separate from navigation announcements."},
|
||||
};
|
||||
|
||||
enum {
|
||||
READING_ID,
|
||||
READING_LABEL,
|
||||
READING_ENABLED,
|
||||
READING_DESCRIPTION,
|
||||
READING_N_COLUMNS
|
||||
};
|
||||
static void name_reading_toggle_cells(void);
|
||||
@@ -94,7 +128,7 @@ static void name_reading_toggle_cells(void) {
|
||||
}
|
||||
|
||||
void prefs_init_reading_settings(void) {
|
||||
reading_store = gtk_list_store_new(READING_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||
reading_store = gtk_list_store_new(READING_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
|
||||
for (unsigned int i = 0; i < G_N_ELEMENTS(reading_rows); i++) {
|
||||
GtkTreeIter iter;
|
||||
gtk_list_store_append(reading_store, &iter);
|
||||
@@ -102,6 +136,7 @@ void prefs_init_reading_settings(void) {
|
||||
READING_ID, reading_rows[i].id,
|
||||
READING_LABEL, reading_rows[i].label,
|
||||
READING_ENABLED, FALSE,
|
||||
READING_DESCRIPTION, reading_rows[i].description,
|
||||
-1);
|
||||
}
|
||||
|
||||
@@ -118,6 +153,12 @@ void prefs_init_reading_settings(void) {
|
||||
"Enabled", toggle_renderer, "active", READING_ENABLED, NULL);
|
||||
gtk_tree_view_append_column(view, enabled_column);
|
||||
gtk_tree_view_append_column(view, setting_column);
|
||||
GtkCellRenderer *descriptionRenderer = gtk_cell_renderer_text_new();
|
||||
g_object_set(descriptionRenderer, "wrap-mode", PANGO_WRAP_WORD_CHAR, "wrap-width", 280, NULL);
|
||||
GtkTreeViewColumn *descriptionColumn = gtk_tree_view_column_new_with_attributes(
|
||||
"Description", descriptionRenderer, "text", READING_DESCRIPTION, NULL);
|
||||
gtk_tree_view_column_set_expand(descriptionColumn, TRUE);
|
||||
gtk_tree_view_append_column(view, descriptionColumn);
|
||||
g_signal_connect(view, "key-press-event", G_CALLBACK(reading_key_press), NULL);
|
||||
name_reading_toggle_cells();
|
||||
}
|
||||
|
||||
@@ -845,11 +845,15 @@ def inside():
|
||||
readingTree = next(node for node in nodes if node.name == "Reading settings"
|
||||
and node.getRole() == pyatspi.ROLE_TABLE)
|
||||
readingTable = readingTree.queryTable()
|
||||
assert readingTable.nColumns == 2
|
||||
assert readingTable.nColumns == 3
|
||||
assert [readingTable.getColumnDescription(column) for column in range(3)] == [
|
||||
"Enabled", "Setting", "Description"]
|
||||
for row in range(readingTable.nRows):
|
||||
stateCell = readingTable.getAccessibleAt(row, 0)
|
||||
settingCell = readingTable.getAccessibleAt(row, 1)
|
||||
descriptionCell = readingTable.getAccessibleAt(row, 2)
|
||||
assert settingCell.name and settingCell.name not in ("checked", "unchecked"), settingCell.name
|
||||
assert descriptionCell.name and descriptionCell.name != settingCell.name, settingCell.name
|
||||
checked = stateCell.getState().contains(pyatspi.STATE_CHECKED)
|
||||
assert stateCell.name == ("checked" if checked else "unchecked"), stateCell.name
|
||||
titleRow = next(row for row in range(readingTable.nRows)
|
||||
@@ -868,6 +872,9 @@ def inside():
|
||||
if readingTable.getAccessibleAt(row, 1).name == "Phonetic character hints")
|
||||
hintCell = readingTable.getAccessibleAt(hintRow, 0)
|
||||
assert hintCell.name == "unchecked" and not hintCell.getState().contains(pyatspi.STATE_CHECKED)
|
||||
hintDescription = readingTable.getAccessibleAt(hintRow, 2).name
|
||||
assert "Left or Right" in hintDescription and "300 ms" in hintDescription, hintDescription
|
||||
assert "cancels the hint" in hintDescription, hintDescription
|
||||
for label, expected in (("Phonetic character hints", "checked"),
|
||||
("Announce lists in Say All", "checked"),
|
||||
("Present tooltips", "checked"),
|
||||
@@ -888,12 +895,26 @@ def inside():
|
||||
assert stateCell.getState().contains(pyatspi.STATE_CHECKED) != wasChecked
|
||||
key("Right")
|
||||
wait_for(lambda: readingTable.getAccessibleAt(0, 1).getState().contains(pyatspi.STATE_FOCUSED), "Right did not focus the setting label")
|
||||
key("Right")
|
||||
descriptionCell = readingTable.getAccessibleAt(0, 2)
|
||||
wait_for(lambda: descriptionCell.getState().contains(pyatspi.STATE_FOCUSED), "Right did not focus the description")
|
||||
# Space still toggles the selected row from the new text column.
|
||||
key("space")
|
||||
wait_for(lambda: stateCell.getState().contains(pyatspi.STATE_CHECKED) == wasChecked,
|
||||
"Space in the description did not toggle the checkbox")
|
||||
key("space")
|
||||
wait_for(lambda: stateCell.getState().contains(pyatspi.STATE_CHECKED) != wasChecked,
|
||||
"Space in the description did not restore the checkbox")
|
||||
key("Down")
|
||||
wait_for(lambda: readingTable.isRowSelected(1), "Down did not select the next reading setting")
|
||||
key("Up")
|
||||
wait_for(lambda: readingTable.isRowSelected(0), "Up did not return to the first reading setting")
|
||||
key("Left")
|
||||
wait_for(lambda: readingTable.getAccessibleAt(0, 1).getState().contains(pyatspi.STATE_FOCUSED), "Left did not return to the setting label")
|
||||
key("Left")
|
||||
wait_for(lambda: readingTable.getAccessibleAt(0, 0).getState().contains(pyatspi.STATE_FOCUSED), "Left did not focus the checkbox")
|
||||
key("Right", "Right")
|
||||
wait_for(lambda: descriptionCell.getState().contains(pyatspi.STATE_FOCUSED), "description focus missing before Tab")
|
||||
key("Tab")
|
||||
wait_for(lambda: not any(node.getState().contains(pyatspi.STATE_FOCUSED) for node in walk(readingTree)), "Tab did not leave reading settings")
|
||||
key("shift+Tab")
|
||||
@@ -922,7 +943,7 @@ def inside():
|
||||
focus(named("Cancel")); key("space")
|
||||
assert process.wait(timeout=5) == 0
|
||||
assert responses.read_remaining() == "", "Cancel submitted reading changes"
|
||||
print("PASS: reading table, Say All, tooltip and mouse choices, Space, arrows, Tab/Shift+Tab, Apply/reload, Cancel")
|
||||
print("PASS: three-column reading table and descriptions, Say All, tooltip and mouse choices, Space, arrows, Tab/Shift+Tab, Apply/reload, Cancel")
|
||||
return
|
||||
if os.environ.get("CTHULHU_PREFS_MODIFIERS_ONLY") == "1":
|
||||
modifierTree = named("Cthulhu modifier keys")
|
||||
|
||||
Reference in new issue
Block a user