1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2011-2017 - Daniel De Matteis
3 * Copyright (C) 2016-2019 - Brad Parker
4 * Copyright (C) 2018-2019 - Andrés Suárez
5 *
6 * RetroArch is free software: you can redistribute it and/or modify it under the terms
7 * of the GNU General Public License as published by the Free Software Found-
8 * ation, either version 3 of the License, or (at your option) any later version.
9 *
10 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 * PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with RetroArch.
15 * If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <stdint.h>
19 #include <stddef.h>
20 #include <string.h>
21
22 #include <retro_miscellaneous.h>
23 #include <windows.h>
24 #if defined(_WIN32) && !defined(_XBOX)
25 #include <process.h>
26 #include <errno.h>
27 #endif
28
29 #include <boolean.h>
30 #include <compat/strl.h>
31 #include <dynamic/dylib.h>
32 #include <lists/file_list.h>
33 #include <file/file_path.h>
34 #include <string/stdstring.h>
35 #include <encodings/utf.h>
36 #include <features/features_cpu.h>
37
38 #ifdef HAVE_CONFIG_H
39 #include "../../config.h"
40 #endif
41
42 #ifdef HAVE_MENU
43 #include "../../menu/menu_driver.h"
44 #endif
45
46 #include "../frontend_driver.h"
47 #include "../../configuration.h"
48 #include "../../defaults.h"
49 #include "../../verbosity.h"
50 #include "../../ui/drivers/ui_win32.h"
51 #include "../../paths.h"
52 #include "../../msg_hash.h"
53 #include "platform_win32.h"
54
55 #include "../../verbosity.h"
56
57 /*
58 #ifdef HAVE_NVDA
59 #include "../../nvda_controller.h"
60 #endif
61 */
62
63 #ifdef HAVE_SAPI
64 #define COBJMACROS
65 #include <sapi.h>
66 #include <ole2.h>
67 #endif
68
69 #ifdef HAVE_SAPI
70 static ISpVoice* pVoice = NULL;
71 #endif
72 #ifdef HAVE_NVDA
73 static bool USE_POWERSHELL = false;
74 static bool USE_NVDA = true;
75 #else
76 static bool USE_POWERSHELL = true;
77 static bool USE_NVDA = false;
78 #endif
79 static bool USE_NVDA_BRAILLE = false;
80
81 #ifndef SM_SERVERR2
82 #define SM_SERVERR2 89
83 #endif
84
85 /* static public global variable */
86 VOID (WINAPI *DragAcceptFiles_func)(HWND, BOOL);
87
88 /* TODO/FIXME - static global variables */
89 static bool dwm_composition_disabled = false;
90 static bool console_needs_free = false;
91 static char win32_cpu_model_name[64] = {0};
92 static bool pi_set = false;
93 #ifdef HAVE_DYNAMIC
94 /* We only load this library once, so we let it be
95 * unloaded at application shutdown, since unloading
96 * it early seems to cause issues on some systems.
97 */
98 static dylib_t dwmlib;
99 static dylib_t shell32lib;
100 static dylib_t nvdalib;
101 #endif
102
103 /* Dynamic loading for Non-Visual Desktop Access support */
104 unsigned long (__stdcall *nvdaController_testIfRunning_func)(void);
105 unsigned long (__stdcall *nvdaController_cancelSpeech_func)(void);
106 unsigned long (__stdcall *nvdaController_brailleMessage_func)(wchar_t*);
107 unsigned long (__stdcall *nvdaController_speakText_func)(wchar_t*);
108
109 #if defined(HAVE_LANGEXTRA) && !defined(_XBOX)
110 #if (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) || !defined(_MSC_VER)
111 struct win32_lang_pair
112 {
113 unsigned short lang_ident;
114 enum retro_language lang;
115 };
116
117 /* https://docs.microsoft.com/en-us/windows/desktop/Intl/language-identifier-constants-and-strings */
118 const struct win32_lang_pair win32_lang_pairs[] =
119 {
120 /* array order MUST be kept, always largest ID first */
121 {0x7c04, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* neutral */
122 {0x1404, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* MO */
123 {0x1004, RETRO_LANGUAGE_CHINESE_SIMPLIFIED}, /* SG */
124 {0xC04, RETRO_LANGUAGE_CHINESE_TRADITIONAL}, /* HK/PRC */
125 {0x816, RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
126 {0x416, RETRO_LANGUAGE_PORTUGUESE_BRAZIL},
127 {0x2a, RETRO_LANGUAGE_VIETNAMESE},
128 {0x19, RETRO_LANGUAGE_RUSSIAN},
129 {0x16, RETRO_LANGUAGE_PORTUGUESE_PORTUGAL},
130 {0x15, RETRO_LANGUAGE_POLISH},
131 {0x13, RETRO_LANGUAGE_DUTCH},
132 {0x12, RETRO_LANGUAGE_KOREAN},
133 {0x11, RETRO_LANGUAGE_JAPANESE},
134 {0x10, RETRO_LANGUAGE_ITALIAN},
135 {0xc, RETRO_LANGUAGE_FRENCH},
136 {0xa, RETRO_LANGUAGE_SPANISH},
137 {0x9, RETRO_LANGUAGE_ENGLISH},
138 {0x8, RETRO_LANGUAGE_GREEK},
139 {0x7, RETRO_LANGUAGE_GERMAN},
140 {0x4, RETRO_LANGUAGE_CHINESE_SIMPLIFIED}, /* neutral */
141 {0x1, RETRO_LANGUAGE_ARABIC},
142 /* MS does not support Esperanto */
143 /*{0x0, RETRO_LANGUAGE_ESPERANTO},*/
144 };
145
win32_get_langid_from_retro_lang(enum retro_language lang)146 unsigned short win32_get_langid_from_retro_lang(enum retro_language lang)
147 {
148 unsigned i;
149
150 for (i = 0; i < sizeof(win32_lang_pairs) / sizeof(win32_lang_pairs[0]); i++)
151 {
152 if (win32_lang_pairs[i].lang == lang)
153 return win32_lang_pairs[i].lang_ident;
154 }
155
156 return 0x409; /* fallback to US English */
157 }
158
win32_get_retro_lang_from_langid(unsigned short langid)159 enum retro_language win32_get_retro_lang_from_langid(unsigned short langid)
160 {
161 unsigned i;
162
163 for (i = 0; i < sizeof(win32_lang_pairs) / sizeof(win32_lang_pairs[0]); i++)
164 {
165 if (win32_lang_pairs[i].lang_ident > 0x3ff)
166 {
167 if (langid == win32_lang_pairs[i].lang_ident)
168 return win32_lang_pairs[i].lang;
169 }
170 else
171 {
172 if ((langid & 0x3ff) == win32_lang_pairs[i].lang_ident)
173 return win32_lang_pairs[i].lang;
174 }
175 }
176
177 return RETRO_LANGUAGE_ENGLISH;
178 }
179 #endif
180 #else
win32_get_langid_from_retro_lang(enum retro_language lang)181 unsigned short win32_get_langid_from_retro_lang(enum retro_language lang)
182 {
183 return 0x409; /* fallback to US English */
184 }
185
win32_get_retro_lang_from_langid(unsigned short langid)186 enum retro_language win32_get_retro_lang_from_langid(unsigned short langid)
187 {
188 return RETRO_LANGUAGE_ENGLISH;
189 }
190 #endif
191
gfx_dwm_shutdown(void)192 static void gfx_dwm_shutdown(void)
193 {
194 #ifdef HAVE_DYNAMIC
195 if (dwmlib)
196 dylib_close(dwmlib);
197 if (shell32lib)
198 dylib_close(shell32lib);
199 dwmlib = NULL;
200 shell32lib = NULL;
201 #endif
202 }
203
gfx_init_dwm(void)204 static bool gfx_init_dwm(void)
205 {
206 HRESULT (WINAPI *mmcss)(BOOL);
207 static bool inited = false;
208
209 if (inited)
210 return true;
211
212 atexit(gfx_dwm_shutdown);
213
214 #ifdef HAVE_DYNAMIC
215 shell32lib = dylib_load("shell32.dll");
216 if (!shell32lib)
217 {
218 RARCH_WARN("Did not find shell32.dll.\n");
219 }
220
221 dwmlib = dylib_load("dwmapi.dll");
222 if (!dwmlib)
223 {
224 RARCH_WARN("Did not find dwmapi.dll.\n");
225 return false;
226 }
227
228 DragAcceptFiles_func =
229 (VOID (WINAPI*)(HWND, BOOL))dylib_proc(shell32lib, "DragAcceptFiles");
230
231 mmcss =
232 (HRESULT(WINAPI*)(BOOL))dylib_proc(dwmlib, "DwmEnableMMCSS");
233 #else
234 DragAcceptFiles_func = DragAcceptFiles;
235 #if 0
236 mmcss = DwmEnableMMCSS;
237 #endif
238 #endif
239
240 if (mmcss)
241 mmcss(TRUE);
242
243 inited = true;
244 return true;
245 }
246
gfx_set_dwm(void)247 static void gfx_set_dwm(void)
248 {
249 HRESULT ret;
250 HRESULT (WINAPI *composition_enable)(UINT);
251 settings_t *settings = config_get_ptr();
252 bool disable_composition = settings->bools.video_disable_composition;
253
254 if (!gfx_init_dwm())
255 return;
256
257 if (disable_composition == dwm_composition_disabled)
258 return;
259
260 #ifdef HAVE_DYNAMIC
261 composition_enable =
262 (HRESULT (WINAPI*)(UINT))dylib_proc(dwmlib, "DwmEnableComposition");
263 #endif
264
265 if (!composition_enable)
266 {
267 RARCH_ERR("Did not find DwmEnableComposition ...\n");
268 return;
269 }
270
271 ret = composition_enable(!disable_composition);
272 if (FAILED(ret))
273 RARCH_ERR("Failed to set composition state ...\n");
274 dwm_composition_disabled = disable_composition;
275 }
276
frontend_win32_get_os(char * s,size_t len,int * major,int * minor)277 static void frontend_win32_get_os(char *s, size_t len, int *major, int *minor)
278 {
279 char buildStr[11] = {0};
280 bool server = false;
281 const char *arch = "";
282
283 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
284 /* Windows 2000 and later */
285 SYSTEM_INFO si = {{0}};
286 OSVERSIONINFOEX vi = {0};
287 vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
288
289 GetSystemInfo(&si);
290
291 /* Available from NT 3.5 and Win95 */
292 GetVersionEx((OSVERSIONINFO*)&vi);
293
294 server = vi.wProductType != VER_NT_WORKSTATION;
295
296 switch (si.wProcessorArchitecture)
297 {
298 case PROCESSOR_ARCHITECTURE_AMD64:
299 arch = "x64";
300 break;
301 case PROCESSOR_ARCHITECTURE_INTEL:
302 arch = "x86";
303 break;
304 case PROCESSOR_ARCHITECTURE_ARM:
305 arch = "ARM";
306 break;
307 default:
308 break;
309 }
310 #else
311 OSVERSIONINFO vi = {0};
312 vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
313
314 /* Available from NT 3.5 and Win95 */
315 GetVersionEx(&vi);
316 #endif
317
318 if (major)
319 *major = vi.dwMajorVersion;
320
321 if (minor)
322 *minor = vi.dwMinorVersion;
323
324 if (vi.dwMajorVersion == 4 && vi.dwMinorVersion == 0)
325 snprintf(buildStr, sizeof(buildStr), "%lu", (DWORD)(LOWORD(vi.dwBuildNumber))); /* Windows 95 build number is in the low-order word only */
326 else
327 snprintf(buildStr, sizeof(buildStr), "%lu", vi.dwBuildNumber);
328
329 switch (vi.dwMajorVersion)
330 {
331 case 10:
332 if (server)
333 strcpy_literal(s, "Windows Server 2016");
334 else
335 strcpy_literal(s, "Windows 10");
336 break;
337 case 6:
338 switch (vi.dwMinorVersion)
339 {
340 case 3:
341 if (server)
342 strcpy_literal(s, "Windows Server 2012 R2");
343 else
344 strcpy_literal(s, "Windows 8.1");
345 break;
346 case 2:
347 if (server)
348 strcpy_literal(s, "Windows Server 2012");
349 else
350 strcpy_literal(s, "Windows 8");
351 break;
352 case 1:
353 if (server)
354 strcpy_literal(s, "Windows Server 2008 R2");
355 else
356 strcpy_literal(s, "Windows 7");
357 break;
358 case 0:
359 if (server)
360 strcpy_literal(s, "Windows Server 2008");
361 else
362 strcpy_literal(s, "Windows Vista");
363 break;
364 default:
365 break;
366 }
367 break;
368 case 5:
369 switch (vi.dwMinorVersion)
370 {
371 case 2:
372 if (server)
373 {
374 strcpy_literal(s, "Windows Server 2003");
375 if (GetSystemMetrics(SM_SERVERR2))
376 strlcat(s, " R2", len);
377 }
378 else
379 {
380 /* Yes, XP Pro x64 is a higher version number than XP x86 */
381 if (string_is_equal(arch, "x64"))
382 strcpy_literal(s, "Windows XP");
383 }
384 break;
385 case 1:
386 strcpy_literal(s, "Windows XP");
387 break;
388 case 0:
389 strcpy_literal(s, "Windows 2000");
390 break;
391 }
392 break;
393 case 4:
394 switch (vi.dwMinorVersion)
395 {
396 case 0:
397 if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
398 strcpy_literal(s, "Windows 95");
399 else if (vi.dwPlatformId == VER_PLATFORM_WIN32_NT)
400 strcpy_literal(s, "Windows NT 4.0");
401 else
402 strcpy_literal(s, "Unknown");
403 break;
404 case 90:
405 strcpy_literal(s, "Windows ME");
406 break;
407 case 10:
408 strcpy_literal(s, "Windows 98");
409 break;
410 }
411 break;
412 default:
413 snprintf(s, len, "Windows %i.%i", *major, *minor);
414 break;
415 }
416
417 if (!string_is_empty(arch))
418 {
419 strlcat(s, " ", len);
420 strlcat(s, arch, len);
421 }
422
423 strlcat(s, " Build ", len);
424 strlcat(s, buildStr, len);
425
426 if (!string_is_empty(vi.szCSDVersion))
427 {
428 strlcat(s, " ", len);
429 strlcat(s, vi.szCSDVersion, len);
430 }
431
432 }
433
frontend_win32_init(void * data)434 static void frontend_win32_init(void *data)
435 {
436 typedef BOOL (WINAPI *isProcessDPIAwareProc)();
437 typedef BOOL (WINAPI *setProcessDPIAwareProc)();
438 #ifdef HAVE_DYNAMIC
439 HMODULE handle =
440 GetModuleHandle("User32.dll");
441 isProcessDPIAwareProc isDPIAwareProc =
442 (isProcessDPIAwareProc)dylib_proc(handle, "IsProcessDPIAware");
443 setProcessDPIAwareProc setDPIAwareProc =
444 (setProcessDPIAwareProc)dylib_proc(handle, "SetProcessDPIAware");
445 #else
446 isProcessDPIAwareProc isDPIAwareProc = IsProcessDPIAware;
447 setProcessDPIAwareProc setDPIAwareProc = SetProcessDPIAware;
448 #endif
449
450 if (isDPIAwareProc)
451 if (!isDPIAwareProc())
452 if (setDPIAwareProc)
453 setDPIAwareProc();
454 }
455
456
457 #ifdef HAVE_NVDA
init_nvda(void)458 static void init_nvda(void)
459 {
460 #ifdef HAVE_DYNAMIC
461 if (USE_NVDA && !nvdalib)
462 {
463 nvdalib = dylib_load("nvdaControllerClient64.dll");
464 if (!nvdalib)
465 {
466 USE_NVDA = false;
467 USE_POWERSHELL = true;
468 }
469 else
470 {
471 nvdaController_testIfRunning_func = ( unsigned long (__stdcall*)(void))dylib_proc(nvdalib, "nvdaController_testIfRunning");
472 nvdaController_cancelSpeech_func = (unsigned long(__stdcall *)(void))dylib_proc(nvdalib, "nvdaController_cancelSpeech");
473 nvdaController_brailleMessage_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvdalib, "nvdaController_brailleMessage");
474 nvdaController_speakText_func = (unsigned long(__stdcall *)(wchar_t*))dylib_proc(nvdalib, "nvdaController_speakText");
475
476 }
477 }
478 #else
479 USE_NVDA = false;
480 USE_POWERSHELL = true;
481 #endif
482 }
483 #endif
484
frontend_win32_get_powerstate(int * seconds,int * percent)485 enum frontend_powerstate frontend_win32_get_powerstate(int *seconds, int *percent)
486 {
487 SYSTEM_POWER_STATUS status;
488 enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE;
489
490 if (!GetSystemPowerStatus(&status))
491 return ret;
492
493 if (status.BatteryFlag == 0xFF)
494 ret = FRONTEND_POWERSTATE_NONE;
495 else if (status.BatteryFlag & (1 << 7))
496 ret = FRONTEND_POWERSTATE_NO_SOURCE;
497 else if (status.BatteryFlag & (1 << 3))
498 ret = FRONTEND_POWERSTATE_CHARGING;
499 else if (status.ACLineStatus == 1)
500 ret = FRONTEND_POWERSTATE_CHARGED;
501 else
502 ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
503
504 *percent = (int)status.BatteryLifePercent;
505 *seconds = (int)status.BatteryLifeTime;
506
507 #ifdef _WIN32
508 if (*percent == 255)
509 *percent = 0;
510 #endif
511
512 return ret;
513 }
514
frontend_win32_get_arch(void)515 enum frontend_architecture frontend_win32_get_arch(void)
516 {
517 #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500
518 /* Windows 2000 and later */
519 SYSTEM_INFO si = {{0}};
520
521 GetSystemInfo(&si);
522
523 switch (si.wProcessorArchitecture)
524 {
525 case PROCESSOR_ARCHITECTURE_AMD64:
526 return FRONTEND_ARCH_X86_64;
527 break;
528 case PROCESSOR_ARCHITECTURE_INTEL:
529 return FRONTEND_ARCH_X86;
530 break;
531 case PROCESSOR_ARCHITECTURE_ARM:
532 return FRONTEND_ARCH_ARM;
533 break;
534 default:
535 break;
536 }
537 #endif
538
539 return FRONTEND_ARCH_NONE;
540 }
541
frontend_win32_parse_drive_list(void * data,bool load_content)542 static int frontend_win32_parse_drive_list(void *data, bool load_content)
543 {
544 #ifdef HAVE_MENU
545 file_list_t *list = (file_list_t*)data;
546 enum msg_hash_enums enum_idx = load_content ?
547 MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR :
548 MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
549 size_t i = 0;
550 unsigned drives = GetLogicalDrives();
551 char drive[] = " :\\";
552
553 for (i = 0; i < 32; i++)
554 {
555 drive[0] = 'A' + i;
556 if (drives & (1 << i))
557 menu_entries_append_enum(list,
558 drive,
559 msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
560 enum_idx,
561 FILE_TYPE_DIRECTORY, 0, 0);
562 }
563 #endif
564
565 return 0;
566 }
567
frontend_win32_env_get(int * argc,char * argv[],void * args,void * params_data)568 static void frontend_win32_env_get(int *argc, char *argv[],
569 void *args, void *params_data)
570 {
571 const char *tmp_dir = getenv("TMP");
572 if (!string_is_empty(tmp_dir))
573 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CACHE],
574 tmp_dir, sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE]));
575
576 gfx_set_dwm();
577
578 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_ASSETS],
579 ":\\assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
580 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER],
581 ":\\filters\\audio", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER]));
582 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER],
583 ":\\filters\\video", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER]));
584 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CHEATS],
585 ":\\cheats", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS]));
586 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_DATABASE],
587 ":\\database\\rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE]));
588 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CURSOR],
589 ":\\database\\cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR]));
590 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_PLAYLIST],
591 ":\\playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
592 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG],
593 ":\\config\\record", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG]));
594 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT],
595 ":\\recordings", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT]));
596 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG],
597 ":\\config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
598 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_REMAP],
599 ":\\config\\remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
600 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_WALLPAPERS],
601 ":\\assets\\wallpapers", sizeof(g_defaults.dirs[DEFAULT_DIR_WALLPAPERS]));
602 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS],
603 ":\\thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS]));
604 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_OVERLAY],
605 ":\\overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY]));
606 #ifdef HAVE_VIDEO_LAYOUT
607 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT],
608 ":\\layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT]));
609 #endif
610 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE],
611 ":\\cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
612 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE_INFO],
613 ":\\info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
614 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG],
615 ":\\autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG]));
616 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SHADER],
617 ":\\shaders", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
618 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS],
619 ":\\downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
620 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT],
621 ":\\screenshots", sizeof(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT]));
622 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SRAM],
623 ":\\saves", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM]));
624 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SAVESTATE],
625 ":\\states", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE]));
626 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_SYSTEM],
627 ":\\system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM]));
628 fill_pathname_expand_special(g_defaults.dirs[DEFAULT_DIR_LOGS],
629 ":\\logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS]));
630
631 #ifndef IS_SALAMANDER
632 dir_check_defaults("custom.ini");
633 #endif
634 }
635
frontend_win32_get_total_mem(void)636 static uint64_t frontend_win32_get_total_mem(void)
637 {
638 /* OSes below 2000 don't have the Ex version,
639 * and non-Ex cannot work with >4GB RAM */
640 #if _WIN32_WINNT >= 0x0500
641 MEMORYSTATUSEX mem_info;
642 mem_info.dwLength = sizeof(MEMORYSTATUSEX);
643 GlobalMemoryStatusEx(&mem_info);
644 return mem_info.ullTotalPhys;
645 #else
646 MEMORYSTATUS mem_info;
647 mem_info.dwLength = sizeof(MEMORYSTATUS);
648 GlobalMemoryStatus(&mem_info);
649 return mem_info.dwTotalPhys;
650 #endif
651 }
652
frontend_win32_get_free_mem(void)653 static uint64_t frontend_win32_get_free_mem(void)
654 {
655 /* OSes below 2000 don't have the Ex version,
656 * and non-Ex cannot work with >4GB RAM */
657 #if _WIN32_WINNT >= 0x0500
658 MEMORYSTATUSEX mem_info;
659 mem_info.dwLength = sizeof(MEMORYSTATUSEX);
660 GlobalMemoryStatusEx(&mem_info);
661 return mem_info.ullAvailPhys;
662 #else
663 MEMORYSTATUS mem_info;
664 mem_info.dwLength = sizeof(MEMORYSTATUS);
665 GlobalMemoryStatus(&mem_info);
666 return mem_info.dwAvailPhys;
667 #endif
668 }
669
frontend_win32_attach_console(void)670 static void frontend_win32_attach_console(void)
671 {
672 #ifdef _WIN32
673 #ifdef _WIN32_WINNT_WINXP
674 /* msys will start the process with FILE_TYPE_PIPE connected.
675 * cmd will start the process with FILE_TYPE_UNKNOWN connected
676 * (since this is subsystem windows application
677 * ... UNLESS stdout/stderr were redirected (then FILE_TYPE_DISK
678 * will be connected most likely)
679 * explorer will start the process with NOTHING connected.
680 *
681 * Now, let's not reconnect anything that's already connected.
682 * If any are disconnected, open a console, and connect to them.
683 * In case we're launched from msys or cmd, try attaching to the
684 * parent process console first.
685 *
686 * Take care to leave a record of what we did, so we can
687 * undo it precisely.
688 */
689
690 bool need_stdout = (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE))
691 == FILE_TYPE_UNKNOWN);
692 bool need_stderr = (GetFileType(GetStdHandle(STD_ERROR_HANDLE))
693 == FILE_TYPE_UNKNOWN);
694
695 if (need_stdout || need_stderr)
696 {
697 if (!AttachConsole( ATTACH_PARENT_PROCESS))
698 AllocConsole();
699
700 SetConsoleTitle("Log Console");
701
702 if (need_stdout)
703 freopen( "CONOUT$", "w", stdout );
704 if (need_stderr)
705 freopen( "CONOUT$", "w", stderr );
706
707 console_needs_free = true;
708 }
709
710 #endif
711 #endif
712 }
713
frontend_win32_detach_console(void)714 static void frontend_win32_detach_console(void)
715 {
716 #if defined(_WIN32) && !defined(_XBOX)
717 #ifdef _WIN32_WINNT_WINXP
718 if (console_needs_free)
719 {
720 /* we don't reconnect stdout/stderr to anything here,
721 * because by definition, they weren't connected to
722 * anything in the first place. */
723 FreeConsole();
724 console_needs_free = false;
725 }
726 #endif
727 #endif
728 }
729
frontend_win32_get_cpu_model_name(void)730 static const char* frontend_win32_get_cpu_model_name(void)
731 {
732 #ifdef ANDROID
733 return NULL;
734 #else
735 cpu_features_get_model_name(win32_cpu_model_name, sizeof(win32_cpu_model_name));
736 return win32_cpu_model_name;
737 #endif
738 }
739
frontend_win32_get_user_language(void)740 enum retro_language frontend_win32_get_user_language(void)
741 {
742 enum retro_language lang = RETRO_LANGUAGE_ENGLISH;
743 #if defined(HAVE_LANGEXTRA) && !defined(_XBOX)
744 #if (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) || !defined(_MSC_VER)
745 LANGID langid = GetUserDefaultUILanguage();
746 lang = win32_get_retro_lang_from_langid(langid);
747 #endif
748 #endif
749 return lang;
750 }
751
752 #if defined(_WIN32) && !defined(_XBOX)
753 enum frontend_fork win32_fork_mode;
754
frontend_win32_respawn(char * s,size_t len,char * args)755 static void frontend_win32_respawn(char *s, size_t len, char *args)
756 {
757 STARTUPINFO si;
758 PROCESS_INFORMATION pi;
759 char executable_path[PATH_MAX_LENGTH] = {0};
760
761 if (win32_fork_mode != FRONTEND_FORK_RESTART)
762 return;
763
764 fill_pathname_application_path(executable_path,
765 sizeof(executable_path));
766 path_set(RARCH_PATH_CORE, executable_path);
767 RARCH_LOG("Restarting RetroArch with commandline: %s and %s\n",
768 executable_path, args);
769
770 memset(&si, 0, sizeof(si));
771 si.cb = sizeof(si);
772 memset(&pi, 0, sizeof(pi));
773
774 if (!CreateProcess( executable_path, args,
775 NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
776 {
777 RARCH_LOG("Failed to restart RetroArch\n");
778 }
779 }
780
frontend_win32_set_fork(enum frontend_fork fork_mode)781 static bool frontend_win32_set_fork(enum frontend_fork fork_mode)
782 {
783 switch (fork_mode)
784 {
785 case FRONTEND_FORK_CORE:
786 break;
787 case FRONTEND_FORK_CORE_WITH_ARGS:
788 break;
789 case FRONTEND_FORK_RESTART:
790 command_event(CMD_EVENT_QUIT, NULL);
791 break;
792 case FRONTEND_FORK_NONE:
793 default:
794 break;
795 }
796 win32_fork_mode = fork_mode;
797 return true;
798 }
799 #endif
800
801 #if defined(_WIN32) && !defined(_XBOX)
accessibility_win_language_id(const char * language)802 static const char *accessibility_win_language_id(const char* language)
803 {
804 if (string_is_equal(language,"en"))
805 return "409";
806 else if (string_is_equal(language,"it"))
807 return "410";
808 else if (string_is_equal(language,"sv"))
809 return "041d";
810 else if (string_is_equal(language,"fr"))
811 return "040c";
812 else if (string_is_equal(language,"de"))
813 return "407";
814 else if (string_is_equal(language,"he"))
815 return "040d";
816 else if (string_is_equal(language,"id"))
817 return "421";
818 else if (string_is_equal(language,"es"))
819 return "040a";
820 else if (string_is_equal(language,"nl"))
821 return "413";
822 else if (string_is_equal(language,"ro"))
823 return "418";
824 else if (string_is_equal(language,"pt_pt"))
825 return "816";
826 else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt"))
827 return "416";
828 else if (string_is_equal(language,"th"))
829 return "041e";
830 else if (string_is_equal(language,"ja"))
831 return "411";
832 else if (string_is_equal(language,"sk"))
833 return "041b";
834 else if (string_is_equal(language,"hi"))
835 return "439";
836 else if (string_is_equal(language,"ar"))
837 return "401";
838 else if (string_is_equal(language,"hu"))
839 return "040e";
840 else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh"))
841 return "804";
842 else if (string_is_equal(language,"el"))
843 return "408";
844 else if (string_is_equal(language,"ru"))
845 return "419";
846 else if (string_is_equal(language,"nb"))
847 return "414";
848 else if (string_is_equal(language,"da"))
849 return "406";
850 else if (string_is_equal(language,"fi"))
851 return "040b";
852 else if (string_is_equal(language,"zh_hk"))
853 return "0c04";
854 else if (string_is_equal(language,"zh_cn"))
855 return "804";
856 else if (string_is_equal(language,"tr"))
857 return "041f";
858 else if (string_is_equal(language,"ko"))
859 return "412";
860 else if (string_is_equal(language,"pl"))
861 return "415";
862 else if (string_is_equal(language,"cs"))
863 return "405";
864 else
865 return "";
866
867
868 }
869
accessibility_win_language_code(const char * language)870 static const char *accessibility_win_language_code(const char* language)
871 {
872 if (string_is_equal(language,"en"))
873 return "Microsoft David Desktop";
874 else if (string_is_equal(language,"it"))
875 return "Microsoft Cosimo Desktop";
876 else if (string_is_equal(language,"sv"))
877 return "Microsoft Bengt Desktop";
878 else if (string_is_equal(language,"fr"))
879 return "Microsoft Paul Desktop";
880 else if (string_is_equal(language,"de"))
881 return "Microsoft Stefan Desktop";
882 else if (string_is_equal(language,"he"))
883 return "Microsoft Asaf Desktop";
884 else if (string_is_equal(language,"id"))
885 return "Microsoft Andika Desktop";
886 else if (string_is_equal(language,"es"))
887 return "Microsoft Pablo Desktop";
888 else if (string_is_equal(language,"nl"))
889 return "Microsoft Frank Desktop";
890 else if (string_is_equal(language,"ro"))
891 return "Microsoft Andrei Desktop";
892 else if (string_is_equal(language,"pt_pt"))
893 return "Microsoft Helia Desktop";
894 else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt"))
895 return "Microsoft Daniel Desktop";
896 else if (string_is_equal(language,"th"))
897 return "Microsoft Pattara Desktop";
898 else if (string_is_equal(language,"ja"))
899 return "Microsoft Ichiro Desktop";
900 else if (string_is_equal(language,"sk"))
901 return "Microsoft Filip Desktop";
902 else if (string_is_equal(language,"hi"))
903 return "Microsoft Hemant Desktop";
904 else if (string_is_equal(language,"ar"))
905 return "Microsoft Naayf Desktop";
906 else if (string_is_equal(language,"hu"))
907 return "Microsoft Szabolcs Desktop";
908 else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh"))
909 return "Microsoft Zhiwei Desktop";
910 else if (string_is_equal(language,"el"))
911 return "Microsoft Stefanos Desktop";
912 else if (string_is_equal(language,"ru"))
913 return "Microsoft Pavel Desktop";
914 else if (string_is_equal(language,"nb"))
915 return "Microsoft Jon Desktop";
916 else if (string_is_equal(language,"da"))
917 return "Microsoft Helle Desktop";
918 else if (string_is_equal(language,"fi"))
919 return "Microsoft Heidi Desktop";
920 else if (string_is_equal(language,"zh_hk"))
921 return "Microsoft Danny Desktop";
922 else if (string_is_equal(language,"zh_cn"))
923 return "Microsoft Kangkang Desktop";
924 else if (string_is_equal(language,"tr"))
925 return "Microsoft Tolga Desktop";
926 else if (string_is_equal(language,"ko"))
927 return "Microsoft Heami Desktop";
928 else if (string_is_equal(language,"pl"))
929 return "Microsoft Adam Desktop";
930 else if (string_is_equal(language,"cs"))
931 return "Microsoft Jakub Desktop";
932 else
933 return "";
934 }
935
terminate_win32_process(PROCESS_INFORMATION pi)936 static bool terminate_win32_process(PROCESS_INFORMATION pi)
937 {
938 TerminateProcess(pi.hProcess,0);
939 CloseHandle(pi.hProcess);
940 CloseHandle(pi.hThread);
941 return true;
942 }
943
944 static PROCESS_INFORMATION g_pi;
945
create_win32_process(char * cmd,const char * input)946 static bool create_win32_process(char* cmd, const char * input)
947 {
948 STARTUPINFO si;
949 HANDLE rd = NULL;
950 bool ret;
951 memset(&si, 0, sizeof(si));
952 si.cb = sizeof(si);
953 memset(&g_pi, 0, sizeof(g_pi));
954
955 if (input)
956 {
957 DWORD dummy;
958 HANDLE wr;
959 if (!CreatePipe(&rd, &wr, NULL, strlen(input))) return false;
960
961 SetHandleInformation(rd, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
962
963 WriteFile(wr, input, strlen(input), &dummy, NULL);
964 CloseHandle(wr);
965
966 si.dwFlags |= STARTF_USESTDHANDLES;
967 si.hStdInput = rd;
968 si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
969 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
970 }
971
972 ret = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW,
973 NULL, NULL, &si, &g_pi);
974 if (rd) CloseHandle(rd);
975 return ret;
976 }
977
is_narrator_running_windows(void)978 static bool is_narrator_running_windows(void)
979 {
980 DWORD status = 0;
981 #ifdef HAVE_NVDA
982 init_nvda();
983 #endif
984
985 if (USE_POWERSHELL)
986 {
987 if (pi_set == false)
988 return false;
989 if (GetExitCodeProcess(g_pi.hProcess, &status))
990 {
991 if (status == STILL_ACTIVE)
992 return true;
993 }
994 return false;
995 }
996 #ifdef HAVE_NVDA
997 else if (USE_NVDA)
998 {
999 long res;
1000 res = nvdaController_testIfRunning_func();
1001
1002 if (res != 0)
1003 {
1004 /* The running nvda service wasn't found, so revert
1005 back to the powershell method
1006 */
1007 RARCH_LOG("Error communicating with NVDA\n");
1008 USE_POWERSHELL = true;
1009 USE_NVDA = false;
1010 return false;
1011 }
1012 return false;
1013 }
1014 #endif
1015 #ifdef HAVE_SAPI
1016 else
1017 {
1018 SPVOICESTATUS pStatus;
1019 if (pVoice)
1020 {
1021 ISpVoice_GetStatus(pVoice, &pStatus, NULL);
1022 if (pStatus.dwRunningState == SPRS_IS_SPEAKING)
1023 return true;
1024 }
1025 }
1026 #endif
1027 return false;
1028 }
1029
accessibility_speak_windows(int speed,const char * speak_text,int priority)1030 static bool accessibility_speak_windows(int speed,
1031 const char* speak_text, int priority)
1032 {
1033 char cmd[512];
1034 const char *voice = get_user_language_iso639_1(true);
1035 const char *language = accessibility_win_language_code(voice);
1036 const char *langid = accessibility_win_language_id(voice);
1037 bool res = false;
1038 const char* speeds[10] = {"-10", "-7.5", "-5", "-2.5", "0", "2", "4", "6", "8", "10"};
1039 size_t nbytes_cmd = 0;
1040 if (speed < 1)
1041 speed = 1;
1042 else if (speed > 10)
1043 speed = 10;
1044
1045 if (priority < 10)
1046 {
1047 if (is_narrator_running_windows())
1048 return true;
1049
1050 }
1051 #ifdef HAVE_NVDA
1052 init_nvda();
1053 #endif
1054
1055 if (USE_POWERSHELL)
1056 {
1057 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);\"";
1058 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);\"";
1059 if (strlen(language) > 0)
1060 snprintf(cmd, sizeof(cmd), template_lang, language, speeds[speed-1]);
1061 else
1062 snprintf(cmd, sizeof(cmd), template_nolang, speeds[speed-1]);
1063
1064 if (pi_set)
1065 terminate_win32_process(g_pi);
1066 pi_set = create_win32_process(cmd, speak_text);
1067 }
1068 #ifdef HAVE_NVDA
1069 else if (USE_NVDA)
1070 {
1071 wchar_t *wc = utf8_to_utf16_string_alloc(speak_text);
1072 long res = nvdaController_testIfRunning_func();
1073
1074 if (!wc || res != 0)
1075 {
1076 RARCH_LOG("Error communicating with NVDA\n");
1077 if (wc)
1078 free(wc);
1079 return false;
1080 }
1081
1082 nvdaController_cancelSpeech_func();
1083
1084 if (USE_NVDA_BRAILLE)
1085 nvdaController_brailleMessage_func(wc);
1086 else
1087 nvdaController_speakText_func(wc);
1088 free(wc);
1089 }
1090 #endif
1091 #ifdef HAVE_SAPI
1092 else
1093 {
1094 HRESULT hr;
1095 /* stop the old voice if running */
1096 if (pVoice)
1097 {
1098 CoUninitialize();
1099 ISpVoice_Release(pVoice);
1100 }
1101 pVoice = NULL;
1102
1103 /* Play the new voice */
1104 if (FAILED(CoInitialize(NULL)))
1105 return NULL;
1106
1107 hr = CoCreateInstance(&CLSID_SpVoice, NULL,
1108 CLSCTX_ALL, &IID_ISpVoice, (void **)&pVoice);
1109
1110 if (SUCCEEDED(hr))
1111 {
1112 wchar_t *wc = utf8_to_utf16_string_alloc(speak_text);
1113 if (!wc)
1114 return false;
1115 hr = ISpVoice_Speak(pVoice, wc, SPF_ASYNC /*SVSFlagsAsync*/, NULL);
1116 free(wc);
1117 }
1118 }
1119 #endif
1120
1121 return true;
1122 }
1123 #endif
1124
1125 frontend_ctx_driver_t frontend_ctx_win32 = {
1126 frontend_win32_env_get, /* env_get */
1127 frontend_win32_init, /* init */
1128 NULL, /* deinit */
1129 #if defined(_WIN32) && !defined(_XBOX)
1130 frontend_win32_respawn, /* exitspawn */
1131 #else
1132 NULL, /* exitspawn */
1133 #endif
1134 NULL, /* process_args */
1135 NULL, /* exec */
1136 #if defined(_WIN32) && !defined(_XBOX)
1137 frontend_win32_set_fork, /* set_fork */
1138 #else
1139 NULL, /* set_fork */
1140 #endif
1141 NULL, /* shutdown */
1142 NULL, /* get_name */
1143 frontend_win32_get_os,
1144 NULL, /* get_rating */
1145 NULL, /* content_loaded */
1146 frontend_win32_get_arch, /* get_architecture */
1147 frontend_win32_get_powerstate,
1148 frontend_win32_parse_drive_list,
1149 frontend_win32_get_total_mem,
1150 frontend_win32_get_free_mem,
1151 NULL, /* install_signal_handler */
1152 NULL, /* get_sighandler_state */
1153 NULL, /* set_sighandler_state */
1154 NULL, /* destroy_sighandler_state */
1155 frontend_win32_attach_console, /* attach_console */
1156 frontend_win32_detach_console, /* detach_console */
1157 NULL, /* get_lakka_version */
1158 NULL, /* set_screen_brightness */
1159 NULL, /* watch_path_for_changes */
1160 NULL, /* check_for_path_changes */
1161 NULL, /* set_sustained_performance_mode */
1162 frontend_win32_get_cpu_model_name,
1163 frontend_win32_get_user_language,
1164 #if defined(_WIN32) && !defined(_XBOX)
1165 is_narrator_running_windows, /* is_narrator_running */
1166 accessibility_speak_windows, /* accessibility_speak */
1167 #else
1168 NULL, /* is_narrator_running */
1169 NULL, /* accessibility_speak */
1170 #endif
1171 "win32", /* ident */
1172 NULL /* get_video_driver */
1173 };
1174