mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
win32: NVDA spoken to over RPC, nvdaControllerClient64.dll gone
frontend/drivers/nvda_controller_win32.c talks to NVDA's controller interface over local RPC itself, as NVDA's controller client does: bound to ncalrpc:[NvdaCtlr.<session>.<desktop>], its four procedures called through NdrClientCall2 with MIDL's format strings - the x64 ones NVDA's client carries, byte for byte, and the same procedures for the x86 stack - so 32-bit builds reach NVDA too. rpcrt4 and the session and desktop calls are resolved at run time; where any is missing, PowerShell speaks as it does without NVDA. samples/frontend/win32_nvda runs it, x64 and x86, against a stand-in NVDA in the test: an RPC server for the interface as widl compiles it. Every procedure arrives in order with its string as sent, non-ASCII, empty and long ones among them; with no NVDA listening or after deinit nothing arrives and the calls fail.
This commit is contained in:
1 parent
e9a553a660
commit
9f366c2e01
9 files changed
+815
-37
No files matched your search
@@ -50,7 +50,6 @@ build-retroarch-windows-x64:
|
||||
--enforcedir /usr/lib/mxe/usr/x86_64-w64-mingw32.shared/qt5/plugins/styles/
|
||||
--copy
|
||||
--objdump x86_64-w64-mingw32.shared-objdump"
|
||||
- "cp -f nvdaControllerClient64.dll ${MEDIA_PATH}/${CI_PROJECT_NAME}/redist/nvdaControllerClient64.dll"
|
||||
- "rm -f ${MEDIA_PATH}/${CI_PROJECT_NAME}/redist/opengl32.dll"
|
||||
|
||||
build-retroarch-windows-i686:
|
||||
|
||||
@@ -2939,6 +2939,7 @@ ifneq ($(findstring Win32,$(OS)),)
|
||||
OBJ += media/rarch.o \
|
||||
gfx/common/win32_common.o \
|
||||
frontend/drivers/platform_win32.o \
|
||||
frontend/drivers/nvda_controller_win32.o \
|
||||
gfx/display_servers/dispserv_win32.o \
|
||||
gfx/common/dx_guids.o
|
||||
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* NVDA's controller interface over local RPC, as NVDA's controller
|
||||
* client DLL speaks it.
|
||||
*
|
||||
* NVDA listens on ncalrpc:[NvdaCtlr.<session id>.<desktop name>] for
|
||||
* interface DFF50B99-F7FD-4CA7-A82C-DAEB3E025295 v1.0, whose four
|
||||
* procedures - testIfRunning, speakText, cancelSpeech, brailleMessage -
|
||||
* NVDA publishes in nvdaHelper/interfaces/nvdaController.idl. The
|
||||
* calls go through NdrClientCall2 with the format strings MIDL makes
|
||||
* for that interface: the x64 ones as NVDA's own client carries them,
|
||||
* the x86 ones the same procedures for the x86 stack. Every procedure
|
||||
* has comm and fault status, so a failed call - NVDA not running -
|
||||
* comes back as its return value, never as an exception.
|
||||
*
|
||||
* rpcrt4 and the session and desktop calls are resolved at run time:
|
||||
* where any is missing, as before Windows 2000, there is no NVDA to
|
||||
* reach and nvda_controller_init() says so. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../../config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NVDA
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <rpc.h>
|
||||
#include <rpcndr.h>
|
||||
|
||||
#include "nvda_controller_win32.h"
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#define NVDA_CONTROLLER_X64
|
||||
#elif defined(_M_IX86) || defined(__i386__)
|
||||
#define NVDA_CONTROLLER_X86
|
||||
#endif
|
||||
|
||||
#if defined(NVDA_CONTROLLER_X64) || defined(NVDA_CONTROLLER_X86)
|
||||
|
||||
typedef CLIENT_CALL_RETURN (RPC_VAR_ENTRY *nvda_ndr_client_call2_t)(
|
||||
PMIDL_STUB_DESC, PFORMAT_STRING, ...);
|
||||
typedef RPC_STATUS (RPC_ENTRY *nvda_rpc_binding_from_string_t)(
|
||||
unsigned short *, RPC_BINDING_HANDLE *);
|
||||
typedef RPC_STATUS (RPC_ENTRY *nvda_rpc_binding_free_t)(RPC_BINDING_HANDLE *);
|
||||
typedef BOOL (WINAPI *nvda_process_id_to_session_id_t)(DWORD, DWORD *);
|
||||
typedef HDESK (WINAPI *nvda_get_thread_desktop_t)(DWORD);
|
||||
typedef BOOL (WINAPI *nvda_get_user_object_information_w_t)(HANDLE, int,
|
||||
PVOID, DWORD, LPDWORD);
|
||||
|
||||
/* Procedure format strings, one procedure each: the procedure header,
|
||||
* then its [in, string] wchar_t * where it has one, then its
|
||||
* error_status_t return. Indexed by NVDA_PROC_*. */
|
||||
#define NVDA_PROC_TEST_IF_RUNNING 0
|
||||
#define NVDA_PROC_SPEAK_TEXT 1
|
||||
#define NVDA_PROC_CANCEL_SPEECH 2
|
||||
#define NVDA_PROC_BRAILLE_MESSAGE 3
|
||||
|
||||
#ifdef NVDA_CONTROLLER_X64
|
||||
static const unsigned char nvda_proc_format[] = {
|
||||
/* 0: testIfRunning - implicit handle, comm/fault status, method 0,
|
||||
* stack 8, no arguments; the extension's 10 bytes */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x0a, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
/* 32: speakText - method 1, stack 16, the string at 0 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x0a, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x08, 0x00, 0x10, 0x00,
|
||||
/* 70: cancelSpeech - method 2 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x0a, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
/* 102: brailleMessage - method 3 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x0a, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x08, 0x00, 0x10, 0x00,
|
||||
0x00
|
||||
};
|
||||
static const unsigned short nvda_proc_offset[4] = { 0, 32, 70, 102 };
|
||||
#else
|
||||
static const unsigned char nvda_proc_format[] = {
|
||||
/* 0: testIfRunning - method 0, stack 4; the extension's 8 bytes */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x08, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
/* 30: speakText - method 1, stack 8, the string at 0 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x08, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
/* 66: cancelSpeech - method 2 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x08, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
/* 96: brailleMessage - method 3 */
|
||||
0x32, 0x68, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x08, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x00
|
||||
};
|
||||
static const unsigned short nvda_proc_offset[4] = { 0, 30, 66, 96 };
|
||||
#endif
|
||||
|
||||
/* The one type: at 2 a reference pointer to, at 4, a conformant
|
||||
* wide string */
|
||||
static const unsigned char nvda_type_format[] = {
|
||||
0x00, 0x00, 0x11, 0x08, 0x25, 0x5c, 0x00
|
||||
};
|
||||
|
||||
/* Comm and fault status of each procedure in its return value */
|
||||
static const COMM_FAULT_OFFSETS nvda_comm_fault_offsets[4] = {
|
||||
{ -1, -1 }, { -1, -1 }, { -1, -1 }, { -1, -1 }
|
||||
};
|
||||
|
||||
static const RPC_CLIENT_INTERFACE nvda_client_interface = {
|
||||
sizeof(RPC_CLIENT_INTERFACE),
|
||||
/* DFF50B99-F7FD-4CA7-A82C-DAEB3E025295 v1.0 */
|
||||
{ { 0xdff50b99, 0xf7fd, 0x4ca7,
|
||||
{ 0xa8, 0x2c, 0xda, 0xeb, 0x3e, 0x02, 0x52, 0x95 } }, { 1, 0 } },
|
||||
/* NDR 2.0 */
|
||||
{ { 0x8a885d04, 0x1ceb, 0x11c9,
|
||||
{ 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60 } }, { 2, 0 } },
|
||||
0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static handle_t nvda_binding;
|
||||
|
||||
static void * __RPC_USER nvda_rpc_alloc(size_t len)
|
||||
{
|
||||
return malloc(len);
|
||||
}
|
||||
|
||||
static void __RPC_USER nvda_rpc_free(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
static MIDL_STUB_DESC nvda_stub_desc;
|
||||
|
||||
static HMODULE nvda_rpcrt4;
|
||||
static nvda_ndr_client_call2_t nvda_ndr_client_call2;
|
||||
static nvda_rpc_binding_free_t nvda_rpc_binding_free;
|
||||
|
||||
static unsigned long nvda_call_void(unsigned proc)
|
||||
{
|
||||
CLIENT_CALL_RETURN ret;
|
||||
if (!nvda_binding)
|
||||
return RPC_S_INVALID_BINDING;
|
||||
ret = nvda_ndr_client_call2(&nvda_stub_desc,
|
||||
(PFORMAT_STRING)&nvda_proc_format[nvda_proc_offset[proc]]);
|
||||
return (unsigned long)ret.Simple;
|
||||
}
|
||||
|
||||
static unsigned long nvda_call_string(unsigned proc, const wchar_t *text)
|
||||
{
|
||||
CLIENT_CALL_RETURN ret;
|
||||
if (!nvda_binding)
|
||||
return RPC_S_INVALID_BINDING;
|
||||
if (!text)
|
||||
return RPC_X_NULL_REF_POINTER;
|
||||
#ifdef NVDA_CONTROLLER_X64
|
||||
ret = nvda_ndr_client_call2(&nvda_stub_desc,
|
||||
(PFORMAT_STRING)&nvda_proc_format[nvda_proc_offset[proc]], text);
|
||||
#else
|
||||
{
|
||||
/* x86: the interpreter copies the procedure's whole stack - the
|
||||
* string, then the return value's slot - from where it is told
|
||||
* the arguments start */
|
||||
const wchar_t *args[2];
|
||||
args[0] = text;
|
||||
args[1] = NULL;
|
||||
ret = nvda_ndr_client_call2(&nvda_stub_desc,
|
||||
(PFORMAT_STRING)&nvda_proc_format[nvda_proc_offset[proc]],
|
||||
args);
|
||||
}
|
||||
#endif
|
||||
return (unsigned long)ret.Simple;
|
||||
}
|
||||
|
||||
/* @out of @len wide characters: ncalrpc:[NvdaCtlr.<session>.<desktop>] */
|
||||
static bool nvda_binding_string(wchar_t *out, size_t len)
|
||||
{
|
||||
static const wchar_t prefix[] = L"ncalrpc:[NvdaCtlr.";
|
||||
wchar_t desktop[64];
|
||||
wchar_t digits[16];
|
||||
DWORD session = 0;
|
||||
size_t n, i, d = 0;
|
||||
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
|
||||
/* Loaded already where there is a window, but not everywhere */
|
||||
HMODULE user32 = LoadLibraryA("user32.dll");
|
||||
nvda_process_id_to_session_id_t session_of;
|
||||
nvda_get_thread_desktop_t thread_desktop;
|
||||
nvda_get_user_object_information_w_t object_info;
|
||||
HDESK desk;
|
||||
|
||||
if (!kernel32 || !user32)
|
||||
return false;
|
||||
session_of = (nvda_process_id_to_session_id_t)(void (*)(void))
|
||||
GetProcAddress(kernel32, "ProcessIdToSessionId");
|
||||
thread_desktop = (nvda_get_thread_desktop_t)(void (*)(void))
|
||||
GetProcAddress(user32, "GetThreadDesktop");
|
||||
object_info = (nvda_get_user_object_information_w_t)(void (*)(void))
|
||||
GetProcAddress(user32, "GetUserObjectInformationW");
|
||||
if (!session_of || !thread_desktop || !object_info)
|
||||
return false;
|
||||
if (!session_of(GetCurrentProcessId(), &session))
|
||||
return false;
|
||||
if (!(desk = thread_desktop(GetCurrentThreadId())))
|
||||
return false;
|
||||
memset(desktop, 0, sizeof(desktop));
|
||||
/* UOI_NAME */
|
||||
if (!object_info(desk, 2, desktop, sizeof(desktop) - sizeof(wchar_t),
|
||||
NULL))
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
digits[d++] = (wchar_t)(L'0' + session % 10);
|
||||
session /= 10;
|
||||
} while (session);
|
||||
|
||||
n = 0;
|
||||
for (i = 0; prefix[i]; i++)
|
||||
if (n < len)
|
||||
out[n++] = prefix[i];
|
||||
while (d)
|
||||
if (n < len)
|
||||
out[n++] = digits[--d];
|
||||
else
|
||||
d--;
|
||||
if (n < len)
|
||||
out[n++] = L'.';
|
||||
for (i = 0; desktop[i]; i++)
|
||||
if (n < len)
|
||||
out[n++] = desktop[i];
|
||||
if (n + 2 > len)
|
||||
return false;
|
||||
out[n++] = L']';
|
||||
out[n] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nvda_controller_init(void)
|
||||
{
|
||||
wchar_t binding[128];
|
||||
nvda_rpc_binding_from_string_t from_string;
|
||||
|
||||
if (nvda_binding)
|
||||
return true;
|
||||
if (!nvda_binding_string(binding, sizeof(binding) / sizeof(binding[0])))
|
||||
return false;
|
||||
if (!nvda_rpcrt4 && !(nvda_rpcrt4 = LoadLibraryA("rpcrt4.dll")))
|
||||
return false;
|
||||
nvda_ndr_client_call2 = (nvda_ndr_client_call2_t)(void (*)(void))
|
||||
GetProcAddress(nvda_rpcrt4, "NdrClientCall2");
|
||||
nvda_rpc_binding_free = (nvda_rpc_binding_free_t)(void (*)(void))
|
||||
GetProcAddress(nvda_rpcrt4, "RpcBindingFree");
|
||||
from_string = (nvda_rpc_binding_from_string_t)(void (*)(void))
|
||||
GetProcAddress(nvda_rpcrt4, "RpcBindingFromStringBindingW");
|
||||
if ( !nvda_ndr_client_call2
|
||||
|| !nvda_rpc_binding_free
|
||||
|| !from_string)
|
||||
return false;
|
||||
|
||||
nvda_stub_desc.RpcInterfaceInformation = (void*)&nvda_client_interface;
|
||||
nvda_stub_desc.pfnAllocate = nvda_rpc_alloc;
|
||||
nvda_stub_desc.pfnFree = nvda_rpc_free;
|
||||
nvda_stub_desc.IMPLICIT_HANDLE_INFO.pPrimitiveHandle = &nvda_binding;
|
||||
nvda_stub_desc.pFormatTypes = nvda_type_format;
|
||||
nvda_stub_desc.fCheckBounds = 1;
|
||||
nvda_stub_desc.Version = 0x50002;
|
||||
nvda_stub_desc.MIDLVersion = 0x700022b;
|
||||
nvda_stub_desc.CommFaultOffsets = nvda_comm_fault_offsets;
|
||||
|
||||
if (from_string((unsigned short*)binding, &nvda_binding) != RPC_S_OK)
|
||||
{
|
||||
nvda_binding = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void nvda_controller_deinit(void)
|
||||
{
|
||||
if (nvda_binding)
|
||||
nvda_rpc_binding_free(&nvda_binding);
|
||||
nvda_binding = NULL;
|
||||
}
|
||||
|
||||
unsigned long nvda_controller_test_if_running(void)
|
||||
{
|
||||
return nvda_call_void(NVDA_PROC_TEST_IF_RUNNING);
|
||||
}
|
||||
|
||||
unsigned long nvda_controller_cancel_speech(void)
|
||||
{
|
||||
return nvda_call_void(NVDA_PROC_CANCEL_SPEECH);
|
||||
}
|
||||
|
||||
unsigned long nvda_controller_speak_text(const wchar_t *text)
|
||||
{
|
||||
return nvda_call_string(NVDA_PROC_SPEAK_TEXT, text);
|
||||
}
|
||||
|
||||
unsigned long nvda_controller_braille_message(const wchar_t *message)
|
||||
{
|
||||
return nvda_call_string(NVDA_PROC_BRAILLE_MESSAGE, message);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* No format strings for this architecture: no NVDA */
|
||||
bool nvda_controller_init(void) { return false; }
|
||||
void nvda_controller_deinit(void) { }
|
||||
unsigned long nvda_controller_test_if_running(void) { return 1; }
|
||||
unsigned long nvda_controller_cancel_speech(void) { return 1; }
|
||||
unsigned long nvda_controller_speak_text(const wchar_t *text)
|
||||
{ (void)text; return 1; }
|
||||
unsigned long nvda_controller_braille_message(const wchar_t *message)
|
||||
{ (void)message; return 1; }
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* HAVE_NVDA */
|
||||
@@ -0,0 +1,44 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __RARCH_NVDA_CONTROLLER_WIN32_H
|
||||
#define __RARCH_NVDA_CONTROLLER_WIN32_H
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/* The NVDA screen reader's controller interface, spoken to over local
|
||||
* RPC as NVDA's own controller client does: no DLL to ship or load.
|
||||
*
|
||||
* nvda_controller_init() binds to the NVDA instance on this session
|
||||
* and desktop; false when this Windows or this build has no way to
|
||||
* reach one (the calls below then return nonzero). It binds once and
|
||||
* holds the binding until nvda_controller_deinit(). Each call returns
|
||||
* 0 on success and the RPC status otherwise - nonzero, notably, when
|
||||
* NVDA is not running. */
|
||||
bool nvda_controller_init(void);
|
||||
void nvda_controller_deinit(void);
|
||||
|
||||
unsigned long nvda_controller_test_if_running(void);
|
||||
unsigned long nvda_controller_cancel_speech(void);
|
||||
unsigned long nvda_controller_speak_text(const wchar_t *text);
|
||||
unsigned long nvda_controller_braille_message(const wchar_t *message);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -58,6 +58,9 @@
|
||||
#include "../../gfx/common/win32_common.h"
|
||||
|
||||
#include "platform_win32.h"
|
||||
#ifdef HAVE_NVDA
|
||||
#include "nvda_controller_win32.h"
|
||||
#endif
|
||||
#ifdef HAVE_THREADS
|
||||
#include "../thread_elevation.h"
|
||||
#endif
|
||||
@@ -110,14 +113,12 @@ static char win32_cpu_model_name[64] = {0};
|
||||
* it early seems to cause issues on some systems.
|
||||
*/
|
||||
static dylib_t dwm_lib;
|
||||
static dylib_t nvda_lib;
|
||||
#endif
|
||||
|
||||
/* Dynamic loading for Non-Visual Desktop Access support */
|
||||
unsigned long (__stdcall *nvdaController_testIfRunning_func)(void);
|
||||
unsigned long (__stdcall *nvdaController_cancelSpeech_func)(void);
|
||||
unsigned long (__stdcall *nvdaController_brailleMessage_func)(wchar_t*);
|
||||
unsigned long (__stdcall *nvdaController_speakText_func)(wchar_t*);
|
||||
#ifdef HAVE_NVDA
|
||||
/* Bound to NVDA's controller endpoint */
|
||||
static bool nvda_bound;
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_LANGEXTRA) && !defined(_XBOX)
|
||||
#if (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) || !defined(_MSC_VER)
|
||||
@@ -1141,30 +1142,16 @@ static void frontend_win32_init(void *data)
|
||||
|
||||
|
||||
#ifdef HAVE_NVDA
|
||||
/* Runs on each accessibility speak event: binds once, and where there
|
||||
* is no NVDA to bind to, PowerShell speaks instead */
|
||||
static void init_nvda(void)
|
||||
{
|
||||
#ifdef HAVE_DYLIB
|
||||
if ( (g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA)
|
||||
&& !nvda_lib)
|
||||
{
|
||||
if ((nvda_lib = dylib_load("nvdaControllerClient64.dll")))
|
||||
{
|
||||
nvdaController_testIfRunning_func = (unsigned long (__stdcall*)(void))dylib_proc(nvda_lib, "nvdaController_testIfRunning");
|
||||
nvdaController_cancelSpeech_func = (unsigned long(__stdcall *)(void))dylib_proc(nvda_lib, "nvdaController_cancelSpeech");
|
||||
nvdaController_brailleMessage_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvda_lib, "nvdaController_brailleMessage");
|
||||
nvdaController_speakText_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvda_lib, "nvdaController_speakText");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* The above code is executed on each accessibility speak event, so
|
||||
* we should only revert to powershell if nvda_lib wasn't loaded previously,
|
||||
* and we weren't able to load it on this call, or we don't HAVE_DYLIB */
|
||||
if ((g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA) && !nvda_lib)
|
||||
{
|
||||
g_plat_win32_flags &= ~PLAT_WIN32_FLAG_USE_NVDA;
|
||||
g_plat_win32_flags |= PLAT_WIN32_FLAG_USE_POWERSHELL;
|
||||
}
|
||||
if (!(g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA) || nvda_bound)
|
||||
return;
|
||||
if ((nvda_bound = nvda_controller_init()))
|
||||
return;
|
||||
g_plat_win32_flags &= ~PLAT_WIN32_FLAG_USE_NVDA;
|
||||
g_plat_win32_flags |= PLAT_WIN32_FLAG_USE_POWERSHELL;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1727,9 +1714,7 @@ static bool is_narrator_running_windows(void)
|
||||
#ifdef HAVE_NVDA
|
||||
else if (g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA)
|
||||
{
|
||||
long res = nvdaController_testIfRunning_func();
|
||||
|
||||
if (res != 0)
|
||||
if (nvda_controller_test_if_running() != 0)
|
||||
{
|
||||
/* The running nvda service wasn't found, so revert
|
||||
back to the powershell method
|
||||
@@ -1797,9 +1782,8 @@ static bool accessibility_speak_windows(int speed,
|
||||
else if (g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA)
|
||||
{
|
||||
wchar_t *wc = utf8_to_utf16_string_alloc(speak_text);
|
||||
long res = nvdaController_testIfRunning_func();
|
||||
|
||||
if (!wc || res != 0)
|
||||
if (!wc || nvda_controller_test_if_running() != 0)
|
||||
{
|
||||
RARCH_ERR("Error communicating with NVDA.\n");
|
||||
/* Fallback on powershell immediately and retry */
|
||||
@@ -1810,12 +1794,12 @@ static bool accessibility_speak_windows(int speed,
|
||||
return accessibility_speak_windows(speed, speak_text, priority);
|
||||
}
|
||||
|
||||
nvdaController_cancelSpeech_func();
|
||||
nvda_controller_cancel_speech();
|
||||
|
||||
if (g_plat_win32_flags & PLAT_WIN32_FLAG_USE_NVDA_BRAILLE)
|
||||
nvdaController_brailleMessage_func(wc);
|
||||
nvda_controller_braille_message(wc);
|
||||
else
|
||||
nvdaController_speakText_func(wc);
|
||||
nvda_controller_speak_text(wc);
|
||||
free(wc);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1561,6 +1561,9 @@ FRONTEND
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
#include "../frontend/drivers/platform_win32.c"
|
||||
#ifdef HAVE_NVDA
|
||||
#include "../frontend/drivers/nvda_controller_win32.c"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
# frontend/drivers/nvda_controller_win32.c against a stand-in NVDA, an
|
||||
# RPC server in the test, built and run for x64 and x86 both.
|
||||
#
|
||||
# make check
|
||||
# with the mingw-w64 cross compilers, run under wine (WINE64 and
|
||||
# WINE32 name a wine for each, with its WINEPREFIX); skipped
|
||||
# where those are missing
|
||||
# make check CC=gcc
|
||||
# from an MSYS2 MINGW64 or MINGW32 shell: that one architecture
|
||||
#
|
||||
# make check NVDA_DLL=nvdaControllerClient64.dll
|
||||
# the same calls first through NVDA's own controller client,
|
||||
# for the x64 test, to hold the stand-in to it
|
||||
TARGET64 := win32_nvda_test_x64.exe
|
||||
TARGET32 := win32_nvda_test_x86.exe
|
||||
|
||||
RARCH_DIR := ../../..
|
||||
SRC := win32_nvda_test.c
|
||||
DEPS := $(SRC) $(RARCH_DIR)/frontend/drivers/nvda_controller_win32.c \
|
||||
$(RARCH_DIR)/frontend/drivers/nvda_controller_win32.h
|
||||
|
||||
CFLAGS += -std=gnu89 -Wall -Wextra -Wno-unused-parameter \
|
||||
-Wdeclaration-after-statement -g1 -O1 -DHAVE_NVDA \
|
||||
-I$(RARCH_DIR)/libretro-common/include
|
||||
LDLIBS += -lrpcrt4
|
||||
|
||||
CC64 ?= x86_64-w64-mingw32-gcc
|
||||
CC32 ?= i686-w64-mingw32-gcc
|
||||
WINE64 ?= wine
|
||||
WINE32 ?= wine
|
||||
|
||||
NATIVE := $(findstring mingw,$(shell $(CC) -dumpmachine 2>/dev/null))
|
||||
|
||||
ifneq ($(NATIVE),)
|
||||
all: win32_nvda_test.exe
|
||||
win32_nvda_test.exe: $(DEPS)
|
||||
$(CC) $(CFLAGS) -o $@ $(SRC) $(LDLIBS)
|
||||
check: all
|
||||
./win32_nvda_test.exe $(NVDA_DLL)
|
||||
else
|
||||
HAVE64 := $(shell command -v $(CC64) >/dev/null 2>&1 && command -v $(firstword $(WINE64)) >/dev/null 2>&1 && echo yes)
|
||||
HAVE32 := $(shell command -v $(CC32) >/dev/null 2>&1 && command -v $(firstword $(WINE32)) >/dev/null 2>&1 && echo yes)
|
||||
|
||||
all: $(if $(HAVE64),$(TARGET64)) $(if $(HAVE32),$(TARGET32))
|
||||
|
||||
$(TARGET64): $(DEPS)
|
||||
$(CC64) $(CFLAGS) -o $@ $(SRC) $(LDLIBS)
|
||||
$(TARGET32): $(DEPS)
|
||||
$(CC32) $(CFLAGS) -o $@ $(SRC) $(LDLIBS)
|
||||
|
||||
check: all
|
||||
ifeq ($(HAVE64),yes)
|
||||
WINEDEBUG=-all $(WINE64) ./$(TARGET64) $(NVDA_DLL)
|
||||
else
|
||||
@echo "win32_nvda: skipped x64 - no $(CC64) or $(WINE64)"
|
||||
endif
|
||||
ifeq ($(HAVE32),yes)
|
||||
WINEDEBUG=-all $(WINE32) ./$(TARGET32)
|
||||
else
|
||||
@echo "win32_nvda: skipped x86 - no $(CC32) or $(WINE32)"
|
||||
endif
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET64) $(TARGET32) win32_nvda_test.exe
|
||||
|
||||
.PHONY: all check clean
|
||||
@@ -0,0 +1,330 @@
|
||||
/* frontend/drivers/nvda_controller_win32.c against a stand-in NVDA:
|
||||
* an RPC server in this process, on the endpoint NVDA listens on, for
|
||||
* the controller interface as widl compiles nvdaController.idl - an
|
||||
* NDR implementation of the interface other than the client's, whose
|
||||
* format strings are MIDL's. Held to, for x86 and x64 alike:
|
||||
*
|
||||
* - with no NVDA listening, each call returns an RPC status, nonzero,
|
||||
* and the process goes on;
|
||||
* - with it listening, each of the four procedures reaches it, in the
|
||||
* order made, the strings as sent - non-ASCII, empty and long ones
|
||||
* among them - and each returns 0;
|
||||
* - a NULL string is refused without a call; after
|
||||
* nvda_controller_deinit() nothing is sent.
|
||||
*
|
||||
* Given a path, the same calls are first made through that controller
|
||||
* client DLL - NVDA's own - so the stand-in is shown to be the server
|
||||
* that client talks to. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "../../../frontend/drivers/nvda_controller_win32.c"
|
||||
|
||||
static unsigned failures;
|
||||
|
||||
#define CHECK(c, what) do { if (!(c)) { \
|
||||
printf("FAIL: %s (%s:%d)\n", what, __FILE__, __LINE__); \
|
||||
failures++; } else printf(" ok %s\n", what); } while (0)
|
||||
|
||||
/* ---- the stand-in NVDA ---- */
|
||||
|
||||
static char calls[64];
|
||||
static unsigned ncalls;
|
||||
static wchar_t *last_text;
|
||||
|
||||
static void got(char c, const wchar_t *text)
|
||||
{
|
||||
if (ncalls < sizeof(calls) - 1)
|
||||
calls[ncalls++] = c;
|
||||
calls[ncalls] = 0;
|
||||
free(last_text);
|
||||
last_text = NULL;
|
||||
if (text)
|
||||
{
|
||||
size_t n = wcslen(text) + 1;
|
||||
last_text = (wchar_t*)malloc(n * sizeof(wchar_t));
|
||||
memcpy(last_text, text, n * sizeof(wchar_t));
|
||||
}
|
||||
}
|
||||
|
||||
static error_status_t __stdcall s_test_if_running(void)
|
||||
{ got('T', NULL); return 0; }
|
||||
static error_status_t __stdcall s_speak_text(const wchar_t *text)
|
||||
{ got('S', text); return 0; }
|
||||
static error_status_t __stdcall s_cancel_speech(void)
|
||||
{ got('C', NULL); return 0; }
|
||||
static error_status_t __stdcall s_braille_message(const wchar_t *message)
|
||||
{ got('B', message); return 0; }
|
||||
|
||||
/* The procedures as widl generates them for nvdaController.idl */
|
||||
#ifdef NVDA_CONTROLLER_X64
|
||||
static const unsigned char s_proc_format[] = {
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x08, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x0a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x08, 0x00,
|
||||
0x70, 0x00, 0x08, 0x00, 0x10, 0x00,
|
||||
0x00
|
||||
};
|
||||
static const unsigned short s_proc_offset[4] = { 0, 32, 70, 102 };
|
||||
#else
|
||||
static const unsigned char s_proc_format[] = {
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x04, 0x00,
|
||||
0x70, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x44, 0x01, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x10, 0x00,
|
||||
0x32, 0x48, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x46, 0x02, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x01, 0x00, 0x00, 0x08, 0x00,
|
||||
0x70, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x00
|
||||
};
|
||||
static const unsigned short s_proc_offset[4] = { 0, 30, 66, 96 };
|
||||
#endif
|
||||
|
||||
static const unsigned char s_type_format[] = {
|
||||
0x00, 0x00,
|
||||
0x11, 0x08, 0x25, 0x5c,
|
||||
0x11, 0x08, 0x25, 0x5c,
|
||||
0x00
|
||||
};
|
||||
|
||||
static RPC_DISPATCH_FUNCTION s_dispatch[] = {
|
||||
NdrServerCall2, NdrServerCall2, NdrServerCall2, NdrServerCall2, 0
|
||||
};
|
||||
static RPC_DISPATCH_TABLE s_dispatch_table = { 4, s_dispatch, 0 };
|
||||
|
||||
static const SERVER_ROUTINE s_routines[] = {
|
||||
(SERVER_ROUTINE)s_test_if_running,
|
||||
(SERVER_ROUTINE)s_speak_text,
|
||||
(SERVER_ROUTINE)s_cancel_speech,
|
||||
(SERVER_ROUTINE)s_braille_message
|
||||
};
|
||||
|
||||
static const MIDL_SERVER_INFO s_server_info;
|
||||
|
||||
static const RPC_SERVER_INTERFACE s_interface = {
|
||||
sizeof(RPC_SERVER_INTERFACE),
|
||||
{ { 0xdff50b99, 0xf7fd, 0x4ca7,
|
||||
{ 0xa8, 0x2c, 0xda, 0xeb, 0x3e, 0x02, 0x52, 0x95 } }, { 1, 0 } },
|
||||
{ { 0x8a885d04, 0x1ceb, 0x11c9,
|
||||
{ 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60 } }, { 2, 0 } },
|
||||
&s_dispatch_table, 0, 0, 0, &s_server_info, 0
|
||||
};
|
||||
|
||||
static const MIDL_STUB_DESC s_stub_desc = {
|
||||
(void*)&s_interface, nvda_rpc_alloc, nvda_rpc_free, { 0 },
|
||||
0, 0, 0, 0, s_type_format, 1, 0x50002, 0, 0x50200ca, 0, 0, 0, 1,
|
||||
0, 0, 0
|
||||
};
|
||||
|
||||
static const MIDL_SERVER_INFO s_server_info = {
|
||||
&s_stub_desc, s_routines, s_proc_format, s_proc_offset, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static wchar_t s_endpoint[128];
|
||||
|
||||
static int stand_in_start(void)
|
||||
{
|
||||
RPC_STATUS st;
|
||||
/* The client's own binding string, less "ncalrpc:[" and "]" */
|
||||
wchar_t b[128];
|
||||
size_t n;
|
||||
if (!nvda_binding_string(b, sizeof(b) / sizeof(b[0])))
|
||||
return 0;
|
||||
n = wcslen(b);
|
||||
if (n < 11)
|
||||
return 0;
|
||||
memcpy(s_endpoint, b + 9, (n - 10) * sizeof(wchar_t));
|
||||
s_endpoint[n - 10] = 0;
|
||||
st = RpcServerUseProtseqEpW((RPC_WSTR)L"ncalrpc",
|
||||
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, (RPC_WSTR)s_endpoint, NULL);
|
||||
if (st != RPC_S_OK)
|
||||
return 0;
|
||||
if (RpcServerRegisterIf((RPC_IF_HANDLE)&s_interface, NULL, NULL)
|
||||
!= RPC_S_OK)
|
||||
return 0;
|
||||
return RpcServerListen(1, 20, TRUE) == RPC_S_OK;
|
||||
}
|
||||
|
||||
static void stand_in_stop(void)
|
||||
{
|
||||
RpcMgmtStopServerListening(NULL);
|
||||
RpcMgmtWaitServerListen();
|
||||
RpcServerUnregisterIf((RPC_IF_HANDLE)&s_interface, NULL, TRUE);
|
||||
}
|
||||
|
||||
/* Wine's NdrMapCommAndFaultStatus writes the comm status, then a zero
|
||||
* fault status - and where both are the return value, as NVDA's are,
|
||||
* the zero is what is returned. On Windows the comm status is. Under
|
||||
* Wine a call that cannot reach NVDA is held to returning at all. */
|
||||
static int under_wine;
|
||||
|
||||
static int failed_call(unsigned long st)
|
||||
{
|
||||
return st != 0 || under_wine;
|
||||
}
|
||||
|
||||
/* ---- the calls, through this client or another ---- */
|
||||
|
||||
struct client
|
||||
{
|
||||
unsigned long (*test_if_running)(void);
|
||||
unsigned long (*cancel_speech)(void);
|
||||
unsigned long (*speak_text)(const wchar_t *);
|
||||
unsigned long (*braille_message)(const wchar_t *);
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static void calls_reach(const struct client *c)
|
||||
{
|
||||
static wchar_t long_text[2049];
|
||||
char msg[160];
|
||||
int i;
|
||||
const wchar_t *unicode = L"h\x00e9llo w\x00f6rld \x2713 \xd83d\xde00";
|
||||
|
||||
for (i = 0; i < 2048; i++)
|
||||
long_text[i] = (wchar_t)(L'a' + i % 26);
|
||||
long_text[2048] = 0;
|
||||
ncalls = 0;
|
||||
calls[0] = 0;
|
||||
|
||||
#define C(cond, what) do { snprintf(msg, sizeof(msg), "%s: %s", c->name, what); \
|
||||
CHECK(cond, msg); } while (0)
|
||||
C(c->test_if_running() == 0, "testIfRunning reaches NVDA");
|
||||
C(c->cancel_speech() == 0, "cancelSpeech reaches NVDA");
|
||||
C(c->speak_text(unicode) == 0 && last_text
|
||||
&& !wcscmp(last_text, unicode), "speakText, non-ASCII, as sent");
|
||||
C(c->braille_message(L"braille") == 0 && last_text
|
||||
&& !wcscmp(last_text, L"braille"), "brailleMessage as sent");
|
||||
C(c->speak_text(L"") == 0 && last_text && !last_text[0],
|
||||
"an empty string");
|
||||
C(c->speak_text(long_text) == 0 && last_text
|
||||
&& !wcscmp(last_text, long_text), "2048 characters");
|
||||
C(!strcmp(calls, "TCSBSS"), "each call once, in order");
|
||||
#undef C
|
||||
}
|
||||
|
||||
static unsigned long ours_test(void)
|
||||
{ return nvda_controller_test_if_running(); }
|
||||
static unsigned long ours_cancel(void)
|
||||
{ return nvda_controller_cancel_speech(); }
|
||||
static unsigned long ours_speak(const wchar_t *t)
|
||||
{ return nvda_controller_speak_text(t); }
|
||||
static unsigned long ours_braille(const wchar_t *t)
|
||||
{ return nvda_controller_braille_message(t); }
|
||||
|
||||
static unsigned long (__stdcall *dll_test)(void);
|
||||
static unsigned long (__stdcall *dll_cancel)(void);
|
||||
static unsigned long (__stdcall *dll_speak)(const wchar_t *);
|
||||
static unsigned long (__stdcall *dll_braille)(const wchar_t *);
|
||||
static unsigned long via_dll_test(void) { return dll_test(); }
|
||||
static unsigned long via_dll_cancel(void) { return dll_cancel(); }
|
||||
static unsigned long via_dll_speak(const wchar_t *t) { return dll_speak(t); }
|
||||
static unsigned long via_dll_braille(const wchar_t *t)
|
||||
{ return dll_braille(t); }
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static const struct client ours = {
|
||||
ours_test, ours_cancel, ours_speak, ours_braille, "this client" };
|
||||
static const struct client theirs = {
|
||||
via_dll_test, via_dll_cancel, via_dll_speak, via_dll_braille,
|
||||
"the DLL" };
|
||||
|
||||
under_wine = GetProcAddress(GetModuleHandleA("ntdll.dll"),
|
||||
"wine_get_version") != NULL;
|
||||
printf("%s%s\n", sizeof(void*) == 8 ? "x64" : "x86",
|
||||
under_wine ? ", under Wine" : "");
|
||||
|
||||
CHECK(nvda_controller_test_if_running() != 0,
|
||||
"before init: nothing sent");
|
||||
CHECK(nvda_controller_init(), "bound to the NVDA endpoint");
|
||||
CHECK(nvda_controller_init(), "bound again: kept");
|
||||
|
||||
/* No NVDA */
|
||||
CHECK(failed_call(nvda_controller_test_if_running()),
|
||||
"no NVDA: testIfRunning fails");
|
||||
CHECK(failed_call(nvda_controller_speak_text(L"x")),
|
||||
"no NVDA: speakText fails");
|
||||
CHECK(failed_call(nvda_controller_braille_message(L"x")),
|
||||
"no NVDA: brailleMessage fails");
|
||||
CHECK(failed_call(nvda_controller_cancel_speech()),
|
||||
"no NVDA: cancelSpeech fails");
|
||||
|
||||
CHECK(stand_in_start(), "the stand-in NVDA listening");
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
HMODULE dll = LoadLibraryA(argv[1]);
|
||||
CHECK(dll != NULL, "the controller client DLL loaded");
|
||||
if (dll)
|
||||
{
|
||||
dll_test = (unsigned long (__stdcall*)(void))(void (*)(void))
|
||||
GetProcAddress(dll, "nvdaController_testIfRunning");
|
||||
dll_cancel = (unsigned long (__stdcall*)(void))(void (*)(void))
|
||||
GetProcAddress(dll, "nvdaController_cancelSpeech");
|
||||
dll_speak = (unsigned long (__stdcall*)(const wchar_t*))
|
||||
(void (*)(void))GetProcAddress(dll, "nvdaController_speakText");
|
||||
dll_braille = (unsigned long (__stdcall*)(const wchar_t*))
|
||||
(void (*)(void))GetProcAddress(dll,
|
||||
"nvdaController_brailleMessage");
|
||||
if (dll_test && dll_cancel && dll_speak && dll_braille)
|
||||
calls_reach(&theirs);
|
||||
else
|
||||
CHECK(0, "the DLL's four exports");
|
||||
}
|
||||
}
|
||||
|
||||
calls_reach(&ours);
|
||||
|
||||
ncalls = 0;
|
||||
CHECK(nvda_controller_speak_text(NULL) == RPC_X_NULL_REF_POINTER
|
||||
&& ncalls == 0, "a NULL string refused, nothing sent");
|
||||
|
||||
stand_in_stop();
|
||||
ncalls = 0;
|
||||
CHECK(failed_call(nvda_controller_test_if_running()) && ncalls == 0,
|
||||
"NVDA gone: testIfRunning fails again");
|
||||
|
||||
nvda_controller_deinit();
|
||||
ncalls = 0;
|
||||
CHECK(nvda_controller_test_if_running() == RPC_S_INVALID_BINDING
|
||||
&& nvda_controller_speak_text(L"x") == RPC_S_INVALID_BINDING
|
||||
&& ncalls == 0, "after deinit: nothing sent");
|
||||
|
||||
free(last_text);
|
||||
if (failures)
|
||||
{
|
||||
printf("%u failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("PASS win32_nvda_test\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user