1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2018 - Krzysztof Haładyn
3  *
4  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
5  *  of the GNU General Public License as published by the Free Software Found-
6  *  ation, either version 3 of the License, or (at your option) any later version.
7  *
8  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
10  *  PURPOSE.  See the GNU General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License along with RetroArch.
13  *  If not, see <http://www.gnu.org/licenses/>.
14  */
15 
16 #include <ppltasks.h>
17 #include <collection.h>
18 #include <windows.devices.enumeration.h>
19 
20 #include <encodings/utf.h>
21 #include <string/stdstring.h>
22 #include <lists/string_list.h>
23 #include <queues/task_queue.h>
24 #include <retro_timers.h>
25 
26 #include "configuration.h"
27 #include "paths.h"
28 
29 #include "uwp_main.h"
30 #include "../retroarch.h"
31 #include "../frontend/frontend.h"
32 #include "../input/input_keymaps.h"
33 #include "../verbosity.h"
34 #include "uwp_func.h"
35 #include "uwp_async.h"
36 
37 using namespace RetroArchUWP;
38 
39 using namespace concurrency;
40 using namespace Windows::ApplicationModel;
41 using namespace Windows::ApplicationModel::Core;
42 using namespace Windows::ApplicationModel::Activation;
43 using namespace Windows::UI::Core;
44 using namespace Windows::UI::Input;
45 using namespace Windows::UI::ViewManagement;
46 using namespace Windows::Devices::Input;
47 using namespace Windows::System;
48 using namespace Windows::System::Profile;
49 using namespace Windows::Foundation;
50 using namespace Windows::Foundation::Collections;
51 using namespace Windows::Graphics::Display;
52 using namespace Windows::Devices::Enumeration;
53 
54 char uwp_dir_install[PATH_MAX_LENGTH] = { 0 };
55 char uwp_dir_data[PATH_MAX_LENGTH]    = { 0 };
56 char uwp_device_family[128]           = { 0 };
57 char win32_cpu_model_name[128]        = { 0 };
58 
59 // Some keys are unavailable in the VirtualKey enum (wtf) but the old-style constants work
60 const struct rarch_key_map rarch_key_map_uwp[] = {
61    { (unsigned int)VirtualKey::Back, RETROK_BACKSPACE },
62    { (unsigned int)VirtualKey::Tab, RETROK_TAB },
63    { (unsigned int)VirtualKey::Clear, RETROK_CLEAR },
64    { (unsigned int)VirtualKey::Enter, RETROK_RETURN },
65    { (unsigned int)VirtualKey::Pause, RETROK_PAUSE },
66    { (unsigned int)VirtualKey::Escape, RETROK_ESCAPE },
67    { (unsigned int)VirtualKey::ModeChange, RETROK_MODE },
68    { (unsigned int)VirtualKey::Space, RETROK_SPACE },
69    { (unsigned int)VirtualKey::PageUp, RETROK_PAGEUP },
70    { (unsigned int)VirtualKey::PageDown, RETROK_PAGEDOWN },
71    { (unsigned int)VirtualKey::End, RETROK_END },
72    { (unsigned int)VirtualKey::Home, RETROK_HOME },
73    { (unsigned int)VirtualKey::Left, RETROK_LEFT },
74    { (unsigned int)VirtualKey::Up, RETROK_UP },
75    { (unsigned int)VirtualKey::Right, RETROK_RIGHT },
76    { (unsigned int)VirtualKey::Down, RETROK_DOWN },
77    { (unsigned int)VirtualKey::Print, RETROK_PRINT },
78    { (unsigned int)VirtualKey::Insert, RETROK_INSERT },
79    { (unsigned int)VirtualKey::Delete, RETROK_DELETE },
80    { (unsigned int)VirtualKey::Help, RETROK_HELP },
81    { (unsigned int)VirtualKey::Number0, RETROK_0 },
82    { (unsigned int)VirtualKey::Number1, RETROK_1 },
83    { (unsigned int)VirtualKey::Number2, RETROK_2 },
84    { (unsigned int)VirtualKey::Number3, RETROK_3 },
85    { (unsigned int)VirtualKey::Number4, RETROK_4 },
86    { (unsigned int)VirtualKey::Number5, RETROK_5 },
87    { (unsigned int)VirtualKey::Number6, RETROK_6 },
88    { (unsigned int)VirtualKey::Number7, RETROK_7 },
89    { (unsigned int)VirtualKey::Number8, RETROK_8 },
90    { (unsigned int)VirtualKey::Number9, RETROK_9 },
91    { (unsigned int)VirtualKey::A, RETROK_a },
92    { (unsigned int)VirtualKey::B, RETROK_b },
93    { (unsigned int)VirtualKey::C, RETROK_c },
94    { (unsigned int)VirtualKey::D, RETROK_d },
95    { (unsigned int)VirtualKey::E, RETROK_e },
96    { (unsigned int)VirtualKey::F, RETROK_f },
97    { (unsigned int)VirtualKey::G, RETROK_g },
98    { (unsigned int)VirtualKey::H, RETROK_h },
99    { (unsigned int)VirtualKey::I, RETROK_i },
100    { (unsigned int)VirtualKey::J, RETROK_j },
101    { (unsigned int)VirtualKey::K, RETROK_k },
102    { (unsigned int)VirtualKey::L, RETROK_l },
103    { (unsigned int)VirtualKey::M, RETROK_m },
104    { (unsigned int)VirtualKey::N, RETROK_n },
105    { (unsigned int)VirtualKey::O, RETROK_o },
106    { (unsigned int)VirtualKey::P, RETROK_p },
107    { (unsigned int)VirtualKey::Q, RETROK_q },
108    { (unsigned int)VirtualKey::R, RETROK_r },
109    { (unsigned int)VirtualKey::S, RETROK_s },
110    { (unsigned int)VirtualKey::T, RETROK_t },
111    { (unsigned int)VirtualKey::U, RETROK_u },
112    { (unsigned int)VirtualKey::V, RETROK_v },
113    { (unsigned int)VirtualKey::W, RETROK_w },
114    { (unsigned int)VirtualKey::X, RETROK_x },
115    { (unsigned int)VirtualKey::Y, RETROK_y },
116    { (unsigned int)VirtualKey::Z, RETROK_z },
117    { (unsigned int)VirtualKey::LeftWindows, RETROK_LSUPER },
118    { (unsigned int)VirtualKey::RightWindows, RETROK_RSUPER },
119    { (unsigned int)VirtualKey::Application, RETROK_MENU },
120    { (unsigned int)VirtualKey::NumberPad0, RETROK_KP0 },
121    { (unsigned int)VirtualKey::NumberPad1, RETROK_KP1 },
122    { (unsigned int)VirtualKey::NumberPad2, RETROK_KP2 },
123    { (unsigned int)VirtualKey::NumberPad3, RETROK_KP3 },
124    { (unsigned int)VirtualKey::NumberPad4, RETROK_KP4 },
125    { (unsigned int)VirtualKey::NumberPad5, RETROK_KP5 },
126    { (unsigned int)VirtualKey::NumberPad6, RETROK_KP6 },
127    { (unsigned int)VirtualKey::NumberPad7, RETROK_KP7 },
128    { (unsigned int)VirtualKey::NumberPad8, RETROK_KP8 },
129    { (unsigned int)VirtualKey::NumberPad9, RETROK_KP9 },
130    { (unsigned int)VirtualKey::Multiply, RETROK_KP_MULTIPLY },
131    { (unsigned int)VirtualKey::Add, RETROK_KP_PLUS },
132    { (unsigned int)VirtualKey::Subtract, RETROK_KP_MINUS },
133    { (unsigned int)VirtualKey::Decimal, RETROK_KP_PERIOD },
134    { (unsigned int)VirtualKey::Divide, RETROK_KP_DIVIDE },
135    { (unsigned int)VirtualKey::F1, RETROK_F1 },
136    { (unsigned int)VirtualKey::F2, RETROK_F2 },
137    { (unsigned int)VirtualKey::F3, RETROK_F3 },
138    { (unsigned int)VirtualKey::F4, RETROK_F4 },
139    { (unsigned int)VirtualKey::F5, RETROK_F5 },
140    { (unsigned int)VirtualKey::F6, RETROK_F6 },
141    { (unsigned int)VirtualKey::F7, RETROK_F7 },
142    { (unsigned int)VirtualKey::F8, RETROK_F8 },
143    { (unsigned int)VirtualKey::F9, RETROK_F9 },
144    { (unsigned int)VirtualKey::F10, RETROK_F10 },
145    { (unsigned int)VirtualKey::F11, RETROK_F11 },
146    { (unsigned int)VirtualKey::F12, RETROK_F12 },
147    { (unsigned int)VirtualKey::F13, RETROK_F13 },
148    { (unsigned int)VirtualKey::F14, RETROK_F14 },
149    { (unsigned int)VirtualKey::F15, RETROK_F15 },
150    { (unsigned int)VirtualKey::NumberKeyLock, RETROK_NUMLOCK },
151    { (unsigned int)VirtualKey::Scroll, RETROK_SCROLLOCK },
152    { (unsigned int)VirtualKey::LeftShift, RETROK_LSHIFT },
153    { (unsigned int)VirtualKey::RightShift, RETROK_RSHIFT },
154    { (unsigned int)VirtualKey::LeftControl, RETROK_LCTRL },
155    { (unsigned int)VirtualKey::RightControl, RETROK_RCTRL },
156    { (unsigned int)VirtualKey::LeftMenu, RETROK_LALT },
157    { (unsigned int)VirtualKey::RightMenu, RETROK_RALT },
158    { VK_RETURN, RETROK_KP_ENTER },
159    { (unsigned int)VirtualKey::CapitalLock, RETROK_CAPSLOCK },
160    { VK_OEM_1, RETROK_SEMICOLON },
161    { VK_OEM_PLUS, RETROK_EQUALS },
162    { VK_OEM_COMMA, RETROK_COMMA },
163    { VK_OEM_MINUS, RETROK_MINUS },
164    { VK_OEM_PERIOD, RETROK_PERIOD },
165    { VK_OEM_2, RETROK_SLASH },
166    { VK_OEM_3, RETROK_BACKQUOTE },
167    { VK_OEM_4, RETROK_LEFTBRACKET },
168    { VK_OEM_5, RETROK_BACKSLASH },
169    { VK_OEM_6, RETROK_RIGHTBRACKET },
170    { VK_OEM_7, RETROK_QUOTE },
171    { 0, RETROK_UNKNOWN }
172 };
173 
174 #define MAX_TOUCH 16
175 struct input_pointer
176 {
177 	int id;
178 	short x;
179    short y;
180 	short full_x;
181    short full_y;
182 	bool isInContact;
183 };
184 
185 struct uwp_input_state_t
186 {
187    struct input_pointer touch[MAX_TOUCH]; /* int alignment */
188    unsigned touch_count;
189    short mouse_screen_x;
190    short mouse_screen_y;
191    short mouse_rel_x;
192    short mouse_rel_y;
193    short mouse_wheel_left;
194    short mouse_wheel_up;
195    bool mouse_left;
196    bool mouse_right;
197    bool mouse_middle;
198    bool mouse_button4;
199    bool mouse_button5;
200 };
201 
202 struct uwp_input_state_t uwp_current_input, uwp_next_input;
203 
204 // Taken from DirectX UWP samples - on Xbox, everything is scaled 200% so getting the DPI calculation correct is crucial
ConvertDipsToPixels(float dips,float dpi)205 static inline float ConvertDipsToPixels(float dips, float dpi)
206 {
207 	static const float dipsPerInch = 96.0f;
208 	return floorf(dips * dpi / dipsPerInch + 0.5f);
209 }
210 
211 // The main function is only used to initialize our IFrameworkView class.
212 [Platform::MTAThread]
213 int main(Platform::Array<Platform::String^>^)
214 {
215 	Platform::String^ install_dir = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + L"\\";
216 	wcstombs(uwp_dir_install, install_dir->Data(), sizeof(uwp_dir_install));
217 	Platform::String^ data_dir = Windows::Storage::ApplicationData::Current->LocalFolder->Path + L"\\";
218 	wcstombs(uwp_dir_data, data_dir->Data(), sizeof(uwp_dir_data));
219 
220 	wcstombs(uwp_device_family,
221          AnalyticsInfo::VersionInfo->DeviceFamily->Data(),
222          sizeof(uwp_device_family));
223 
224 	RARCH_LOG("Data dir: %ls\n", data_dir->Data());
225 	RARCH_LOG("Install dir: %ls\n", install_dir->Data());
226 
227 	auto direct3DApplicationSource = ref new Direct3DApplicationSource();
228 	CoreApplication::Run(direct3DApplicationSource);
229 	return 0;
230 }
231 
232 IFrameworkView^ Direct3DApplicationSource::CreateView()
233 {
234 	return ref new App();
235 }
236 
237 App^ App::m_instance;
238 
App()239 App::App() :
240 	m_initialized(false),
241 	m_windowClosed(false),
242 	m_windowVisible(true),
243 	m_windowFocused(true),
244 	m_windowResized(false)
245 {
246 	m_instance = this;
247 }
248 
249 /* The first method called when the IFrameworkView is being created. */
250 void App::Initialize(CoreApplicationView^ applicationView)
251 {
252 	/* Register event handlers for app lifecycle. This example includes Activated, so that we
253 	 * can make the CoreWindow active and start rendering on the window. */
254 	applicationView->Activated +=
255 		ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this, &App::OnActivated);
256 
257 	CoreApplication::Suspending +=
258 		ref new EventHandler<SuspendingEventArgs^>(this, &App::OnSuspending);
259 
260 	CoreApplication::Resuming +=
261 		ref new EventHandler<Platform::Object^>(this, &App::OnResuming);
262 }
263 
264 /* Called when the CoreWindow object is created (or re-created). */
265 void App::SetWindow(CoreWindow^ window)
266 {
267 	window->SizeChanged +=
268 		ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(this, &App::OnWindowSizeChanged);
269 
270 	window->VisibilityChanged +=
271 		ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &App::OnVisibilityChanged);
272 
273 	window->Activated +=
274 		ref new TypedEventHandler<CoreWindow^, WindowActivatedEventArgs^>(this, &App::OnWindowActivated);
275 
276 	window->Closed +=
277 		ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>(this, &App::OnWindowClosed);
278 
279 	window->KeyDown +=
280 		ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &App::OnKey);
281 
282 	window->KeyUp +=
283 		ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &App::OnKey);
284 
285 	window->PointerPressed +=
286 		ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &App::OnPointer);
287 
288 	window->PointerReleased +=
289 		ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &App::OnPointer);
290 
291 	window->PointerMoved +=
292 		ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &App::OnPointer);
293 
294 	window->PointerWheelChanged +=
295 		ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &App::OnPointer);
296 
297 	DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();
298 
299 	currentDisplayInformation->DpiChanged +=
300 		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnDpiChanged);
301 
302 	DisplayInformation::DisplayContentsInvalidated +=
303 		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnDisplayContentsInvalidated);
304 
305 	currentDisplayInformation->OrientationChanged +=
306 		ref new TypedEventHandler<DisplayInformation^, Object^>(this, &App::OnOrientationChanged);
307 
308 	Windows::UI::Core::SystemNavigationManager::GetForCurrentView()->BackRequested +=
309 		ref new EventHandler<Windows::UI::Core::BackRequestedEventArgs^>(this, &App::OnBackRequested);
310 }
311 
312 /* Initializes scene resources, or loads a previously saved app state. */
313 void App::Load(Platform::String^ entryPoint)
314 {
315 	int ret = rarch_main(NULL, NULL, NULL);
316 	if (ret != 0)
317 	{
318 		RARCH_ERR("Init failed\n");
319 		CoreApplication::Exit();
320 		return;
321 	}
322 	m_initialized = true;
323 
324 	auto catalog = Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage();
325 
326 	catalog->PackageInstalling +=
327 		ref new TypedEventHandler<PackageCatalog^, PackageInstallingEventArgs^>(this, &App::OnPackageInstalling);
328 }
329 
330 /* This method is called after the window becomes active. */
Run()331 void App::Run()
332 {
333    bool x = false;
334 	if (!m_initialized)
335 	{
336 		RARCH_WARN("Initialization failed, so not running\n");
337 		return;
338 	}
339 
340 
341    for (;;)
342 	{
343       int ret;
344 		CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
345 
346 		ret = runloop_iterate();
347 
348 		task_queue_check();
349 
350 		if (!x)
351 		{
352 			/* HACK: I have no idea why is this necessary but
353           * it is required to get proper scaling on Xbox *
354 			 * Perhaps PreferredLaunchViewSize is broken and
355           * we need to wait until the app starts to call TryResizeView */
356 			m_windowResized = true;
357 			x = true;
358 		}
359 
360 		if (ret == -1)
361 			break;
362 	}
363 }
364 
365 /* Required for IFrameworkView.
366  * Terminate events do not cause Uninitialize to be called.
367  * It will be called if your IFrameworkView
368  * class is torn down while the app is in the foreground. */
Uninitialize()369 void App::Uninitialize()
370 {
371 	main_exit(NULL);
372 }
373 
374 /* Application lifecycle event handlers. */
375 
376 void App::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
377 {
378 	/* Run() won't start until the CoreWindow is activated. */
379 	CoreWindow::GetForCurrentThread()->Activate();
380 }
381 
382 void App::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args)
383 {
384 	/* Save app state asynchronously after requesting a deferral. Holding a deferral
385 	 * indicates that the application is busy performing suspending operations. Be
386 	 * aware that a deferral may not be held indefinitely. After about five seconds,
387 	 * the app will be forced to exit.
388     */
389 	SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral();
390 	auto                     app = this;
391 
392 	create_task([app, deferral]()
__anonb7eed5fe0102() 393 	{
394 		/* TODO: Maybe creating a save state here would be a good idea? */
395 		settings_t* settings     = config_get_ptr();
396       bool config_save_on_exit = settings->bools.config_save_on_exit;
397 
398 		if (config_save_on_exit)
399       {
400 			if (!path_is_empty(RARCH_PATH_CONFIG))
401 			{
402 				const char* config_path = path_get(RARCH_PATH_CONFIG);
403 				bool path_exists        = !string_is_empty(config_path);
404 
405             if (path_exists)
406             {
407                if (config_save_file(config_path))
408                {
409                   RARCH_LOG("[config] %s \"%s\".\n",
410                         msg_hash_to_str(MSG_SAVED_NEW_CONFIG_TO),
411                         config_path);
412                }
413                else
414                {
415                   RARCH_ERR("[config] %s \"%s\".\n",
416                      msg_hash_to_str(MSG_FAILED_SAVING_CONFIG_TO),
417                      config_path);
418                }
419             }
420 
421 			}
422 		}
423 
424 		deferral->Complete();
425 	});
426 }
427 
428 void App::OnResuming(Platform::Object^ sender, Platform::Object^ args)
429 {
430 	/* Restore any data or state that was unloaded on suspend. By default, data
431 	 * and state are persisted when resuming from suspend. Note that this event
432 	 * does not occur if the app was previously terminated.
433     */
434 }
435 
436 void App::OnBackRequested(Platform::Object^ sender, Windows::UI::Core::BackRequestedEventArgs^ args)
437 {
438 	/* Prevent the B controller button on Xbox One from quitting the app */
439 	args->Handled = true;
440 }
441 
442 /* Window event handlers. */
443 
444 void App::OnWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
445 {
446 	m_windowResized = true;
447 }
448 
449 void App::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
450 {
451 	m_windowVisible = args->Visible;
452 }
453 
454 void App::OnWindowActivated(CoreWindow^ sender, WindowActivatedEventArgs^ args)
455 {
456 	m_windowFocused = args->WindowActivationState != CoreWindowActivationState::Deactivated;
457 }
458 
459 void App::OnKey(CoreWindow^ sender, KeyEventArgs^ args)
460 {
461 	uint16_t mod = 0;
462 	if ((sender->GetKeyState(VirtualKey::Shift) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
463 		mod |= RETROKMOD_SHIFT;
464 	if ((sender->GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
465 		mod |= RETROKMOD_CTRL;
466 	if ((sender->GetKeyState(VirtualKey::Menu) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
467 		mod |= RETROKMOD_ALT;
468 	if ((sender->GetKeyState(VirtualKey::CapitalLock) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
469 		mod |= RETROKMOD_CAPSLOCK;
470 	if ((sender->GetKeyState(VirtualKey::Scroll) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
471 		mod |= RETROKMOD_SCROLLOCK;
472 	if ((sender->GetKeyState(VirtualKey::LeftWindows) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked ||
473 		(sender->GetKeyState(VirtualKey::RightWindows) & CoreVirtualKeyStates::Locked) == CoreVirtualKeyStates::Locked)
474 		mod |= RETROKMOD_META;
475 
476 	unsigned keycode = input_keymaps_translate_keysym_to_rk((unsigned)args->VirtualKey);
477 
478 	input_keyboard_event(!args->KeyStatus.IsKeyReleased, keycode, 0, mod, RETRO_DEVICE_KEYBOARD);
479 }
480 
481 void App::OnPointer(CoreWindow^ sender, PointerEventArgs^ args)
482 {
483 
484 	float dpi = DisplayInformation::GetForCurrentView()->LogicalDpi;
485 
486 	if (args->CurrentPoint->PointerDevice->PointerDeviceType == PointerDeviceType::Mouse)
487 	{
488 		uwp_next_input.mouse_left = args->CurrentPoint->Properties->IsLeftButtonPressed;
489 		uwp_next_input.mouse_middle = args->CurrentPoint->Properties->IsMiddleButtonPressed;
490 		uwp_next_input.mouse_right = args->CurrentPoint->Properties->IsRightButtonPressed;
491 		uwp_next_input.mouse_button4 = args->CurrentPoint->Properties->IsXButton1Pressed;
492 		uwp_next_input.mouse_button5 = args->CurrentPoint->Properties->IsXButton2Pressed;
493 		uwp_next_input.mouse_screen_x = ConvertDipsToPixels(args->CurrentPoint->Position.X, dpi);
494 		uwp_next_input.mouse_screen_y = ConvertDipsToPixels(args->CurrentPoint->Position.Y, dpi);
495 		uwp_next_input.mouse_rel_x = uwp_next_input.mouse_screen_x - uwp_current_input.mouse_screen_x;
496 		uwp_next_input.mouse_rel_y = uwp_next_input.mouse_screen_y - uwp_current_input.mouse_screen_y;
497 		if (args->CurrentPoint->Properties->IsHorizontalMouseWheel)
498 			uwp_next_input.mouse_wheel_left += args->CurrentPoint->Properties->MouseWheelDelta;
499 		else
500 			uwp_next_input.mouse_wheel_up += args->CurrentPoint->Properties->MouseWheelDelta;
501 	}
502 	else
503 	{
504 		unsigned i, free_index = MAX_TOUCH; bool found = false;
505 		int id = args->CurrentPoint->PointerId;
506 
507 		for (i = 0; i < uwp_next_input.touch_count; i++)
508 		{
509 			if (!uwp_next_input.touch[i].isInContact && free_index == MAX_TOUCH)
510 				free_index = i;
511 			if (uwp_next_input.touch[i].id == id)
512 			{
513 				found = true;
514 				break;
515 			}
516 		}
517 
518 		if (!found)
519 		{
520 			if (free_index >= 0 && free_index < uwp_next_input.touch_count)
521 				i = free_index;
522 			else if (uwp_next_input.touch_count + 1 < MAX_TOUCH)
523 				i = ++uwp_next_input.touch_count;
524 			else
525 				return;
526 		}
527 
528 		uwp_next_input.touch[i].id = id;
529 
530 		struct video_viewport vp;
531 
532 		/* convert from event coordinates to core and screen coordinates */
533 		vp.x           = 0;
534 		vp.y           = 0;
535 		vp.width       = 0;
536 		vp.height      = 0;
537 		vp.full_width  = 0;
538 		vp.full_height = 0;
539 
540 		video_driver_translate_coord_viewport_wrap(
541 			&vp,
542 			ConvertDipsToPixels(args->CurrentPoint->Position.X, dpi),
543 			ConvertDipsToPixels(args->CurrentPoint->Position.Y, dpi),
544 			&uwp_next_input.touch[i].x,
545 			&uwp_next_input.touch[i].y,
546 			&uwp_next_input.touch[i].full_x,
547 			&uwp_next_input.touch[i].full_y);
548 
549 		uwp_next_input.touch[i].isInContact = args->CurrentPoint->IsInContact;
550 
551 	}
552 }
553 
554 void App::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
555 {
556 	m_windowClosed = true;
557 }
558 
559 /* DisplayInformation event handlers. */
560 
561 void App::OnDpiChanged(DisplayInformation^ sender, Object^ args)
562 {
563 	m_windowResized = true;
564 }
565 
566 void App::OnOrientationChanged(DisplayInformation^ sender, Object^ args)
567 {
568 	m_windowResized = true;
569 }
570 
571 void App::OnDisplayContentsInvalidated(DisplayInformation^ sender, Object^ args)
572 {
573 	/* Probably can be ignored? */
574 }
575 
576 void App::OnPackageInstalling(PackageCatalog^ sender, PackageInstallingEventArgs^ args)
577 {
578 	/* TODO: This doesn't seem to work even though it's exactly the same as in sample app and it works there */
579 	if (args->IsComplete)
580 	{
581 		char msg[512];
582 		snprintf(msg, sizeof(msg), "Package \"%ls\" installed, a restart may be necessary", args->Package->DisplayName->Data());
583 		runloop_msg_queue_push(msg, 1, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
584 	}
585 }
586 
587 /* Implement UWP equivalents of various win32_* functions */
588 extern "C" {
589 
win32_has_focus(void * data)590 	bool win32_has_focus(void *data)
591 	{
592 		return App::GetInstance()->IsWindowFocused();
593 	}
594 
win32_set_video_mode(void * data,unsigned width,unsigned height,bool fullscreen)595 	bool win32_set_video_mode(void *data, unsigned width, unsigned height, bool fullscreen)
596 	{
597 		if (App::GetInstance()->IsInitialized())
598 		{
599 			if (fullscreen != ApplicationView::GetForCurrentView()->IsFullScreenMode)
600 			{
601 				if (fullscreen)
602 					ApplicationView::GetForCurrentView()->TryEnterFullScreenMode();
603 				else
604 					ApplicationView::GetForCurrentView()->ExitFullScreenMode();
605 			}
606 			ApplicationView::GetForCurrentView()->TryResizeView(Size(width, height));
607 		}
608 		else
609 		{
610 			/* In case the window is not activated yet, TryResizeView will fail and we have to set the initial parameters instead */
611 			/* Note that these are preserved after restarting the app and used for the UWP splash screen size (!), so they should be set only during init and not changed afterwards */
612 			ApplicationView::PreferredLaunchViewSize = Size(width, height);
613 			ApplicationView::PreferredLaunchWindowingMode = fullscreen ? ApplicationViewWindowingMode::FullScreen : ApplicationViewWindowingMode::PreferredLaunchViewSize;
614 		}
615 
616 		/* Setting the window size may sometimes fail "because UWP"
617 		 * (i.e. we are on device with no windows, or Windows sandbox decides the window can't be that small)
618 		 * so in case resizing fails we just send a resized event back to RetroArch with old size
619 		 * (and report success because otherwise it bails out hard about failing to set video mode)
620 		 */
621 		App::GetInstance()->SetWindowResized();
622 		return true;
623 	}
624 
win32_show_cursor(void * data,bool state)625 	void win32_show_cursor(void *data, bool state)
626    {
627       CoreWindow::GetForCurrentThread()->PointerCursor = state ? ref new CoreCursor(CoreCursorType::Arrow, 0) : nullptr;
628    }
629 
630 
win32_get_metrics(void * data,enum display_metric_types type,float * value)631 	bool win32_get_metrics(void* data,
632 		enum display_metric_types type, float* value)
633 	{
634 		int pixels_x        = DisplayInformation::GetForCurrentView()->ScreenWidthInRawPixels;
635 		int pixels_y        = DisplayInformation::GetForCurrentView()->ScreenHeightInRawPixels;
636 		int raw_dpi_x       = DisplayInformation::GetForCurrentView()->RawDpiX;
637 		int raw_dpi_y       = DisplayInformation::GetForCurrentView()->RawDpiY;
638 		int physical_width  = pixels_x / raw_dpi_x;
639 		int physical_height = pixels_y / raw_dpi_y;
640 
641 		switch (type)
642 		{
643 		case DISPLAY_METRIC_PIXEL_WIDTH:
644 			*value           = pixels_x;
645 			return true;
646 		case DISPLAY_METRIC_PIXEL_HEIGHT:
647 			*value           = pixels_y;
648 			return true;
649 		case DISPLAY_METRIC_MM_WIDTH:
650 			/* 25.4 mm in an inch. */
651 			*value           = 254 * physical_width / 10;
652 			return true;
653 		case DISPLAY_METRIC_MM_HEIGHT:
654 			/* 25.4 mm in an inch. */
655 			*value           = 254 * physical_height / 10;
656 			return true;
657 		case DISPLAY_METRIC_DPI:
658 			*value           = raw_dpi_x;
659 			return true;
660 		case DISPLAY_METRIC_NONE:
661 		default:
662 			*value           = 0;
663 			break;
664 		}
665 		return false;
666 	}
667 
win32_check_window(void * data,bool * quit,bool * resize,unsigned * width,unsigned * height)668 	void win32_check_window(void *data,
669          bool *quit, bool *resize, unsigned *width, unsigned *height)
670 	{
671 		*quit   = App::GetInstance()->IsWindowClosed();
672       settings_t* settings = config_get_ptr();
673 		if (settings->bools.video_force_resolution)
674 		{
675 			*width = settings->uints.video_fullscreen_x != 0 ? settings->uints.video_fullscreen_x : 3840;
676 			*height = settings->uints.video_fullscreen_y != 0 ? settings->uints.video_fullscreen_y : 2160;
677 			return;
678 		}
679 
680 		*resize = App::GetInstance()->CheckWindowResized();
681 		if (*resize)
682 		{
683 			float dpi = DisplayInformation::GetForCurrentView()->LogicalDpi;
684 			*width    = ConvertDipsToPixels(CoreWindow::GetForCurrentThread()->Bounds.Width, dpi);
685 			*height   = ConvertDipsToPixels(CoreWindow::GetForCurrentThread()->Bounds.Height, dpi);
686 		}
687 	}
688 
uwp_get_corewindow(void)689 	void* uwp_get_corewindow(void)
690 	{
691 		return (void*)CoreWindow::GetForCurrentThread();
692 	}
693 
uwp_fill_installed_core_packages(struct string_list * list)694 	void uwp_fill_installed_core_packages(struct string_list *list)
695 	{
696 		for (auto package : Windows::ApplicationModel::Package::Current->Dependencies)
697 		{
698 			if (package->IsOptional)
699 			{
700 				string_list_elem_attr attr{};
701 				string_list_append(list, utf16_to_utf8_string_alloc((package->InstalledLocation->Path + L"\\cores")->Data()), attr);
702 			}
703 		}
704 	}
705 
uwp_input_next_frame(void * data)706 	void uwp_input_next_frame(void *data)
707 	{
708 		uwp_current_input                = uwp_next_input;
709 		uwp_next_input.mouse_rel_x       = 0;
710 		uwp_next_input.mouse_rel_y       = 0;
711 		uwp_next_input.mouse_wheel_up   %= WHEEL_DELTA;
712 		uwp_next_input.mouse_wheel_left %= WHEEL_DELTA;
713 	}
714 
uwp_keyboard_pressed(unsigned key)715 	bool uwp_keyboard_pressed(unsigned key)
716    {
717       VirtualKey sym = (VirtualKey)rarch_keysym_lut[(enum retro_key)key];
718 
719       if (sym == VirtualKey::None)
720          return false;
721 
722       CoreWindow^ window = CoreWindow::GetForCurrentThread();
723 
724       /* At times CoreWindow will return NULL while running Dolphin core
725        * Dolphin core runs on its own CPU thread separate from the UI-thread and so we must do a check for this. */
726       if (!window)
727          return false;
728       return (window->GetKeyState(sym) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down;
729    }
730 
uwp_mouse_state(unsigned port,unsigned id,bool screen)731 	int16_t uwp_mouse_state(unsigned port, unsigned id, bool screen)
732    {
733       int16_t state = 0;
734 
735       switch (id)
736       {
737          case RETRO_DEVICE_ID_MOUSE_X:
738             return screen
739                ? uwp_current_input.mouse_screen_x
740                : uwp_current_input.mouse_rel_x;
741          case RETRO_DEVICE_ID_MOUSE_Y:
742             return screen
743                ? uwp_current_input.mouse_screen_y
744                : uwp_current_input.mouse_rel_y;
745          case RETRO_DEVICE_ID_MOUSE_LEFT:
746             return uwp_current_input.mouse_left;
747          case RETRO_DEVICE_ID_MOUSE_RIGHT:
748             return uwp_current_input.mouse_right;
749          case RETRO_DEVICE_ID_MOUSE_WHEELUP:
750             return uwp_current_input.mouse_wheel_up > WHEEL_DELTA;
751          case RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
752             return uwp_current_input.mouse_wheel_up < -WHEEL_DELTA;
753          case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP:
754             return uwp_current_input.mouse_wheel_left > WHEEL_DELTA;
755          case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN:
756             return uwp_current_input.mouse_wheel_left < -WHEEL_DELTA;
757          case RETRO_DEVICE_ID_MOUSE_MIDDLE:
758             return uwp_current_input.mouse_middle;
759          case RETRO_DEVICE_ID_MOUSE_BUTTON_4:
760             return uwp_current_input.mouse_button4;
761          case RETRO_DEVICE_ID_MOUSE_BUTTON_5:
762             return uwp_current_input.mouse_button5;
763       }
764 
765       return 0;
766    }
767 
uwp_pointer_state(unsigned idx,unsigned id,bool screen)768 	int16_t uwp_pointer_state(unsigned idx, unsigned id, bool screen)
769 	{
770 		switch (id)
771       {
772          case RETRO_DEVICE_ID_POINTER_X:
773             return screen
774                ? uwp_current_input.touch[idx].full_x
775                : uwp_current_input.touch[idx].x;
776          case RETRO_DEVICE_ID_POINTER_Y:
777             return screen
778                ? uwp_current_input.touch[idx].full_y
779                : uwp_current_input.touch[idx].y;
780          case RETRO_DEVICE_ID_POINTER_PRESSED:
781             return uwp_current_input.touch[idx].isInContact;
782          case RETRO_DEVICE_ID_POINTER_COUNT:
783             return uwp_current_input.touch_count;
784          default:
785             break;
786       }
787 
788 		return 0;
789 	}
790 
uwp_open_broadfilesystemaccess_settings(void)791 	void uwp_open_broadfilesystemaccess_settings(void)
792 	{
793 		Windows::System::Launcher::LaunchUriAsync(ref new Uri("ms-settings:privacy-broadfilesystemaccess"));
794 	}
795 
uwp_get_language(void)796 	enum retro_language uwp_get_language(void)
797 	{
798 		auto lang                 = Windows::System::UserProfile::GlobalizationPreferences::Languages->GetAt(0);
799       struct string_list  split = {0};
800 		char lang_bcp[16]         = {0};
801 		char lang_iso[16]         = {0};
802 
803 		wcstombs(lang_bcp, lang->Data(), sizeof(lang_bcp));
804 
805 		/* Trying to convert BCP 47 language codes to ISO 639 ones */
806       string_list_initialize(&split);
807 		string_split_noalloc(&split, lang_bcp, "-");
808 
809 		strlcpy(lang_iso, split.elems[0].data, sizeof(lang_iso));
810 
811 		if (split.size >= 2)
812 		{
813 			strlcat(lang_iso, "_", sizeof(lang_iso));
814 			strlcat(lang_iso, split.elems[split.size >= 3 ? 2 : 1].data,
815                sizeof(lang_iso));
816 		}
817       string_list_deinitialize(&split);
818 		return rarch_get_language_from_iso(lang_iso);
819 	}
820 
uwp_get_cpu_model_name(void)821 	const char *uwp_get_cpu_model_name(void)
822 	{
823 		Platform::String^ cpu_id = nullptr;
824 		Platform::String^ cpu_name = nullptr;
825 
826 		/* GUID_DEVICE_PROCESSOR: {97FADB10-4E33-40AE-359C-8BEF029DBDD0} */
827 		Platform::String^ if_filter = L"System.Devices.InterfaceClassGuid:=\"{97FADB10-4E33-40AE-359C-8BEF029DBDD0}\"";
828 
829 		/* Enumerate all CPU DeviceInterfaces, and get DeviceInstanceID of the first one. */
830 		cpu_id = RunAsyncAndCatchErrors<Platform::String^>([&]() {
831 			return create_task(DeviceInformation::FindAllAsync(if_filter)).then(
832 				[&](DeviceInformationCollection^ collection)
833 				{
834 					return dynamic_cast<Platform::String^>(
835 						collection->GetAt(0)->Properties->Lookup(L"System.Devices.DeviceInstanceID"));
836 				});
837 			}, nullptr);
838 
839 		if (cpu_id)
840 		{
841 			Platform::String^ dev_filter = L"System.Devices.DeviceInstanceID:=\"" + cpu_id + L"\"";
842 
843 			/* Get the Device with the same ID as the DeviceInterface
844 			 * Then get the name (description) of that Device
845 			 * We have to do this because the DeviceInterface we get doesn't have a proper description. */
846 			cpu_name = RunAsyncAndCatchErrors<Platform::String^>([&]() {
847 				return create_task(
848 					DeviceInformation::FindAllAsync(dev_filter, {}, DeviceInformationKind::Device)).then(
849 						[&](DeviceInformationCollection^ collection)
850 						{
851 							return cpu_name = collection->GetAt(0)->Name;
852 						});
853 				}, nullptr);
854 		}
855 
856 
857 		if (!cpu_name)
858          return "Unknown";
859 
860       wcstombs(win32_cpu_model_name, cpu_name->Data(), sizeof(win32_cpu_model_name));
861       return win32_cpu_model_name;
862 	}
863 }
864