Merge branch 'deedra'
This commit is contained in:
commit
4c47cc5fc4
5 files changed
+112
-12
No files matched your search
@@ -244,6 +244,10 @@ It reports accepted typed characters, the current line on focus and menu return,
|
||||
characters and words on horizontal navigation, lines on vertical/page/document-boundary
|
||||
navigation, changed selections, and Backspace/Delete/Cut feedback. Password
|
||||
contents are excluded, and read-only edits do not report unsuccessful deletions.
|
||||
Command-history recalls in MUSHclient report the recalled command, or `blank`
|
||||
when returning to empty input. The input hook observes Up/Down before the
|
||||
application consumes them, so recalls also work when the caret position stays
|
||||
the same. Unrelated programmatic text changes remain silent.
|
||||
Undo refreshes the text snapshot used by subsequent navigation. It can attach to
|
||||
an already-focused editor and resume after the helper restarts.
|
||||
|
||||
|
||||
+29
-1
@@ -15,6 +15,7 @@ typedef struct EditState {
|
||||
WCHAR *text;
|
||||
DWORD length;
|
||||
BOOL busy, known, destroyed;
|
||||
BOOL history_key, history_replaced;
|
||||
WCHAR pending_high;
|
||||
DWORD pending_caret;
|
||||
} EditState;
|
||||
@@ -102,6 +103,10 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
BOOL requested_focus = message == RegisterWindowMessageW(EDIT_FOCUS_MESSAGE);
|
||||
BOOL focus = message == WM_SETFOCUS || requested_focus;
|
||||
BOOL selected = message == EM_SETSEL;
|
||||
// A dispatched arrow belongs to ordinary caret navigation. Applications
|
||||
// such as MUSHclient consume history arrows before DispatchMessage instead.
|
||||
if (navigation || focus) state->history_key = state->history_replaced = FALSE;
|
||||
BOOL history_replacement = message == WM_SETTEXT && state->history_key;
|
||||
if (!navigation && !changing && !focus && !selected && message != EM_SETPASSWORDCHAR)
|
||||
return DefSubclassProc(window, message, wp, lp);
|
||||
|
||||
@@ -152,7 +157,17 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
if (focus) {
|
||||
current_line(window, start, text);
|
||||
kind = WINE_ACCESS_TEXT_FOCUS; report = TRUE;
|
||||
} else if ((navigation || selected) && (start != old_start || end != old_end)) {
|
||||
} else if (history_replacement && result) {
|
||||
// Pretranslated Up/Down recalls arrive as SetWindowText followed by
|
||||
// SetSel, without any WM_KEYDOWN reaching this procedure. Read the whole
|
||||
// recalled command once, including blank and equal-length replacements.
|
||||
if (state->text) copy_range(text, state->text, 0, state->length);
|
||||
else current_line(window, start, text);
|
||||
state->history_key = FALSE;
|
||||
state->history_replaced = TRUE;
|
||||
report = TRUE;
|
||||
} else if ((navigation || (selected && !state->history_replaced)) &&
|
||||
(start != old_start || end != old_end)) {
|
||||
if (start != end && state->text && end <= state->length) {
|
||||
// Speak the changed portion when extending or shrinking a selection.
|
||||
DWORD first = start, last = end;
|
||||
@@ -262,6 +277,19 @@ __declspec(dllexport) LRESULT CALLBACK WineAccessInputHook(int code, WPARAM wp,
|
||||
HWND focus = GetFocus();
|
||||
if (wine_access_watch_edit(focus))
|
||||
SendMessageW(focus, RegisterWindowMessageW(EDIT_FOCUS_MESSAGE), 0, 0);
|
||||
EditState *state = (EditState *)GetPropW(focus, EDIT_STATE_PROPERTY);
|
||||
WCHAR class_name[64];
|
||||
// Scintilla uses the same attachment property with a different state.
|
||||
if (state && GetClassNameW(focus, class_name, 64) &&
|
||||
wine_access_class_matches(class_name, L"Edit") && !state->busy) {
|
||||
const MSG *message = (const MSG *)lp;
|
||||
// Restrict replacement feedback to this retrieved input message.
|
||||
// Timers, unrelated programmatic changes and background edits stay silent.
|
||||
state->history_key = message->hwnd == focus && message->message == WM_KEYDOWN &&
|
||||
(message->wParam == VK_UP || message->wParam == VK_DOWN) &&
|
||||
!(GetKeyState(VK_CONTROL) & 0x8000);
|
||||
state->history_replaced = FALSE;
|
||||
}
|
||||
#ifdef _WIN64
|
||||
wine_access_scintilla_input(focus, (const MSG *)lp);
|
||||
#endif
|
||||
|
||||
+17
-10
@@ -304,9 +304,13 @@ Ctrl+End line reading, and Up/Down navigation in a read-only multiline output
|
||||
control. Typing checks include repeated letters, spaces, Enter/Tab, accented
|
||||
characters, surrogate pairs, selection replacement, documents beyond the full
|
||||
snapshot limit, and silence for password/background/read-only fields and
|
||||
input rejected at the text limit. Both 32-bit and 64-bit fixtures use the same
|
||||
assertions. Lifecycle variants attach after the control has focus or
|
||||
restart the helper without closing it. Every text action must have an established
|
||||
input rejected at the text limit. Command-history checks consume queued Up/Down
|
||||
before dispatch, then replace the text and move the caret as MUSHclient does.
|
||||
They require ordered, single announcements for equal-length and rapid recalls
|
||||
and empty input, with no password, background or unrelated programmatic output.
|
||||
Both 32-bit and 64-bit fixtures use the same assertions. Lifecycle variants
|
||||
attach after the control has focus or restart the helper without closing it.
|
||||
Every text action must have an established
|
||||
focus source, matching the Go reader's filtering contract.
|
||||
|
||||
The Notepad fixture launches Wine's actual `notepad.exe` and uses `SendInput`
|
||||
@@ -315,13 +319,16 @@ It checks resulting document text as well as recorded accessibility output. Thes
|
||||
tests use a recording D-Bus service; they do not run the full reader or prove
|
||||
audible typing echo, physical keys, or a physical braille display.
|
||||
|
||||
For live MUSHclient acceptance, install the helper and reopen MUSHclient so the
|
||||
updated 32-bit DLL can load. In the command field, check Left/Right and
|
||||
Ctrl+Left/Right feedback without submitting a command. Use Ctrl+Tab to reach
|
||||
the read-only output notepad supplied by the Aardwolf review-buffer plugin;
|
||||
check Up/Down line reading and Left/Right characters there, then return to
|
||||
the command field. Check plugin menus and dialogs still work, and repeat after
|
||||
restarting the reader. These hooks provide caret feedback; independent review
|
||||
For live MUSHclient acceptance, install the helper, restart Cthulhu and reopen
|
||||
MUSHclient so the updated 32-bit DLL can load. In the command field, check Left/Right and
|
||||
Ctrl+Left/Right feedback without submitting a command. Use Up/Down to recall
|
||||
existing command history. Check each recalled command is spoken once,
|
||||
including equal-length entries and `blank` when returning to empty input.
|
||||
Do not submit recalled commands just to test speech.
|
||||
Use Ctrl+Tab to reach the read-only output notepad supplied by the Aardwolf
|
||||
review-buffer plugin; check Up/Down line reading and Left/Right characters
|
||||
there, then return to the command field. Check plugin menus and dialogs still
|
||||
work, and repeat after restarting the reader. These hooks provide caret feedback; independent review
|
||||
commands use the optional flat-review protocol described above. Native braille
|
||||
routing into Wine text remains unsupported.
|
||||
|
||||
|
||||
+55
-1
@@ -18,8 +18,24 @@ static void register_control(HINSTANCE instance, const WCHAR *nativeName, const
|
||||
static const WCHAR *editClass = L"Edit";
|
||||
#endif
|
||||
|
||||
static HWND editor, password;
|
||||
static HWND editor, password, history;
|
||||
static int step;
|
||||
static int history_position;
|
||||
|
||||
// Model MUSHclient's PreTranslateMessage: consume queued arrows before the
|
||||
// Edit procedure, then use SetWindowText and SetSel to recall a command.
|
||||
static BOOL history_input(const MSG *message) {
|
||||
if (message->hwnd != history || message->message != WM_KEYDOWN ||
|
||||
(message->wParam != VK_UP && message->wParam != VK_DOWN)) return FALSE;
|
||||
if (message->wParam == VK_UP && history_position < 2) ++history_position;
|
||||
else if (message->wParam == VK_DOWN && history_position > 0) --history_position;
|
||||
else return TRUE;
|
||||
const WCHAR *commands[] = {L"", L"history north", L"history south"};
|
||||
SetWindowTextW(history, commands[history_position]);
|
||||
int length = GetWindowTextLengthW(history);
|
||||
SendMessageW(history, EM_SETSEL, length, length);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void key(UINT key, BOOL control, BOOL shift) {
|
||||
BYTE saved[256], state[256];
|
||||
@@ -194,6 +210,43 @@ static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wp, LPARAM
|
||||
SendMessageW(editor, WM_CHAR, L'%', 1);
|
||||
if (GetWindowTextLengthW(editor) != 4) ExitProcess(2);
|
||||
break;
|
||||
case 41:
|
||||
// Keep this field away from the managed-label regression's static labels.
|
||||
history = CreateWindowW(editClass, L"", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
|
||||
100, 150, 300, 25, window, NULL, GetModuleHandleW(NULL), NULL);
|
||||
SetFocus(history);
|
||||
break;
|
||||
case 42: PostMessageW(history, WM_KEYDOWN, VK_UP, 1); break;
|
||||
case 43: PostMessageW(history, WM_KEYDOWN, VK_UP, 1); break;
|
||||
case 44: PostMessageW(history, WM_KEYDOWN, VK_DOWN, 1); break;
|
||||
case 45: PostMessageW(history, WM_KEYDOWN, VK_DOWN, 1); break;
|
||||
case 46:
|
||||
// Rapid recalls must stay ordered, including equal-length commands.
|
||||
PostMessageW(history, WM_KEYDOWN, VK_UP, 1);
|
||||
PostMessageW(history, WM_KEYDOWN, VK_UP, 1);
|
||||
PostMessageW(history, WM_KEYDOWN, VK_DOWN, 1);
|
||||
break;
|
||||
case 47:
|
||||
// Ordinary navigation after a recall must use the refreshed snapshot.
|
||||
PostMessageW(history, WM_KEYDOWN, VK_LEFT, 1);
|
||||
PostMessageW(history, WM_KEYDOWN, VK_RIGHT, 1);
|
||||
break;
|
||||
case 48:
|
||||
SetWindowTextW(history, L"programmatic history sentinel");
|
||||
PostMessageW(history, WM_KEYDOWN, VK_DOWN, 1);
|
||||
break;
|
||||
case 49:
|
||||
PostMessageW(history, WM_KEYDOWN, VK_DOWN, 1); // no history change
|
||||
break;
|
||||
case 50:
|
||||
SendMessageW(history, EM_SETPASSWORDCHAR, L'*', 0);
|
||||
PostMessageW(history, WM_KEYDOWN, VK_UP, 1);
|
||||
break;
|
||||
case 51:
|
||||
SetFocus(password);
|
||||
SendMessageW(history, EM_SETPASSWORDCHAR, 0, 0);
|
||||
PostMessageW(history, WM_KEYDOWN, VK_UP, 1); // background recall
|
||||
break;
|
||||
default:
|
||||
DestroyWindow(window); break;
|
||||
}
|
||||
@@ -245,6 +298,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show)
|
||||
SetTimer(window, 1, 1500, NULL);
|
||||
MSG message;
|
||||
while (GetMessageW(&message, NULL, 0, 0) > 0) {
|
||||
if (history_input(&message)) continue;
|
||||
TranslateMessage(&message); DispatchMessageW(&message);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -312,6 +312,13 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
||||
typed = [e[2] for e in text_events if e[1] == "text" and "kind=8" in e[5]]
|
||||
assert typed == ["z", "z", "space", "\u00e9", "\U0001f600", "new line", "tab", "q", "L", "\U0001f600"], f"typing echo missing or incorrect: {typed!r}"
|
||||
assert not any(e[1] == "text" and e[2] in ("!", "@", "~", "%") for e in events), "rejected, password or background typing leaked"
|
||||
history_focus = next(e[0] for e in text_events
|
||||
if e[1] == "focus" and e[4] == "blank")
|
||||
history_actions = [e[2] for e in text_events
|
||||
if e[0] == history_focus and e[1] == "text"]
|
||||
assert history_actions == ["history north", "history south", "history north", "blank",
|
||||
"history north", "history south", "history north", "h", "blank", "blank"], \
|
||||
f"command history missing, reordered, duplicated or leaked: {history_actions!r}"
|
||||
if args.fixture == "winforms":
|
||||
assert any(e[2] == "Email" and e[3] == "edit" and e[4] == "alpha beta"
|
||||
for e in text_events), "managed email label/focus missing"
|
||||
|
||||
Reference in new issue
Block a user