116 lines
4.6 KiB
C
116 lines
4.6 KiB
C
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
#define COBJMACROS
|
|
#include <windows.h>
|
|
#include <oleauto.h>
|
|
#include <uiautomationcore.h>
|
|
#include <uiautomationcoreapi.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#ifdef WINE_ACCESS_PRISM_FIXTURE
|
|
#include <shellapi.h>
|
|
#define PRISM_STATIC
|
|
#include <prism.h>
|
|
#define LOAD(name) static __typeof__(&name) p_##name
|
|
LOAD(prism_config_init); LOAD(prism_init); LOAD(prism_registry_acquire_best);
|
|
LOAD(prism_backend_name); LOAD(prism_backend_speak); LOAD(prism_shutdown);
|
|
LOAD(prism_backend_stop);
|
|
static PrismContext *context;
|
|
static PrismBackend *backend;
|
|
#endif
|
|
|
|
static HWND window;
|
|
static int step = -2;
|
|
|
|
static void notify(const WCHAR *text, enum NotificationProcessing processing) {
|
|
#ifdef WINE_ACCESS_PRISM_FIXTURE
|
|
char utf8[20000];
|
|
WideCharToMultiByte(CP_UTF8, 0, text, -1, utf8, sizeof(utf8), NULL, NULL);
|
|
BOOL interrupt = processing == NotificationProcessing_ImportantMostRecent ||
|
|
processing == NotificationProcessing_ImportantAll;
|
|
// Prism rejects empty speak text; its stop command emits an empty important
|
|
// UIA notification instead.
|
|
PrismError result = utf8[0] ? p_prism_backend_speak(backend, utf8, interrupt) :
|
|
interrupt ? p_prism_backend_stop(backend) : PRISM_OK;
|
|
if (result != PRISM_OK) ExitProcess(4);
|
|
#else
|
|
BSTR displayString = SysAllocString(text);
|
|
BSTR activity = SysAllocString(L"Fixture");
|
|
IRawElementProviderSimple *provider = NULL;
|
|
if (FAILED(UiaHostProviderFromHwnd(window, &provider)) || !provider) ExitProcess(2);
|
|
HRESULT result = UiaRaiseNotificationEvent(provider, NotificationKind_ActionCompleted,
|
|
processing, displayString, activity);
|
|
IRawElementProviderSimple_Release(provider);
|
|
SysFreeString(displayString);
|
|
SysFreeString(activity);
|
|
if (FAILED(result)) ExitProcess(3);
|
|
#endif
|
|
}
|
|
|
|
static LRESULT CALLBACK window_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) {
|
|
if (message == WM_TIMER) {
|
|
// Real UIA calls through normal imports, reached by the global native hook.
|
|
switch (++step) {
|
|
case -1: case 0: break;
|
|
case 1: notify(L"Menu caf\u00e9 \U0001f600", NotificationProcessing_All); break;
|
|
case 2: notify(L"Second item", NotificationProcessing_ImportantMostRecent); break;
|
|
case 3: notify(L"", NotificationProcessing_ImportantMostRecent); break;
|
|
case 4: notify(L"", NotificationProcessing_All); break;
|
|
case 5: {
|
|
WCHAR longText[4100];
|
|
for (UINT i = 0; i < ARRAYSIZE(longText) - 1; ++i) longText[i] = L'x';
|
|
longText[4094] = 0xd83d; longText[4095] = 0xde00;
|
|
longText[ARRAYSIZE(longText) - 1] = 0;
|
|
notify(longText, NotificationProcessing_All);
|
|
break;
|
|
}
|
|
case 6: ShowWindow(hwnd, SW_HIDE); break;
|
|
case 7: notify(L"Background must stay silent", NotificationProcessing_ImportantAll); break;
|
|
default: DestroyWindow(hwnd); break;
|
|
}
|
|
return 0;
|
|
}
|
|
if (message == WM_DESTROY) { PostQuitMessage(0); return 0; }
|
|
return DefWindowProcW(hwnd, message, wp, lp);
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
|
|
(void)previous; (void)args; (void)show;
|
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
WNDCLASSW cls = {0};
|
|
cls.hInstance = instance; cls.lpfnWndProc = window_proc;
|
|
cls.lpszClassName = L"WineAccessNotificationFixture";
|
|
RegisterClassW(&cls);
|
|
window = CreateWindowW(cls.lpszClassName, L"Notification fixture", WS_OVERLAPPEDWINDOW,
|
|
10, 10, 300, 200, NULL, NULL, instance, NULL);
|
|
if (!window) return 1;
|
|
ShowWindow(window, SW_SHOW); SetForegroundWindow(window);
|
|
#ifdef WINE_ACCESS_PRISM_FIXTURE
|
|
int argumentCount = 0;
|
|
WCHAR **arguments = CommandLineToArgvW(GetCommandLineW(), &argumentCount);
|
|
HMODULE module = argumentCount == 2 ? LoadLibraryW(arguments[1]) : NULL;
|
|
LocalFree(arguments);
|
|
if (!module) return 5;
|
|
#undef LOAD
|
|
#define LOAD(name) do { FARPROC address = GetProcAddress(module, #name); \
|
|
memcpy(&p_##name, &address, sizeof(p_##name)); if (!p_##name) return 6; } while (0)
|
|
LOAD(prism_config_init); LOAD(prism_init); LOAD(prism_registry_acquire_best);
|
|
LOAD(prism_backend_name); LOAD(prism_backend_speak); LOAD(prism_shutdown);
|
|
LOAD(prism_backend_stop);
|
|
PrismConfig config = p_prism_config_init();
|
|
context = p_prism_init(&config);
|
|
if (!context) return 7;
|
|
backend = p_prism_registry_acquire_best(context);
|
|
if (!backend || strcmp(p_prism_backend_name(backend), "UIA")) return 8;
|
|
#endif
|
|
SetTimer(window, 1, 500, NULL);
|
|
MSG message;
|
|
while (GetMessageW(&message, NULL, 0, 0) > 0) {
|
|
TranslateMessage(&message); DispatchMessageW(&message);
|
|
}
|
|
#ifdef WINE_ACCESS_PRISM_FIXTURE
|
|
p_prism_shutdown(context);
|
|
#endif
|
|
CoUninitialize();
|
|
return 0;
|
|
}
|