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
|
characters and words on horizontal navigation, lines on vertical/page/document-boundary
|
||||||
navigation, changed selections, and Backspace/Delete/Cut feedback. Password
|
navigation, changed selections, and Backspace/Delete/Cut feedback. Password
|
||||||
contents are excluded, and read-only edits do not report unsuccessful deletions.
|
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
|
Undo refreshes the text snapshot used by subsequent navigation. It can attach to
|
||||||
an already-focused editor and resume after the helper restarts.
|
an already-focused editor and resume after the helper restarts.
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -15,6 +15,7 @@ typedef struct EditState {
|
|||||||
WCHAR *text;
|
WCHAR *text;
|
||||||
DWORD length;
|
DWORD length;
|
||||||
BOOL busy, known, destroyed;
|
BOOL busy, known, destroyed;
|
||||||
|
BOOL history_key, history_replaced;
|
||||||
WCHAR pending_high;
|
WCHAR pending_high;
|
||||||
DWORD pending_caret;
|
DWORD pending_caret;
|
||||||
} EditState;
|
} 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 requested_focus = message == RegisterWindowMessageW(EDIT_FOCUS_MESSAGE);
|
||||||
BOOL focus = message == WM_SETFOCUS || requested_focus;
|
BOOL focus = message == WM_SETFOCUS || requested_focus;
|
||||||
BOOL selected = message == EM_SETSEL;
|
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)
|
if (!navigation && !changing && !focus && !selected && message != EM_SETPASSWORDCHAR)
|
||||||
return DefSubclassProc(window, message, wp, lp);
|
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) {
|
if (focus) {
|
||||||
current_line(window, start, text);
|
current_line(window, start, text);
|
||||||
kind = WINE_ACCESS_TEXT_FOCUS; report = TRUE;
|
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) {
|
if (start != end && state->text && end <= state->length) {
|
||||||
// Speak the changed portion when extending or shrinking a selection.
|
// Speak the changed portion when extending or shrinking a selection.
|
||||||
DWORD first = start, last = end;
|
DWORD first = start, last = end;
|
||||||
@@ -262,6 +277,19 @@ __declspec(dllexport) LRESULT CALLBACK WineAccessInputHook(int code, WPARAM wp,
|
|||||||
HWND focus = GetFocus();
|
HWND focus = GetFocus();
|
||||||
if (wine_access_watch_edit(focus))
|
if (wine_access_watch_edit(focus))
|
||||||
SendMessageW(focus, RegisterWindowMessageW(EDIT_FOCUS_MESSAGE), 0, 0);
|
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
|
#ifdef _WIN64
|
||||||
wine_access_scintilla_input(focus, (const MSG *)lp);
|
wine_access_scintilla_input(focus, (const MSG *)lp);
|
||||||
#endif
|
#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
|
control. Typing checks include repeated letters, spaces, Enter/Tab, accented
|
||||||
characters, surrogate pairs, selection replacement, documents beyond the full
|
characters, surrogate pairs, selection replacement, documents beyond the full
|
||||||
snapshot limit, and silence for password/background/read-only fields and
|
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
|
input rejected at the text limit. Command-history checks consume queued Up/Down
|
||||||
assertions. Lifecycle variants attach after the control has focus or
|
before dispatch, then replace the text and move the caret as MUSHclient does.
|
||||||
restart the helper without closing it. Every text action must have an established
|
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.
|
focus source, matching the Go reader's filtering contract.
|
||||||
|
|
||||||
The Notepad fixture launches Wine's actual `notepad.exe` and uses `SendInput`
|
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
|
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.
|
audible typing echo, physical keys, or a physical braille display.
|
||||||
|
|
||||||
For live MUSHclient acceptance, install the helper and reopen MUSHclient so the
|
For live MUSHclient acceptance, install the helper, restart Cthulhu and reopen
|
||||||
updated 32-bit DLL can load. In the command field, check Left/Right and
|
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
|
Ctrl+Left/Right feedback without submitting a command. Use Up/Down to recall
|
||||||
the read-only output notepad supplied by the Aardwolf review-buffer plugin;
|
existing command history. Check each recalled command is spoken once,
|
||||||
check Up/Down line reading and Left/Right characters there, then return to
|
including equal-length entries and `blank` when returning to empty input.
|
||||||
the command field. Check plugin menus and dialogs still work, and repeat after
|
Do not submit recalled commands just to test speech.
|
||||||
restarting the reader. These hooks provide caret feedback; independent review
|
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
|
commands use the optional flat-review protocol described above. Native braille
|
||||||
routing into Wine text remains unsupported.
|
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";
|
static const WCHAR *editClass = L"Edit";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static HWND editor, password;
|
static HWND editor, password, history;
|
||||||
static int step;
|
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) {
|
static void key(UINT key, BOOL control, BOOL shift) {
|
||||||
BYTE saved[256], state[256];
|
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);
|
SendMessageW(editor, WM_CHAR, L'%', 1);
|
||||||
if (GetWindowTextLengthW(editor) != 4) ExitProcess(2);
|
if (GetWindowTextLengthW(editor) != 4) ExitProcess(2);
|
||||||
break;
|
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:
|
default:
|
||||||
DestroyWindow(window); break;
|
DestroyWindow(window); break;
|
||||||
}
|
}
|
||||||
@@ -245,6 +298,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show)
|
|||||||
SetTimer(window, 1, 1500, NULL);
|
SetTimer(window, 1, 1500, NULL);
|
||||||
MSG message;
|
MSG message;
|
||||||
while (GetMessageW(&message, NULL, 0, 0) > 0) {
|
while (GetMessageW(&message, NULL, 0, 0) > 0) {
|
||||||
|
if (history_input(&message)) continue;
|
||||||
TranslateMessage(&message); DispatchMessageW(&message);
|
TranslateMessage(&message); DispatchMessageW(&message);
|
||||||
}
|
}
|
||||||
return 0;
|
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]]
|
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 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"
|
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":
|
if args.fixture == "winforms":
|
||||||
assert any(e[2] == "Email" and e[3] == "edit" and e[4] == "alpha beta"
|
assert any(e[2] == "Email" and e[3] == "edit" and e[4] == "alpha beta"
|
||||||
for e in text_events), "managed email label/focus missing"
|
for e in text_events), "managed email label/focus missing"
|
||||||
|
|||||||
Reference in new issue
Block a user