/* 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 . */ #include #include #include #include #include #if defined(_WIN32) && !defined(_XBOX) #include #include #endif #include #include #include #include #include #include #include #include #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 #include #endif #ifdef HAVE_SAPI static ISpVoice* pVoice = NULL; #endif #ifdef HAVE_NVDA static bool USE_POWERSHELL = false; static bool USE_NVDA = true; #else static bool USE_POWERSHELL = true; static bool USE_NVDA = false; #endif static 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) strcpy_literal(s, "Windows Server 2016"); else strcpy_literal(s, "Windows 10"); break; case 6: switch (vi.dwMinorVersion) { case 3: if (server) strcpy_literal(s, "Windows Server 2012 R2"); else strcpy_literal(s, "Windows 8.1"); break; case 2: if (server) strcpy_literal(s, "Windows Server 2012"); else strcpy_literal(s, "Windows 8"); break; case 1: if (server) strcpy_literal(s, "Windows Server 2008 R2"); else strcpy_literal(s, "Windows 7"); break; case 0: if (server) strcpy_literal(s, "Windows Server 2008"); else strcpy_literal(s, "Windows Vista"); break; default: break; } break; case 5: switch (vi.dwMinorVersion) { case 2: if (server) { strcpy_literal(s, "Windows Server 2003"); 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")) strcpy_literal(s, "Windows XP"); } break; case 1: strcpy_literal(s, "Windows XP"); break; case 0: strcpy_literal(s, "Windows 2000"); break; } break; case 4: switch (vi.dwMinorVersion) { case 0: if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) strcpy_literal(s, "Windows 95"); else if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT) strcpy_literal(s, "Windows NT 4.0"); else strcpy_literal(s, "Unknown"); break; case 90: strcpy_literal(s, "Windows ME"); break; case 10: strcpy_literal(s, "Windows 98"); 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_arch(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_env_get(int *argc, char *argv[], void *args, void *params_data) { const char *tmp_dir = getenv("TMP"); if (!string_is_empty(tmp_dir)) fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CACHE], tmp_dir, sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE])); 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])); #ifndef IS_SALAMANDER dir_check_defaults("custom.ini"); #endif } 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, const char * input) { STARTUPINFO si; HANDLE rd = NULL; bool ret; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); memset(&g_pi, 0, sizeof(g_pi)); if (input) { DWORD dummy; HANDLE wr; if (!CreatePipe(&rd, &wr, NULL, strlen(input))) return false; SetHandleInformation(rd, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); WriteFile(wr, input, strlen(input), &dummy, NULL); CloseHandle(wr); si.dwFlags |= STARTF_USESTDHANDLES; si.hStdInput = rd; si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); si.hStdError = GetStdHandle(STD_ERROR_HANDLE); } ret = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &g_pi); if (rd) CloseHandle(rd); return ret; } 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[512]; 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"}; size_t nbytes_cmd = 0; 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) { const char * template_lang = "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($input);\""; const char * template_nolang = "powershell.exe -NoProfile -WindowStyle Hidden -Command \"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.Rate = %s; $synth.Speak($input);\""; if (strlen(language) > 0) snprintf(cmd, sizeof(cmd), template_lang, language, speeds[speed-1]); else snprintf(cmd, sizeof(cmd), template_nolang, speeds[speed-1]); if (pi_set) terminate_win32_process(g_pi); pi_set = create_win32_process(cmd, speak_text); } #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); 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_env_get, /* env_get */ frontend_win32_init, /* 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, /* content_loaded */ frontend_win32_get_arch, /* 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, /* get_lakka_version */ NULL, /* set_screen_brightness */ 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, /* is_narrator_running */ accessibility_speak_windows, /* accessibility_speak */ #else NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ #endif "win32", /* ident */ NULL /* get_video_driver */ };