Finish up edit field accessibility.
This commit is contained in:
1 parent
6f6d4040c4
commit
c5657ee5d4
9 files changed
+361
-39
No files matched your search
+31
-19
@@ -5,14 +5,13 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#define EDIT_SUBCLASS_ID 0x57415431
|
||||
#define EDIT_STATE_PROPERTY L"CthulhuWineEditState"
|
||||
// Bound memory and work inside applications. Navigation reuses the snapshot;
|
||||
// only text changes refresh it. Large documents retain line/character reading.
|
||||
// only text changes refresh it. Large documents retain line reading.
|
||||
#define MAX_SNAPSHOT_UNITS (1024 * 1024)
|
||||
|
||||
typedef struct EditState {
|
||||
WCHAR *text;
|
||||
DWORD length, start, end;
|
||||
DWORD length;
|
||||
BOOL busy, known, destroyed;
|
||||
} EditState;
|
||||
|
||||
@@ -45,6 +44,7 @@ static void current_line(HWND window, DWORD caret, WCHAR *output) {
|
||||
output[0] = 1023;
|
||||
LRESULT copied = SendMessageW(window, EM_GETLINE, line, (LPARAM)output);
|
||||
if (copied < 0 || copied > 1023) copied = 0;
|
||||
if (copied && output[copied - 1] >= 0xd800 && output[copied - 1] <= 0xdbff) --copied;
|
||||
output[copied] = 0;
|
||||
}
|
||||
|
||||
@@ -85,8 +85,10 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
wp == VK_HOME || wp == VK_END || wp == VK_PRIOR || wp == VK_NEXT);
|
||||
BOOL changing = message == WM_CHAR || (message == WM_KEYDOWN && wp == VK_DELETE) ||
|
||||
message == WM_PASTE || message == WM_CUT || message == WM_CLEAR ||
|
||||
message == WM_UNDO || message == EM_REPLACESEL || message == WM_SETTEXT;
|
||||
BOOL focus = message == WM_SETFOCUS;
|
||||
message == WM_UNDO || message == EM_UNDO || message == EM_REPLACESEL ||
|
||||
message == WM_SETTEXT || message == EM_SETHANDLE || message == EM_FMTLINES;
|
||||
BOOL requested_focus = message == RegisterWindowMessageW(EDIT_FOCUS_MESSAGE);
|
||||
BOOL focus = message == WM_SETFOCUS || requested_focus;
|
||||
BOOL selected = message == EM_SETSEL;
|
||||
if (!navigation && !changing && !focus && !selected && message != EM_SETPASSWORDCHAR)
|
||||
return DefSubclassProc(window, message, wp, lp);
|
||||
@@ -97,20 +99,22 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
BOOL password = (GetWindowLongPtrW(window, GWL_STYLE) & ES_PASSWORD) != 0;
|
||||
if (!password && (!state->known || focus)) snapshot(window, state);
|
||||
WCHAR removed[1024] = {0};
|
||||
if (!password && state->text && changing) {
|
||||
if (!password && state->text && changing && old_start <= state->length && old_end <= state->length) {
|
||||
DWORD first = old_start, last = old_end;
|
||||
if (first == last) {
|
||||
if (message == WM_CHAR && wp == VK_BACK && first) {
|
||||
--first;
|
||||
if (first && state->text[first] >= 0xdc00 && state->text[first] <= 0xdfff) --first;
|
||||
else if (first && state->text[first] == '\n' && state->text[first - 1] == '\r') --first;
|
||||
} else if (message == WM_KEYDOWN && wp == VK_DELETE && last < state->length) {
|
||||
++last;
|
||||
if (last < state->length && state->text[last - 1] >= 0xd800 && state->text[last - 1] <= 0xdbff) ++last;
|
||||
else if (last < state->length && state->text[last - 1] == '\r' && state->text[last] == '\n') ++last;
|
||||
}
|
||||
}
|
||||
if (last <= state->length) copy_range(removed, state->text, first, last);
|
||||
}
|
||||
LRESULT result = DefSubclassProc(window, message, wp, lp);
|
||||
LRESULT result = requested_focus ? 0 : DefSubclassProc(window, message, wp, lp);
|
||||
if (state->destroyed) {
|
||||
free(state->text); free(state);
|
||||
return result;
|
||||
@@ -138,11 +142,11 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
// Speak the changed portion when extending or shrinking a selection.
|
||||
DWORD first = start, last = end;
|
||||
kind = WINE_ACCESS_TEXT_SELECTED;
|
||||
if (old_start != old_end && start == old_start) {
|
||||
if (old_start != old_end && old_end <= state->length && start == old_start) {
|
||||
first = end < old_end ? end : old_end;
|
||||
last = end > old_end ? end : old_end;
|
||||
if (end < old_end) kind = WINE_ACCESS_TEXT_UNSELECTED;
|
||||
} else if (old_start != old_end && end == old_end) {
|
||||
} else if (old_start != old_end && old_end <= state->length && end == old_end) {
|
||||
first = start < old_start ? start : old_start;
|
||||
last = start > old_start ? start : old_start;
|
||||
if (start > old_start) kind = WINE_ACCESS_TEXT_UNSELECTED;
|
||||
@@ -151,7 +155,8 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
} else if (old_start != old_end && state->text && old_end <= state->length) {
|
||||
copy_range(text, state->text, old_start, old_end);
|
||||
kind = WINE_ACCESS_TEXT_UNSELECTED;
|
||||
} else if (navigation && (wp == VK_UP || wp == VK_DOWN || wp == VK_PRIOR || wp == VK_NEXT)) {
|
||||
} else if (navigation && (wp == VK_UP || wp == VK_DOWN || wp == VK_PRIOR || wp == VK_NEXT ||
|
||||
((wp == VK_HOME || wp == VK_END) && (GetKeyState(VK_CONTROL) & 0x8000)))) {
|
||||
current_line(window, start, text);
|
||||
} else if (state->text && start < state->length) {
|
||||
DWORD last = start + 1;
|
||||
@@ -165,39 +170,46 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
current_line(window, start, text);
|
||||
}
|
||||
report = TRUE;
|
||||
} else if (changing && removed[0] && old_length > state->length) {
|
||||
} else if (changing && removed[0] && old_length > state->length &&
|
||||
(message == WM_CLEAR || message == WM_CUT ||
|
||||
(message == WM_CHAR && wp == VK_BACK) ||
|
||||
(message == WM_KEYDOWN && wp == VK_DELETE))) {
|
||||
lstrcpynW(text, removed, 1024);
|
||||
kind = WINE_ACCESS_TEXT_DELETED; report = TRUE;
|
||||
}
|
||||
if (report) notify_text(window, kind, text, start, end);
|
||||
state->start = start; state->end = end;
|
||||
state->busy = FALSE;
|
||||
return result;
|
||||
}
|
||||
|
||||
void wine_access_watch_edit(HWND window) {
|
||||
BOOL wine_access_watch_edit(HWND window) {
|
||||
WCHAR class_name[64];
|
||||
if (!window || !GetClassNameW(window, class_name, 64) || lstrcmpW(class_name, L"Edit")) return;
|
||||
if (!window || !GetClassNameW(window, class_name, 64) || lstrcmpW(class_name, L"Edit")) return FALSE;
|
||||
// Wine's comctl32 v5 exports GetWindowSubclass only by ordinal. Our own
|
||||
// property also keeps attachment independent of the application's manifest.
|
||||
if (GetPropW(window, EDIT_STATE_PROPERTY)) return;
|
||||
if (GetPropW(window, EDIT_STATE_PROPERTY)) return FALSE;
|
||||
EditState *state = calloc(1, sizeof(*state));
|
||||
if (!state) return;
|
||||
if (!state) return FALSE;
|
||||
if (!SetWindowSubclass(window, edit_proc, EDIT_SUBCLASS_ID, (DWORD_PTR)state)) {
|
||||
free(state); return;
|
||||
free(state); return FALSE;
|
||||
}
|
||||
if (!SetPropW(window, EDIT_STATE_PROPERTY, state)) {
|
||||
RemoveWindowSubclass(window, edit_proc, EDIT_SUBCLASS_ID);
|
||||
free(state); return;
|
||||
free(state); return FALSE;
|
||||
}
|
||||
// Subclass callbacks can outlive the reader. Keep their module mapped until
|
||||
// the application exits; each control frees its state on WM_NCDESTROY.
|
||||
HMODULE module;
|
||||
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
|
||||
(LPCWSTR)(UINT_PTR)edit_proc, &module);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
__declspec(dllexport) LRESULT CALLBACK WineAccessInputHook(int code, WPARAM wp, LPARAM lp) {
|
||||
if (code >= 0 && wp == PM_REMOVE) wine_access_watch_edit(GetFocus());
|
||||
if (code >= 0 && wp == PM_REMOVE) {
|
||||
HWND focus = GetFocus();
|
||||
if (wine_access_watch_edit(focus))
|
||||
SendMessageW(focus, RegisterWindowMessageW(EDIT_FOCUS_MESSAGE), 0, 0);
|
||||
}
|
||||
return CallNextHookEx(NULL, code, wp, lp);
|
||||
}
|
||||
Reference in new issue
Block a user