mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-24 00:20:01 +00:00
1153 lines
34 KiB
C
1153 lines
34 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2016-2019 - Brad Parker
|
|
* Copyright (C) 2018-2019 - Andrés Suárez
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include <retro_miscellaneous.h>
|
|
#include <windows.h>
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
#include <process.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#include <boolean.h>
|
|
#include <compat/strl.h>
|
|
#include <dynamic/dylib.h>
|
|
#include <lists/file_list.h>
|
|
#include <file/file_path.h>
|
|
#include <string/stdstring.h>
|
|
#include <encodings/utf.h>
|
|
#include <features/features_cpu.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../../config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MENU
|
|
#include "../../menu/menu_driver.h"
|
|
#endif
|
|
|
|
#include "../frontend_driver.h"
|
|
#include "../../configuration.h"
|
|
#include "../../defaults.h"
|
|
#include "../../verbosity.h"
|
|
#include "../../ui/drivers/ui_win32.h"
|
|
#include "../../paths.h"
|
|
#include "../../msg_hash.h"
|
|
#include "platform_win32.h"
|
|
|
|
#include "../../verbosity.h"
|
|
|
|
/*
|
|
#ifdef HAVE_NVDA
|
|
#include "../../nvda_controller.h"
|
|
#endif
|
|
*/
|
|
|
|
#ifdef HAVE_SAPI
|
|
#define COBJMACROS
|
|
#include <sapi.h>
|
|
#include <ole2.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SAPI
|
|
static ISpVoice* pVoice = NULL;
|
|
#endif
|
|
#ifdef HAVE_NVDA
|
|
bool USE_POWERSHELL = false;
|
|
bool USE_NVDA = true;
|
|
#else
|
|
bool USE_POWERSHELL = true;
|
|
bool USE_NVDA = false;
|
|
#endif
|
|
bool USE_NVDA_BRAILLE = false;
|
|
|
|
#ifndef SM_SERVERR2
|
|
#define SM_SERVERR2 89
|
|
#endif
|
|
|
|
/* static public global variable */
|
|
VOID (WINAPI *DragAcceptFiles_func)(HWND, BOOL);
|
|
|
|
/* TODO/FIXME - static global variables */
|
|
static bool dwm_composition_disabled = false;
|
|
static bool console_needs_free = false;
|
|
static char win32_cpu_model_name[64] = {0};
|
|
static bool pi_set = false;
|
|
#ifdef HAVE_DYNAMIC
|
|
/* We only load this library once, so we let it be
|
|
* unloaded at application shutdown, since unloading
|
|
* it early seems to cause issues on some systems.
|
|
*/
|
|
static dylib_t dwmlib;
|
|
static dylib_t shell32lib;
|
|
static dylib_t nvdalib;
|
|
#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*);
|
|
|
|
#if defined(HAVE_LANGEXTRA) && !defined(_XBOX)
|
|
#if (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) || !defined(_MSC_VER)
|
|
struct win32_lang_pair
|
|
{
|
|
unsigned short lang_ident;
|
|
enum retro_language lang;
|
|
};
|
|
|
|
/* https://docs.microsoft.com/en-us/windows/desktop/Intl/language-identifier-constants-and-strings */
|
|
const struct win32_lang_pair win32_lang_pairs[] =
|
|
{
|
|
/* array order MUST be kept, always largest ID first */
|
|
{0x7c04, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* neutral */
|
|
{0x1404, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* MO */
|
|
{0x1004, RETRO_LANGUAGE_CHINESE_SIMPLIFIED}, /* SG */
|
|
{0xC04, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* HK/PRC */
|
|
{0x816, RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
|
|
{0x416, RETRO_LANGUAGE_PORTUGUESE_BRAZIL},
|
|
{0x2a, RETRO_LANGUAGE_VIETNAMESE},
|
|
{0x19, RETRO_LANGUAGE_RUSSIAN},
|
|
{0x16, RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
|
|
{0x15, RETRO_LANGUAGE_POLISH},
|
|
{0x13, RETRO_LANGUAGE_DUTCH},
|
|
{0x12, RETRO_LANGUAGE_KOREAN},
|
|
{0x11, RETRO_LANGUAGE_JAPANESE},
|
|
{0x10, RETRO_LANGUAGE_ITALIAN},
|
|
{0xc, RETRO_LANGUAGE_FRENCH},
|
|
{0xa, RETRO_LANGUAGE_SPANISH},
|
|
{0x9, RETRO_LANGUAGE_ENGLISH},
|
|
{0x8, RETRO_LANGUAGE_GREEK},
|
|
{0x7, RETRO_LANGUAGE_GERMAN},
|
|
{0x4, RETRO_LANGUAGE_CHINESE_SIMPLIFIED}, /* neutral */
|
|
{0x1, RETRO_LANGUAGE_ARABIC},
|
|
/* MS does not support Esperanto */
|
|
/*{0x0, RETRO_LANGUAGE_ESPERANTO},*/
|
|
};
|
|
|
|
unsigned short win32_get_langid_from_retro_lang(enum retro_language lang)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sizeof(win32_lang_pairs) / sizeof(win32_lang_pairs[0]); i++)
|
|
{
|
|
if (win32_lang_pairs[i].lang == lang)
|
|
return win32_lang_pairs[i].lang_ident;
|
|
}
|
|
|
|
return 0x409; /* fallback to US English */
|
|
}
|
|
|
|
enum retro_language win32_get_retro_lang_from_langid(unsigned short langid)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < sizeof(win32_lang_pairs) / sizeof(win32_lang_pairs[0]); i++)
|
|
{
|
|
if (win32_lang_pairs[i].lang_ident > 0x3ff)
|
|
{
|
|
if (langid == win32_lang_pairs[i].lang_ident)
|
|
return win32_lang_pairs[i].lang;
|
|
}
|
|
else
|
|
{
|
|
if ((langid & 0x3ff) == win32_lang_pairs[i].lang_ident)
|
|
return win32_lang_pairs[i].lang;
|
|
}
|
|
}
|
|
|
|
return RETRO_LANGUAGE_ENGLISH;
|
|
}
|
|
#endif
|
|
#else
|
|
unsigned short win32_get_langid_from_retro_lang(enum retro_language lang)
|
|
{
|
|
return 0x409; /* fallback to US English */
|
|
}
|
|
|
|
enum retro_language win32_get_retro_lang_from_langid(unsigned short langid)
|
|
{
|
|
return RETRO_LANGUAGE_ENGLISH;
|
|
}
|
|
#endif
|
|
|
|
static void gfx_dwm_shutdown(void)
|
|
{
|
|
#ifdef HAVE_DYNAMIC
|
|
if (dwmlib)
|
|
dylib_close(dwmlib);
|
|
if (shell32lib)
|
|
dylib_close(shell32lib);
|
|
dwmlib = NULL;
|
|
shell32lib = NULL;
|
|
#endif
|
|
}
|
|
|
|
static bool gfx_init_dwm(void)
|
|
{
|
|
HRESULT (WINAPI *mmcss)(BOOL);
|
|
static bool inited = false;
|
|
|
|
if (inited)
|
|
return true;
|
|
|
|
atexit(gfx_dwm_shutdown);
|
|
|
|
#ifdef HAVE_DYNAMIC
|
|
shell32lib = dylib_load("shell32.dll");
|
|
if (!shell32lib)
|
|
{
|
|
RARCH_WARN("Did not find shell32.dll.\n");
|
|
}
|
|
|
|
dwmlib = dylib_load("dwmapi.dll");
|
|
if (!dwmlib)
|
|
{
|
|
RARCH_WARN("Did not find dwmapi.dll.\n");
|
|
return false;
|
|
}
|
|
|
|
DragAcceptFiles_func =
|
|
(VOID (WINAPI*)(HWND, BOOL))dylib_proc(shell32lib, "DragAcceptFiles");
|
|
|
|
mmcss =
|
|
(HRESULT(WINAPI*)(BOOL))dylib_proc(dwmlib, "DwmEnableMMCSS");
|
|
#else
|
|
DragAcceptFiles_func = DragAcceptFiles;
|
|
#if 0
|
|
mmcss = DwmEnableMMCSS;
|
|
#endif
|
|
#endif
|
|
|
|
if (mmcss)
|
|
mmcss(TRUE);
|
|
|
|
inited = true;
|
|
return true;
|
|
}
|
|
|
|
static void gfx_set_dwm(void)
|
|
{
|
|
HRESULT ret;
|
|
HRESULT (WINAPI *composition_enable)(UINT);
|
|
settings_t *settings = config_get_ptr();
|
|
bool disable_composition = settings->bools.video_disable_composition;
|
|
|
|
if (!gfx_init_dwm())
|
|
return;
|
|
|
|
if (disable_composition == dwm_composition_disabled)
|
|
return;
|
|
|
|
#ifdef HAVE_DYNAMIC
|
|
composition_enable =
|
|
(HRESULT (WINAPI*)(UINT))dylib_proc(dwmlib, "DwmEnableComposition");
|
|
#endif
|
|
|
|
if (!composition_enable)
|
|
{
|
|
RARCH_ERR("Did not find DwmEnableComposition ...\n");
|
|
return;
|
|
}
|
|
|
|
ret = composition_enable(!disable_composition);
|
|
if (FAILED(ret))
|
|
RARCH_ERR("Failed to set composition state ...\n");
|
|
dwm_composition_disabled = disable_composition;
|
|
}
|
|
|
|
static void frontend_win32_get_os(char *s, size_t len, int *major, int *minor)
|
|
{
|
|
char buildStr[11] = {0};
|
|
bool server = false;
|
|
const char *arch = "";
|
|
|
|
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
|
/* Windows 2000 and later */
|
|
SYSTEM_INFO si = {{0}};
|
|
OSVERSIONINFOEX vi = {0};
|
|
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
|
|
GetSystemInfo(&si);
|
|
|
|
/* Available from NT 3.5 and Win95 */
|
|
GetVersionEx((OSVERSIONINFO*)&vi);
|
|
|
|
server = vi.wProductType != VER_NT_WORKSTATION;
|
|
|
|
switch (si.wProcessorArchitecture)
|
|
{
|
|
case PROCESSOR_ARCHITECTURE_AMD64:
|
|
arch = "x64";
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
arch = "x86";
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_ARM:
|
|
arch = "ARM";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#else
|
|
OSVERSIONINFO vi = {0};
|
|
vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
|
|
/* Available from NT 3.5 and Win95 */
|
|
GetVersionEx(&vi);
|
|
#endif
|
|
|
|
if (major)
|
|
*major = vi.dwMajorVersion;
|
|
|
|
if (minor)
|
|
*minor = vi.dwMinorVersion;
|
|
|
|
if (vi.dwMajorVersion == 4 && vi.dwMinorVersion == 0)
|
|
snprintf(buildStr, sizeof(buildStr), "%lu", (DWORD)(LOWORD(vi.dwBuildNumber))); /* Windows 95 build number is in the low-order word only */
|
|
else
|
|
snprintf(buildStr, sizeof(buildStr), "%lu", vi.dwBuildNumber);
|
|
|
|
switch (vi.dwMajorVersion)
|
|
{
|
|
case 10:
|
|
if (server)
|
|
strlcpy(s, "Windows Server 2016", len);
|
|
else
|
|
strlcpy(s, "Windows 10", len);
|
|
break;
|
|
case 6:
|
|
switch (vi.dwMinorVersion)
|
|
{
|
|
case 3:
|
|
if (server)
|
|
strlcpy(s, "Windows Server 2012 R2", len);
|
|
else
|
|
strlcpy(s, "Windows 8.1", len);
|
|
break;
|
|
case 2:
|
|
if (server)
|
|
strlcpy(s, "Windows Server 2012", len);
|
|
else
|
|
strlcpy(s, "Windows 8", len);
|
|
break;
|
|
case 1:
|
|
if (server)
|
|
strlcpy(s, "Windows Server 2008 R2", len);
|
|
else
|
|
strlcpy(s, "Windows 7", len);
|
|
break;
|
|
case 0:
|
|
if (server)
|
|
strlcpy(s, "Windows Server 2008", len);
|
|
else
|
|
strlcpy(s, "Windows Vista", len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case 5:
|
|
switch (vi.dwMinorVersion)
|
|
{
|
|
case 2:
|
|
if (server)
|
|
{
|
|
strlcpy(s, "Windows Server 2003", len);
|
|
if (GetSystemMetrics(SM_SERVERR2))
|
|
strlcat(s, " R2", len);
|
|
}
|
|
else
|
|
{
|
|
/* Yes, XP Pro x64 is a higher version number than XP x86 */
|
|
if (string_is_equal(arch, "x64"))
|
|
strlcpy(s, "Windows XP", len);
|
|
}
|
|
break;
|
|
case 1:
|
|
strlcpy(s, "Windows XP", len);
|
|
break;
|
|
case 0:
|
|
strlcpy(s, "Windows 2000", len);
|
|
break;
|
|
}
|
|
break;
|
|
case 4:
|
|
switch (vi.dwMinorVersion)
|
|
{
|
|
case 0:
|
|
if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
|
|
strlcpy(s, "Windows 95", len);
|
|
else if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
|
|
strlcpy(s, "Windows NT 4.0", len);
|
|
else
|
|
strlcpy(s, "Unknown", len);
|
|
break;
|
|
case 90:
|
|
strlcpy(s, "Windows ME", len);
|
|
break;
|
|
case 10:
|
|
strlcpy(s, "Windows 98", len);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
snprintf(s, len, "Windows %i.%i", *major, *minor);
|
|
break;
|
|
}
|
|
|
|
if (!string_is_empty(arch))
|
|
{
|
|
strlcat(s, " ", len);
|
|
strlcat(s, arch, len);
|
|
}
|
|
|
|
strlcat(s, " Build ", len);
|
|
strlcat(s, buildStr, len);
|
|
|
|
if (!string_is_empty(vi.szCSDVersion))
|
|
{
|
|
strlcat(s, " ", len);
|
|
strlcat(s, vi.szCSDVersion, len);
|
|
}
|
|
|
|
}
|
|
|
|
static void frontend_win32_init(void *data)
|
|
{
|
|
typedef BOOL (WINAPI *isProcessDPIAwareProc)();
|
|
typedef BOOL (WINAPI *setProcessDPIAwareProc)();
|
|
#ifdef HAVE_DYNAMIC
|
|
HMODULE handle =
|
|
GetModuleHandle("User32.dll");
|
|
isProcessDPIAwareProc isDPIAwareProc =
|
|
(isProcessDPIAwareProc)dylib_proc(handle, "IsProcessDPIAware");
|
|
setProcessDPIAwareProc setDPIAwareProc =
|
|
(setProcessDPIAwareProc)dylib_proc(handle, "SetProcessDPIAware");
|
|
#else
|
|
isProcessDPIAwareProc isDPIAwareProc = IsProcessDPIAware;
|
|
setProcessDPIAwareProc setDPIAwareProc = SetProcessDPIAware;
|
|
#endif
|
|
|
|
if (isDPIAwareProc)
|
|
if (!isDPIAwareProc())
|
|
if (setDPIAwareProc)
|
|
setDPIAwareProc();
|
|
}
|
|
|
|
|
|
#ifdef HAVE_NVDA
|
|
static void init_nvda(void)
|
|
{
|
|
#ifdef HAVE_DYNAMIC
|
|
if (USE_NVDA && !nvdalib)
|
|
{
|
|
nvdalib = dylib_load("nvdaControllerClient64.dll");
|
|
if (!nvdalib)
|
|
{
|
|
USE_NVDA = false;
|
|
USE_POWERSHELL = true;
|
|
}
|
|
else
|
|
{
|
|
nvdaController_testIfRunning_func = ( unsigned long (__stdcall*)(void))dylib_proc(nvdalib, "nvdaController_testIfRunning");
|
|
nvdaController_cancelSpeech_func = (unsigned long(__stdcall *)(void))dylib_proc(nvdalib, "nvdaController_cancelSpeech");
|
|
nvdaController_brailleMessage_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvdalib, "nvdaController_brailleMessage");
|
|
nvdaController_speakText_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvdalib, "nvdaController_speakText");
|
|
|
|
}
|
|
}
|
|
#else
|
|
USE_NVDA = false;
|
|
USE_POWERSHELL = true;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
enum frontend_powerstate frontend_win32_get_powerstate(int *seconds, int *percent)
|
|
{
|
|
SYSTEM_POWER_STATUS status;
|
|
enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE;
|
|
|
|
if (!GetSystemPowerStatus(&status))
|
|
return ret;
|
|
|
|
if (status.BatteryFlag == 0xFF)
|
|
ret = FRONTEND_POWERSTATE_NONE;
|
|
else if (status.BatteryFlag & (1 << 7))
|
|
ret = FRONTEND_POWERSTATE_NO_SOURCE;
|
|
else if (status.BatteryFlag & (1 << 3))
|
|
ret = FRONTEND_POWERSTATE_CHARGING;
|
|
else if (status.ACLineStatus == 1)
|
|
ret = FRONTEND_POWERSTATE_CHARGED;
|
|
else
|
|
ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
|
|
|
|
*percent = (int)status.BatteryLifePercent;
|
|
*seconds = (int)status.BatteryLifeTime;
|
|
|
|
#ifdef _WIN32
|
|
if (*percent == 255)
|
|
*percent = 0;
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
enum frontend_architecture frontend_win32_get_architecture(void)
|
|
{
|
|
#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
|
|
/* Windows 2000 and later */
|
|
SYSTEM_INFO si = {{0}};
|
|
|
|
GetSystemInfo(&si);
|
|
|
|
switch (si.wProcessorArchitecture)
|
|
{
|
|
case PROCESSOR_ARCHITECTURE_AMD64:
|
|
return FRONTEND_ARCH_X86_64;
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
return FRONTEND_ARCH_X86;
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_ARM:
|
|
return FRONTEND_ARCH_ARM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return FRONTEND_ARCH_NONE;
|
|
}
|
|
|
|
static int frontend_win32_parse_drive_list(void *data, bool load_content)
|
|
{
|
|
#ifdef HAVE_MENU
|
|
file_list_t *list = (file_list_t*)data;
|
|
enum msg_hash_enums enum_idx = load_content ?
|
|
MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR :
|
|
MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
|
|
size_t i = 0;
|
|
unsigned drives = GetLogicalDrives();
|
|
char drive[] = " :\\";
|
|
|
|
for (i = 0; i < 32; i++)
|
|
{
|
|
drive[0] = 'A' + i;
|
|
if (drives & (1 << i))
|
|
menu_entries_append_enum(list,
|
|
drive,
|
|
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
|
|
enum_idx,
|
|
FILE_TYPE_DIRECTORY, 0, 0);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void frontend_win32_environment_get(int *argc, char *argv[],
|
|
void *args, void *params_data)
|
|
{
|
|
gfx_set_dwm();
|
|
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_ASSETS],
|
|
":\\assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER],
|
|
":\\filters\\audio", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER],
|
|
":\\filters\\video", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CHEATS],
|
|
":\\cheats", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_DATABASE],
|
|
":\\database\\rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CURSOR],
|
|
":\\database\\cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_PLAYLIST],
|
|
":\\playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG],
|
|
":\\config\\record", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT],
|
|
":\\recordings", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG],
|
|
":\\config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_REMAP],
|
|
":\\config\\remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_WALLPAPERS],
|
|
":\\assets\\wallpapers", sizeof(g_defaults.dirs[DEFAULT_DIR_WALLPAPERS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS],
|
|
":\\thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_OVERLAY],
|
|
":\\overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY]));
|
|
#ifdef HAVE_VIDEO_LAYOUT
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT],
|
|
":\\layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT]));
|
|
#endif
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE],
|
|
":\\cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE_INFO],
|
|
":\\info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG],
|
|
":\\autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SHADER],
|
|
":\\shaders", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS],
|
|
":\\downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT],
|
|
":\\screenshots", sizeof(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SRAM],
|
|
":\\saves", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SAVESTATE],
|
|
":\\states", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SYSTEM],
|
|
":\\system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM]));
|
|
fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_LOGS],
|
|
":\\logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS]));
|
|
}
|
|
|
|
static uint64_t frontend_win32_get_total_mem(void)
|
|
{
|
|
/* OSes below 2000 don't have the Ex version,
|
|
* and non-Ex cannot work with >4GB RAM */
|
|
#if _WIN32_WINNT >= 0x0500
|
|
MEMORYSTATUSEX mem_info;
|
|
mem_info.dwLength = sizeof(MEMORYSTATUSEX);
|
|
GlobalMemoryStatusEx(&mem_info);
|
|
return mem_info.ullTotalPhys;
|
|
#else
|
|
MEMORYSTATUS mem_info;
|
|
mem_info.dwLength = sizeof(MEMORYSTATUS);
|
|
GlobalMemoryStatus(&mem_info);
|
|
return mem_info.dwTotalPhys;
|
|
#endif
|
|
}
|
|
|
|
static uint64_t frontend_win32_get_free_mem(void)
|
|
{
|
|
/* OSes below 2000 don't have the Ex version,
|
|
* and non-Ex cannot work with >4GB RAM */
|
|
#if _WIN32_WINNT >= 0x0500
|
|
MEMORYSTATUSEX mem_info;
|
|
mem_info.dwLength = sizeof(MEMORYSTATUSEX);
|
|
GlobalMemoryStatusEx(&mem_info);
|
|
return mem_info.ullAvailPhys;
|
|
#else
|
|
MEMORYSTATUS mem_info;
|
|
mem_info.dwLength = sizeof(MEMORYSTATUS);
|
|
GlobalMemoryStatus(&mem_info);
|
|
return mem_info.dwAvailPhys;
|
|
#endif
|
|
}
|
|
|
|
static void frontend_win32_attach_console(void)
|
|
{
|
|
#ifdef _WIN32
|
|
#ifdef _WIN32_WINNT_WINXP
|
|
/* msys will start the process with FILE_TYPE_PIPE connected.
|
|
* cmd will start the process with FILE_TYPE_UNKNOWN connected
|
|
* (since this is subsystem windows application
|
|
* ... UNLESS stdout/stderr were redirected (then FILE_TYPE_DISK
|
|
* will be connected most likely)
|
|
* explorer will start the process with NOTHING connected.
|
|
*
|
|
* Now, let's not reconnect anything that's already connected.
|
|
* If any are disconnected, open a console, and connect to them.
|
|
* In case we're launched from msys or cmd, try attaching to the
|
|
* parent process console first.
|
|
*
|
|
* Take care to leave a record of what we did, so we can
|
|
* undo it precisely.
|
|
*/
|
|
|
|
bool need_stdout = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE))
|
|
== FILE_TYPE_UNKNOWN);
|
|
bool need_stderr = (GetFileType(GetStdHandle(STD_ERROR_HANDLE))
|
|
== FILE_TYPE_UNKNOWN);
|
|
|
|
if (need_stdout || need_stderr)
|
|
{
|
|
if (!AttachConsole( ATTACH_PARENT_PROCESS))
|
|
AllocConsole();
|
|
|
|
SetConsoleTitle("Log Console");
|
|
|
|
if (need_stdout)
|
|
freopen( "CONOUT$", "w", stdout );
|
|
if (need_stderr)
|
|
freopen( "CONOUT$", "w", stderr );
|
|
|
|
console_needs_free = true;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static void frontend_win32_detach_console(void)
|
|
{
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
#ifdef _WIN32_WINNT_WINXP
|
|
if (console_needs_free)
|
|
{
|
|
/* we don't reconnect stdout/stderr to anything here,
|
|
* because by definition, they weren't connected to
|
|
* anything in the first place. */
|
|
FreeConsole();
|
|
console_needs_free = false;
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
static const char* frontend_win32_get_cpu_model_name(void)
|
|
{
|
|
#ifdef ANDROID
|
|
return NULL;
|
|
#else
|
|
cpu_features_get_model_name(win32_cpu_model_name, sizeof(win32_cpu_model_name));
|
|
return win32_cpu_model_name;
|
|
#endif
|
|
}
|
|
|
|
enum retro_language frontend_win32_get_user_language(void)
|
|
{
|
|
enum retro_language lang = RETRO_LANGUAGE_ENGLISH;
|
|
#if defined(HAVE_LANGEXTRA) && !defined(_XBOX)
|
|
#if (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) || !defined(_MSC_VER)
|
|
LANGID langid = GetUserDefaultUILanguage();
|
|
lang = win32_get_retro_lang_from_langid(langid);
|
|
#endif
|
|
#endif
|
|
return lang;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
enum frontend_fork win32_fork_mode;
|
|
|
|
static void frontend_win32_respawn(char *s, size_t len, char *args)
|
|
{
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
char executable_path[PATH_MAX_LENGTH] = {0};
|
|
|
|
if (win32_fork_mode != FRONTEND_FORK_RESTART)
|
|
return;
|
|
|
|
fill_pathname_application_path(executable_path,
|
|
sizeof(executable_path));
|
|
path_set(RARCH_PATH_CORE, executable_path);
|
|
RARCH_LOG("Restarting RetroArch with commandline: %s and %s\n",
|
|
executable_path, args);
|
|
|
|
memset(&si, 0, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
memset(&pi, 0, sizeof(pi));
|
|
|
|
if (!CreateProcess( executable_path, args,
|
|
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
|
|
{
|
|
RARCH_LOG("Failed to restart RetroArch\n");
|
|
}
|
|
}
|
|
|
|
static bool frontend_win32_set_fork(enum frontend_fork fork_mode)
|
|
{
|
|
switch (fork_mode)
|
|
{
|
|
case FRONTEND_FORK_CORE:
|
|
break;
|
|
case FRONTEND_FORK_CORE_WITH_ARGS:
|
|
break;
|
|
case FRONTEND_FORK_RESTART:
|
|
command_event(CMD_EVENT_QUIT, NULL);
|
|
break;
|
|
case FRONTEND_FORK_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
win32_fork_mode = fork_mode;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
static const char *accessibility_win_language_id(const char* language)
|
|
{
|
|
if (string_is_equal(language,"en"))
|
|
return "409";
|
|
else if (string_is_equal(language,"it"))
|
|
return "410";
|
|
else if (string_is_equal(language,"sv"))
|
|
return "041d";
|
|
else if (string_is_equal(language,"fr"))
|
|
return "040c";
|
|
else if (string_is_equal(language,"de"))
|
|
return "407";
|
|
else if (string_is_equal(language,"he"))
|
|
return "040d";
|
|
else if (string_is_equal(language,"id"))
|
|
return "421";
|
|
else if (string_is_equal(language,"es"))
|
|
return "040a";
|
|
else if (string_is_equal(language,"nl"))
|
|
return "413";
|
|
else if (string_is_equal(language,"ro"))
|
|
return "418";
|
|
else if (string_is_equal(language,"pt_pt"))
|
|
return "816";
|
|
else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt"))
|
|
return "416";
|
|
else if (string_is_equal(language,"th"))
|
|
return "041e";
|
|
else if (string_is_equal(language,"ja"))
|
|
return "411";
|
|
else if (string_is_equal(language,"sk"))
|
|
return "041b";
|
|
else if (string_is_equal(language,"hi"))
|
|
return "439";
|
|
else if (string_is_equal(language,"ar"))
|
|
return "401";
|
|
else if (string_is_equal(language,"hu"))
|
|
return "040e";
|
|
else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh"))
|
|
return "804";
|
|
else if (string_is_equal(language,"el"))
|
|
return "408";
|
|
else if (string_is_equal(language,"ru"))
|
|
return "419";
|
|
else if (string_is_equal(language,"nb"))
|
|
return "414";
|
|
else if (string_is_equal(language,"da"))
|
|
return "406";
|
|
else if (string_is_equal(language,"fi"))
|
|
return "040b";
|
|
else if (string_is_equal(language,"zh_hk"))
|
|
return "0c04";
|
|
else if (string_is_equal(language,"zh_cn"))
|
|
return "804";
|
|
else if (string_is_equal(language,"tr"))
|
|
return "041f";
|
|
else if (string_is_equal(language,"ko"))
|
|
return "412";
|
|
else if (string_is_equal(language,"pl"))
|
|
return "415";
|
|
else if (string_is_equal(language,"cs"))
|
|
return "405";
|
|
else
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
static const char *accessibility_win_language_code(const char* language)
|
|
{
|
|
if (string_is_equal(language,"en"))
|
|
return "Microsoft David Desktop";
|
|
else if (string_is_equal(language,"it"))
|
|
return "Microsoft Cosimo Desktop";
|
|
else if (string_is_equal(language,"sv"))
|
|
return "Microsoft Bengt Desktop";
|
|
else if (string_is_equal(language,"fr"))
|
|
return "Microsoft Paul Desktop";
|
|
else if (string_is_equal(language,"de"))
|
|
return "Microsoft Stefan Desktop";
|
|
else if (string_is_equal(language,"he"))
|
|
return "Microsoft Asaf Desktop";
|
|
else if (string_is_equal(language,"id"))
|
|
return "Microsoft Andika Desktop";
|
|
else if (string_is_equal(language,"es"))
|
|
return "Microsoft Pablo Desktop";
|
|
else if (string_is_equal(language,"nl"))
|
|
return "Microsoft Frank Desktop";
|
|
else if (string_is_equal(language,"ro"))
|
|
return "Microsoft Andrei Desktop";
|
|
else if (string_is_equal(language,"pt_pt"))
|
|
return "Microsoft Helia Desktop";
|
|
else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt"))
|
|
return "Microsoft Daniel Desktop";
|
|
else if (string_is_equal(language,"th"))
|
|
return "Microsoft Pattara Desktop";
|
|
else if (string_is_equal(language,"ja"))
|
|
return "Microsoft Ichiro Desktop";
|
|
else if (string_is_equal(language,"sk"))
|
|
return "Microsoft Filip Desktop";
|
|
else if (string_is_equal(language,"hi"))
|
|
return "Microsoft Hemant Desktop";
|
|
else if (string_is_equal(language,"ar"))
|
|
return "Microsoft Naayf Desktop";
|
|
else if (string_is_equal(language,"hu"))
|
|
return "Microsoft Szabolcs Desktop";
|
|
else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh"))
|
|
return "Microsoft Zhiwei Desktop";
|
|
else if (string_is_equal(language,"el"))
|
|
return "Microsoft Stefanos Desktop";
|
|
else if (string_is_equal(language,"ru"))
|
|
return "Microsoft Pavel Desktop";
|
|
else if (string_is_equal(language,"nb"))
|
|
return "Microsoft Jon Desktop";
|
|
else if (string_is_equal(language,"da"))
|
|
return "Microsoft Helle Desktop";
|
|
else if (string_is_equal(language,"fi"))
|
|
return "Microsoft Heidi Desktop";
|
|
else if (string_is_equal(language,"zh_hk"))
|
|
return "Microsoft Danny Desktop";
|
|
else if (string_is_equal(language,"zh_cn"))
|
|
return "Microsoft Kangkang Desktop";
|
|
else if (string_is_equal(language,"tr"))
|
|
return "Microsoft Tolga Desktop";
|
|
else if (string_is_equal(language,"ko"))
|
|
return "Microsoft Heami Desktop";
|
|
else if (string_is_equal(language,"pl"))
|
|
return "Microsoft Adam Desktop";
|
|
else if (string_is_equal(language,"cs"))
|
|
return "Microsoft Jakub Desktop";
|
|
else
|
|
return "";
|
|
}
|
|
|
|
static bool terminate_win32_process(PROCESS_INFORMATION pi)
|
|
{
|
|
TerminateProcess(pi.hProcess,0);
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
return true;
|
|
}
|
|
|
|
static PROCESS_INFORMATION g_pi;
|
|
|
|
static bool create_win32_process(char* cmd)
|
|
{
|
|
STARTUPINFO si;
|
|
memset(&si, 0, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
memset(&g_pi, 0, sizeof(g_pi));
|
|
|
|
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW,
|
|
NULL, NULL, &si, &g_pi))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool is_narrator_running_windows(void)
|
|
{
|
|
DWORD status = 0;
|
|
#ifdef HAVE_NVDA
|
|
init_nvda();
|
|
#endif
|
|
|
|
if (USE_POWERSHELL)
|
|
{
|
|
if (pi_set == false)
|
|
return false;
|
|
if (GetExitCodeProcess(g_pi.hProcess, &status))
|
|
{
|
|
if (status == STILL_ACTIVE)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#ifdef HAVE_NVDA
|
|
else if (USE_NVDA)
|
|
{
|
|
long res;
|
|
res = nvdaController_testIfRunning_func();
|
|
|
|
if (res != 0)
|
|
{
|
|
/* The running nvda service wasn't found, so revert
|
|
back to the powershell method
|
|
*/
|
|
RARCH_LOG("Error communicating with NVDA\n");
|
|
USE_POWERSHELL = true;
|
|
USE_NVDA = false;
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_SAPI
|
|
else
|
|
{
|
|
SPVOICESTATUS pStatus;
|
|
if (pVoice)
|
|
{
|
|
ISpVoice_GetStatus(pVoice, &pStatus, NULL);
|
|
if (pStatus.dwRunningState == SPRS_IS_SPEAKING)
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static bool accessibility_speak_windows(int speed,
|
|
const char* speak_text, int priority)
|
|
{
|
|
char cmd[1200];
|
|
const char *voice = get_user_language_iso639_1(true);
|
|
const char *language = accessibility_win_language_code(voice);
|
|
const char *langid = accessibility_win_language_id(voice);
|
|
bool res = false;
|
|
const char* speeds[10] = {"-10", "-7.5", "-5", "-2.5", "0", "2", "4", "6", "8", "10"};
|
|
|
|
if (speed < 1)
|
|
speed = 1;
|
|
else if (speed > 10)
|
|
speed = 10;
|
|
|
|
if (priority < 10)
|
|
{
|
|
if (is_narrator_running_windows())
|
|
return true;
|
|
|
|
}
|
|
#ifdef HAVE_NVDA
|
|
init_nvda();
|
|
#endif
|
|
|
|
if (USE_POWERSHELL)
|
|
{
|
|
if (strlen(language) > 0)
|
|
snprintf(cmd, sizeof(cmd),
|
|
"powershell.exe -NoProfile -WindowStyle Hidden -Command \"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.SelectVoice(\\\"%s\\\"); $synth.Rate = %s; $synth.Speak(\\\"%s\\\");\"", language, speeds[speed-1], (char*) speak_text);
|
|
else
|
|
snprintf(cmd, sizeof(cmd),
|
|
"powershell.exe -NoProfile -WindowStyle Hidden -Command \"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.Rate = %s; $synth.Speak(\\\"%s\\\");\"", speeds[speed-1], (char*) speak_text);
|
|
if (pi_set)
|
|
terminate_win32_process(g_pi);
|
|
res = create_win32_process(cmd);
|
|
if (!res)
|
|
{
|
|
pi_set = false;
|
|
return true;
|
|
}
|
|
pi_set = true;
|
|
}
|
|
#ifdef HAVE_NVDA
|
|
else if (USE_NVDA)
|
|
{
|
|
wchar_t *wc = utf8_to_utf16_string_alloc(speak_text);
|
|
long res = nvdaController_testIfRunning_func();
|
|
|
|
if (!wc || res != 0)
|
|
{
|
|
RARCH_LOG("Error communicating with NVDA\n");
|
|
if (wc)
|
|
free(wc);
|
|
return false;
|
|
}
|
|
|
|
nvdaController_cancelSpeech_func();
|
|
|
|
if (USE_NVDA_BRAILLE)
|
|
nvdaController_brailleMessage_func(wc);
|
|
else
|
|
nvdaController_speakText_func(wc);
|
|
free(wc);
|
|
}
|
|
#endif
|
|
#ifdef HAVE_SAPI
|
|
else
|
|
{
|
|
HRESULT hr;
|
|
/* stop the old voice if running */
|
|
if (pVoice)
|
|
{
|
|
CoUninitialize();
|
|
ISpVoice_Release(pVoice);
|
|
}
|
|
pVoice = NULL;
|
|
|
|
/* Play the new voice */
|
|
if (FAILED(CoInitialize(NULL)))
|
|
return NULL;
|
|
|
|
hr = CoCreateInstance(&CLSID_SpVoice, NULL,
|
|
CLSCTX_ALL, &IID_ISpVoice, (void **)&pVoice);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
wchar_t *wc = utf8_to_utf16_string_alloc(speak_text);
|
|
|
|
snprintf(cmd, sizeof(cmd),
|
|
"<rate speed=\"%s\"/><volume level=\"80\"/><lang langid=\"%s\"/>%s", speeds[speed], langid, speak_text);
|
|
|
|
if (!wc)
|
|
return false;
|
|
|
|
hr = ISpVoice_Speak(pVoice, wc, SPF_ASYNC /*SVSFlagsAsync*/, NULL);
|
|
free(wc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
frontend_ctx_driver_t frontend_ctx_win32 = {
|
|
frontend_win32_environment_get,
|
|
frontend_win32_init,
|
|
NULL, /* deinit */
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
frontend_win32_respawn, /* exitspawn */
|
|
#else
|
|
NULL, /* exitspawn */
|
|
#endif
|
|
NULL, /* process_args */
|
|
NULL, /* exec */
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
frontend_win32_set_fork, /* set_fork */
|
|
#else
|
|
NULL, /* set_fork */
|
|
#endif
|
|
NULL, /* shutdown */
|
|
NULL, /* get_name */
|
|
frontend_win32_get_os,
|
|
NULL, /* get_rating */
|
|
NULL, /* load_content */
|
|
frontend_win32_get_architecture,
|
|
frontend_win32_get_powerstate,
|
|
frontend_win32_parse_drive_list,
|
|
frontend_win32_get_total_mem,
|
|
frontend_win32_get_free_mem,
|
|
NULL, /* install_signal_handler */
|
|
NULL, /* get_sighandler_state */
|
|
NULL, /* set_sighandler_state */
|
|
NULL, /* destroy_sighandler_state */
|
|
frontend_win32_attach_console, /* attach_console */
|
|
frontend_win32_detach_console, /* detach_console */
|
|
NULL, /* watch_path_for_changes */
|
|
NULL, /* check_for_path_changes */
|
|
NULL, /* set_sustained_performance_mode */
|
|
frontend_win32_get_cpu_model_name,
|
|
frontend_win32_get_user_language,
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
is_narrator_running_windows,
|
|
accessibility_speak_windows,
|
|
#else
|
|
NULL, /* is_narrator_running */
|
|
NULL, /* accessibility_speak */
|
|
#endif
|
|
"win32"
|
|
};
|