1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 2011-2020  Warzone 2100 Project
4 
5 	Warzone 2100 is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	Warzone 2100 is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with Warzone 2100; if not, write to the Free Software
17 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 /**
20  * @file main_sdl.cpp
21  *
22  * SDL backend code
23  */
24 
25 // Get platform defines before checking for them!
26 #include "lib/framework/wzapp.h"
27 
28 #include "lib/framework/input.h"
29 #include "lib/framework/utf.h"
30 #include "lib/ivis_opengl/pieclip.h"
31 #include "lib/ivis_opengl/piemode.h"
32 #include "lib/ivis_opengl/screen.h"
33 #include "lib/exceptionhandler/dumpinfo.h"
34 #include "lib/gamelib/gtime.h"
35 #include "src/warzoneconfig.h"
36 #include "src/game.h"
37 #include "gfx_api_sdl.h"
38 #include "gfx_api_gl_sdl.h"
39 #include <SDL.h>
40 #include <SDL_thread.h>
41 #include <SDL_clipboard.h>
42 #if defined(HAVE_SDL_VULKAN_H)
43 #include <SDL_vulkan.h>
44 #endif
45 #include <SDL_version.h>
46 #include "wz2100icon.h"
47 #include "cursors_sdl.h"
48 #include <algorithm>
49 #include <map>
50 #include <locale.h>
51 #include <atomic>
52 #include <chrono>
53 
54 #if defined(WZ_OS_MAC)
55 #include "cocoa_sdl_helpers.h"
56 #include "cocoa_wz_menus.h"
57 #endif
58 
59 void mainLoop();
60 // used in crash reports & version info
61 const char *BACKEND = "SDL";
62 
63 std::map<KEY_CODE, SDL_Keycode> KEY_CODE_to_SDLKey;
64 std::map<SDL_Keycode, KEY_CODE > SDLKey_to_KEY_CODE;
65 
66 int realmain(int argc, char *argv[]);
67 
68 // the main stub which calls realmain() aka, WZ's main startup routines
main(int argc,char * argv[])69 int main(int argc, char *argv[])
70 {
71 	return realmain(argc, argv);
72 }
73 
74 // At this time, we only have 1 window.
75 static SDL_Window *WZwindow = nullptr;
76 static optional<video_backend> WZbackend = video_backend::opengl;
77 
78 #if defined(WZ_OS_MAC)
79 // on macOS, SDL_WINDOW_FULLSCREEN_DESKTOP *must* be used (or high-DPI fullscreen toggling breaks)
80 const WINDOW_MODE WZ_SDL_DEFAULT_FULLSCREEN_MODE = WINDOW_MODE::desktop_fullscreen;
81 #else
82 const WINDOW_MODE WZ_SDL_DEFAULT_FULLSCREEN_MODE = WINDOW_MODE::fullscreen;
83 #endif
84 static WINDOW_MODE lastFullscreenMode = WZ_SDL_DEFAULT_FULLSCREEN_MODE;
85 
86 // The screen that the game window is on.
87 int screenIndex = 0;
88 // The logical resolution of the game in the game's coordinate system (points).
89 unsigned int screenWidth = 0;
90 unsigned int screenHeight = 0;
91 // The logical resolution of the SDL window in the window's coordinate system (points) - i.e. not accounting for the Game Display Scale setting.
92 unsigned int windowWidth = 0;
93 unsigned int windowHeight = 0;
94 // The current display scale factor.
95 unsigned int current_displayScale = 100;
96 float current_displayScaleFactor = 1.f;
97 
98 static std::vector<screeninfo> displaylist;	// holds all our possible display lists
99 
100 std::atomic<Uint32> wzSDLAppEvent((Uint32)-1);
101 enum wzSDLAppEventCodes
102 {
103 	MAINTHREADEXEC
104 };
105 
106 /* The possible states for keys */
107 enum KEY_STATE
108 {
109 	KEY_UP,
110 	KEY_PRESSED,
111 	KEY_DOWN,
112 	KEY_RELEASED,
113 	KEY_PRESSRELEASE,	// When a key goes up and down in a frame
114 	KEY_DOUBLECLICK,	// Only used by mouse keys
115 	KEY_DRAG			// Only used by mouse keys
116 };
117 
118 struct INPUT_STATE
119 {
120 	KEY_STATE state; /// Last key/mouse state
121 	UDWORD lastdown; /// last key/mouse button down timestamp
122 	Vector2i pressPos;    ///< Location of last mouse press event.
123 	Vector2i releasePos;  ///< Location of last mouse release event.
124 };
125 
126 // Clipboard routines
127 bool has_scrap(void);
128 bool get_scrap(char **dst);
129 
130 /// constant for the interval between 2 singleclicks for doubleclick event in ms
131 #define DOUBLE_CLICK_INTERVAL 250
132 
133 /* The current state of the keyboard */
134 static INPUT_STATE aKeyState[KEY_MAXSCAN];		// NOTE: SDL_NUM_SCANCODES is the max, but KEY_MAXSCAN is our limit
135 
136 /* The current location of the mouse */
137 static Uint16 mouseXPos = 0;
138 static Uint16 mouseYPos = 0;
139 static Vector2i mouseWheelSpeed;
140 static bool mouseInWindow = true;
141 
142 /* How far the mouse has to move to start a drag */
143 #define DRAG_THRESHOLD	5
144 
145 /* Which button is being used for a drag */
146 static MOUSE_KEY_CODE dragKey;
147 
148 /* The start of a possible drag by the mouse */
149 static int dragX = 0;
150 static int dragY = 0;
151 
152 /* The current mouse button state */
153 static INPUT_STATE aMouseState[MOUSE_END];
154 static MousePresses mousePresses;
155 
156 /* The current screen resizing state for this iteration through the game loop, in the game coordinate system */
157 struct ScreenSizeChange
158 {
ScreenSizeChangeScreenSizeChange159 	ScreenSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
160 	:	oldWidth(oldWidth)
161 	,	oldHeight(oldHeight)
162 	,	newWidth(newWidth)
163 	,	newHeight(newHeight)
164 	{ }
165 	unsigned int oldWidth;
166 	unsigned int oldHeight;
167 	unsigned int newWidth;
168 	unsigned int newHeight;
169 };
170 static ScreenSizeChange* currentScreenResizingStatus = nullptr;
171 
172 /* The size of the input buffer */
173 #define INPUT_MAXSTR 256
174 
175 /* The input string buffer */
176 struct InputKey
177 {
178 	UDWORD key;
179 	utf_32_char unicode;
180 };
181 
182 static InputKey	pInputBuffer[INPUT_MAXSTR];
183 static InputKey	*pStartBuffer, *pEndBuffer;
184 static utf_32_char *utf8Buf;				// is like the old 'unicode' from SDL 1.x
185 void* GetTextEventsOwner = nullptr;
186 
187 /**************************/
188 /***     Misc support   ***/
189 /**************************/
190 
wzGetPlatform()191 WzString wzGetPlatform()
192 {
193 	return WzString::fromUtf8(SDL_GetPlatform());
194 }
195 
196 // See if we have TEXT in the clipboard
has_scrap(void)197 bool has_scrap(void)
198 {
199 	return SDL_HasClipboardText();
200 }
201 
202 // Set the clipboard text
wzSetClipboardText(const char * src)203 bool wzSetClipboardText(const char *src)
204 {
205 	if (SDL_SetClipboardText(src))
206 	{
207 		debug(LOG_ERROR, "Could not put clipboard text because : %s", SDL_GetError());
208 		return false;
209 	}
210 	return true;
211 }
212 
213 // Get text from the clipboard
get_scrap(char ** dst)214 bool get_scrap(char **dst)
215 {
216 	if (has_scrap())
217 	{
218 		char *cliptext = SDL_GetClipboardText();
219 		if (!cliptext)
220 		{
221 			debug(LOG_ERROR, "Could not get clipboard text because : %s", SDL_GetError());
222 			return false;
223 		}
224 		*dst = cliptext;
225 		return true;
226 	}
227 	else
228 	{
229 		// wasn't text or no text in the clipboard
230 		return false;
231 	}
232 }
233 
StartTextInput(void * pTextInputRequester)234 void StartTextInput(void* pTextInputRequester)
235 {
236 	if (!GetTextEventsOwner)
237 	{
238 		SDL_StartTextInput();	// enable text events
239 		debug(LOG_INPUT, "SDL text events started");
240 	}
241 	else if (pTextInputRequester != GetTextEventsOwner)
242 	{
243 		debug(LOG_INPUT, "StartTextInput called by new input requester before old requester called StopTextInput");
244 	}
245 	GetTextEventsOwner = pTextInputRequester;
246 }
247 
StopTextInput(void * pTextInputResigner)248 void StopTextInput(void* pTextInputResigner)
249 {
250 	if (!GetTextEventsOwner)
251 	{
252 		debug(LOG_INPUT, "Ignoring StopTextInput call when text input is already disabled");
253 		return;
254 	}
255 	if (pTextInputResigner != GetTextEventsOwner)
256 	{
257 		// Rejecting StopTextInput from regsigner who is not the last requester
258 		debug(LOG_INPUT, "Ignoring StopTextInput call from resigner that is not the last requester (i.e. caller of StartTextInput)");
259 		return;
260 	}
261 	SDL_StopTextInput();	// disable text events
262 	GetTextEventsOwner = nullptr;
263 	debug(LOG_INPUT, "SDL text events stopped");
264 }
265 
isInTextInputMode()266 bool isInTextInputMode()
267 {
268 	bool result = (GetTextEventsOwner != nullptr);
269 	ASSERT((SDL_IsTextInputActive() != SDL_FALSE) == result, "How did GetTextEvents state and SDL_IsTextInputActive get out of sync?");
270 	return result;
271 }
272 
273 /* Put a character into a text buffer overwriting any text under the cursor */
wzGetSelection()274 WzString wzGetSelection()
275 {
276 	WzString retval;
277 	static char *scrap = nullptr;
278 
279 	if (get_scrap(&scrap))
280 	{
281 		retval = WzString::fromUtf8(scrap);
282 	}
283 	return retval;
284 }
285 
wzAvailableResolutions()286 std::vector<screeninfo> wzAvailableResolutions()
287 {
288 	return displaylist;
289 }
290 
wzAvailableDisplayScales()291 std::vector<unsigned int> wzAvailableDisplayScales()
292 {
293 	static const unsigned int wzDisplayScales[] = { 100, 125, 150, 200, 250, 300, 400, 500 };
294 	return std::vector<unsigned int>(wzDisplayScales, wzDisplayScales + (sizeof(wzDisplayScales) / sizeof(wzDisplayScales[0])));
295 }
296 
sortGfxBackendsForCurrentSystem(std::vector<video_backend> & backends)297 static std::vector<video_backend>& sortGfxBackendsForCurrentSystem(std::vector<video_backend>& backends)
298 {
299 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX) && (defined(_M_ARM64) || defined(_M_ARM))
300 	// On ARM-based Windows, DirectX should be first (for compatibility)
301 	std::stable_sort(backends.begin(), backends.end(), [](video_backend a, video_backend b) -> bool {
302 		if (a == b) { return false; }
303 		if (a == video_backend::directx) { return true; }
304 		return false;
305 	});
306 #else
307 	// currently, no-op
308 #endif
309 	return backends;
310 }
311 
wzAvailableGfxBackends()312 std::vector<video_backend> wzAvailableGfxBackends()
313 {
314 	std::vector<video_backend> availableBackends;
315 	availableBackends.push_back(video_backend::opengl);
316 #if !defined(WZ_OS_MAC) // OpenGL ES is not supported on macOS, and WZ doesn't currently ship with an OpenGL ES library on macOS
317 	availableBackends.push_back(video_backend::opengles);
318 #endif
319 #if defined(WZ_VULKAN_ENABLED) && defined(HAVE_SDL_VULKAN_H)
320 	availableBackends.push_back(video_backend::vulkan);
321 #endif
322 #if defined(WZ_BACKEND_DIRECTX)
323 	availableBackends.push_back(video_backend::directx);
324 #endif
325 	sortGfxBackendsForCurrentSystem(availableBackends);
326 	return availableBackends;
327 }
328 
wzGetDefaultGfxBackendForCurrentSystem()329 video_backend wzGetDefaultGfxBackendForCurrentSystem()
330 {
331 	// SDL backend supports: OpenGL, OpenGLES, Vulkan (if compiled with support), DirectX (on Windows, via LibANGLE)
332 
333 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX) && (defined(_M_ARM64) || defined(_M_ARM))
334 	// On ARM-based Windows, DirectX should be the default (for compatibility)
335 	return video_backend::directx;
336 #else
337 	// Future TODO examples:
338 	//	- Default to Vulkan backend on macOS versions > 10.??, to use Metal via MoltenVK (needs testing - and may require exclusions depending on hardware?)
339 	//	- Default to DirectX (via LibANGLE) backend on Windows, depending on Windows version (and possibly hardware? / DirectX-level support?)
340 	//	- Check if Vulkan appears to be properly supported on a Windows / Linux system, and default to Vulkan backend?
341 
342 	// For now, default to OpenGL (which automatically falls back to OpenGL ES if needed)
343 	return video_backend::opengl;
344 #endif
345 }
346 
wzGetNextFallbackGfxBackendForCurrentSystem(const video_backend & current_failed_backend)347 static video_backend wzGetNextFallbackGfxBackendForCurrentSystem(const video_backend& current_failed_backend)
348 {
349 	video_backend next_backend;
350 #if defined(_WIN32) && defined(WZ_BACKEND_DIRECTX)
351 	switch (current_failed_backend)
352 	{
353 		case video_backend::opengl:
354 			// offer DirectX as a fallback option if OpenGL failed
355 			next_backend = video_backend::directx;
356 			break;
357 #if (defined(_M_ARM64) || defined(_M_ARM))
358 		case video_backend::directx:
359 			// since DirectX is the default on ARM-based Windows, offer OpenGL as an alternative
360 			next_backend = video_backend::opengl;
361 			break;
362 #endif
363 		default:
364 			// offer usual default
365 			next_backend = wzGetDefaultGfxBackendForCurrentSystem();
366 			break;
367 	}
368 #elif defined(WZ_OS_MAC)
369 	switch (current_failed_backend)
370 	{
371 		case video_backend::opengl:
372 			// offer Vulkan (which uses Vulkan -> Metal) as a fallback option if OpenGL failed
373 			next_backend = video_backend::vulkan;
374 			break;
375 		default:
376 			// offer usual default
377 			next_backend = wzGetDefaultGfxBackendForCurrentSystem();
378 			break;
379 	}
380 #else
381 	next_backend = wzGetDefaultGfxBackendForCurrentSystem();
382 #endif
383 
384 	// sanity-check: verify that next_backend is in available backends
385 	const auto available = wzAvailableGfxBackends();
386 	if (std::find(available.begin(), available.end(), next_backend) == available.end())
387 	{
388 		// next_backend does not exist in the list of available backends, so default to wzGetDefaultGfxBackendForCurrentSystem()
389 		next_backend = wzGetDefaultGfxBackendForCurrentSystem();
390 	}
391 
392 	return next_backend;
393 }
394 
SDL_WZBackend_GetDrawableSize(SDL_Window * window,int * w,int * h)395 void SDL_WZBackend_GetDrawableSize(SDL_Window* window,
396 								   int*        w,
397 								   int*        h)
398 {
399 	if (!WZbackend.has_value())
400 	{
401 		return;
402 	}
403 	switch (WZbackend.value())
404 	{
405 #if defined(WZ_BACKEND_DIRECTX)
406 		case video_backend::directx: // because DirectX is supported via OpenGLES (LibANGLE)
407 #endif
408 		case video_backend::opengl:
409 		case video_backend::opengles:
410 			return SDL_GL_GetDrawableSize(window, w, h);
411 		case video_backend::vulkan:
412 #if defined(HAVE_SDL_VULKAN_H)
413 			return SDL_Vulkan_GetDrawableSize(window, w, h);
414 #else
415 			SDL_version compiled_version;
416 			SDL_VERSION(&compiled_version);
417 			debug(LOG_FATAL, "The version of SDL used for compilation (%u.%u.%u) did not have the SDL_vulkan.h header", (unsigned int)compiled_version.major, (unsigned int)compiled_version.minor, (unsigned int)compiled_version.patch);
418 			if (w) { w = 0; }
419 			if (h) { h = 0; }
420 			return;
421 #endif
422 		case video_backend::num_backends:
423 			debug(LOG_FATAL, "Should never happen");
424 			return;
425 	}
426 }
427 
setDisplayScale(unsigned int displayScale)428 void setDisplayScale(unsigned int displayScale)
429 {
430 	ASSERT(displayScale >= 100, "Invalid display scale: %u", displayScale);
431 	current_displayScale = displayScale;
432 	current_displayScaleFactor = (float)displayScale / 100.f;
433 }
434 
wzGetCurrentDisplayScale()435 unsigned int wzGetCurrentDisplayScale()
436 {
437 	return current_displayScale;
438 }
439 
wzShowMouse(bool visible)440 void wzShowMouse(bool visible)
441 {
442 	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
443 }
444 
wzGetTicks()445 int wzGetTicks()
446 {
447 	return SDL_GetTicks();
448 }
449 
wzDisplayDialog(DialogType type,const char * title,const char * message)450 void wzDisplayDialog(DialogType type, const char *title, const char *message)
451 {
452 	Uint32 sdl_messagebox_flags = 0;
453 	switch (type)
454 	{
455 		case Dialog_Error:
456 			sdl_messagebox_flags = SDL_MESSAGEBOX_ERROR;
457 			break;
458 		case Dialog_Warning:
459 			sdl_messagebox_flags = SDL_MESSAGEBOX_WARNING;
460 			break;
461 		case Dialog_Information:
462 			sdl_messagebox_flags = SDL_MESSAGEBOX_INFORMATION;
463 			break;
464 	}
465 	SDL_ShowSimpleMessageBox(sdl_messagebox_flags, title, message, WZwindow);
466 }
467 
wzGetCurrentWindowMode()468 WINDOW_MODE wzGetCurrentWindowMode()
469 {
470 	if (!WZbackend.has_value())
471 	{
472 		// return a dummy value
473 		return WINDOW_MODE::windowed;
474 	}
475 	if (WZwindow == nullptr)
476 	{
477 		debug(LOG_WARNING, "wzGetCurrentWindowMode called when window is not available");
478 		return WINDOW_MODE::windowed;
479 	}
480 
481 	Uint32 flags = SDL_GetWindowFlags(WZwindow);
482 	if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
483 	{
484 		return WINDOW_MODE::desktop_fullscreen;
485 	}
486 	else if (flags & SDL_WINDOW_FULLSCREEN)
487 	{
488 		return WINDOW_MODE::fullscreen;
489 	}
490 	else
491 	{
492 		return WINDOW_MODE::windowed;
493 	}
494 }
495 
wzSupportedWindowModes()496 std::vector<WINDOW_MODE> wzSupportedWindowModes()
497 {
498 #if defined(WZ_OS_MAC)
499 	// on macOS, SDL_WINDOW_FULLSCREEN_DESKTOP *must* be used (or high-DPI fullscreen toggling breaks)
500 	// thus "classic" fullscreen is not supported
501 	return {WINDOW_MODE::desktop_fullscreen, WINDOW_MODE::windowed};
502 #else
503 	return {WINDOW_MODE::desktop_fullscreen, WINDOW_MODE::windowed, WINDOW_MODE::fullscreen};
504 #endif
505 }
506 
wzGetNextWindowMode(WINDOW_MODE currentMode)507 WINDOW_MODE wzGetNextWindowMode(WINDOW_MODE currentMode)
508 {
509 	auto supportedModes = wzSupportedWindowModes();
510 	ASSERT_OR_RETURN(WINDOW_MODE::windowed, !supportedModes.empty(), "No supported fullscreen / windowed modes available?");
511 	auto it = std::find(supportedModes.begin(), supportedModes.end(), currentMode);
512 	if (it == supportedModes.end())
513 	{
514 		// we appear to be in an unsupported mode - so default to the first
515 		return *supportedModes.begin();
516 	}
517 	++it;
518 	if (it == supportedModes.end()) { it = supportedModes.begin(); }
519 	return *it;
520 }
521 
wzAltEnterToggleFullscreen()522 WINDOW_MODE wzAltEnterToggleFullscreen()
523 {
524 	// toggles out of and into the "last" fullscreen mode
525 	auto mode = wzGetCurrentWindowMode();
526 	switch (mode)
527 	{
528 		case WINDOW_MODE::desktop_fullscreen:
529 			// fall-through
530 		case WINDOW_MODE::fullscreen:
531 			lastFullscreenMode = mode;
532 			if (wzChangeWindowMode(WINDOW_MODE::windowed))
533 			{
534 				mode = WINDOW_MODE::windowed;
535 			}
536 			break;
537 		case WINDOW_MODE::windowed:
538 			// use lastFullscreenMode
539 			if (wzChangeWindowMode(lastFullscreenMode))
540 			{
541 				mode = lastFullscreenMode;
542 			}
543 			break;
544 	}
545 	return mode;
546 }
547 
wzChangeWindowMode(WINDOW_MODE mode)548 bool wzChangeWindowMode(WINDOW_MODE mode)
549 {
550 	if (wzGetCurrentWindowMode() == mode)
551 	{
552 		// already in this mode
553 		return true;
554 	}
555 
556 	int sdl_result = -1;
557 	switch (mode)
558 	{
559 		case WINDOW_MODE::desktop_fullscreen:
560 			sdl_result = SDL_SetWindowFullscreen(WZwindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
561 			if (sdl_result != 0) { return false; }
562 			wzSetWindowIsResizable(false);
563 			break;
564 		case WINDOW_MODE::windowed:
565 			sdl_result = SDL_SetWindowFullscreen(WZwindow, 0);
566 			if (sdl_result != 0) { return false; }
567 			wzSetWindowIsResizable(true);
568 			break;
569 		case WINDOW_MODE::fullscreen:
570 			sdl_result = SDL_SetWindowFullscreen(WZwindow, SDL_WINDOW_FULLSCREEN);
571 			if (sdl_result != 0) { return false; }
572 			wzSetWindowIsResizable(false);
573 			break;
574 	}
575 
576 	return sdl_result == 0;
577 }
578 
wzIsFullscreen()579 bool wzIsFullscreen()
580 {
581 	if (WZwindow == nullptr)
582 	{
583 		// NOTE: Can't use debug(...) to log here or it will risk overwriting fatal error messages,
584 		// as `_debug(...)` calls this function
585 		return false;
586 	}
587 	Uint32 flags = SDL_GetWindowFlags(WZwindow);
588 	if ((flags & SDL_WINDOW_FULLSCREEN) || (flags & SDL_WINDOW_FULLSCREEN_DESKTOP))
589 	{
590 		return true;
591 	}
592 	return false;
593 }
594 
wzIsMaximized()595 bool wzIsMaximized()
596 {
597 	if (WZwindow == nullptr)
598 	{
599 		debug(LOG_WARNING, "wzIsMaximized called when window is not available");
600 		return false;
601 	}
602 	Uint32 flags = SDL_GetWindowFlags(WZwindow);
603 	if (flags & SDL_WINDOW_MAXIMIZED)
604 	{
605 		return true;
606 	}
607 	return false;
608 }
609 
wzQuit()610 void wzQuit()
611 {
612 	// Create a quit event to halt game loop.
613 	SDL_Event quitEvent;
614 	quitEvent.type = SDL_QUIT;
615 	SDL_PushEvent(&quitEvent);
616 }
617 
wzGrabMouse()618 void wzGrabMouse()
619 {
620 	if (!WZbackend.has_value())
621 	{
622 		return;
623 	}
624 	if (WZwindow == nullptr)
625 	{
626 		debug(LOG_WARNING, "wzGrabMouse called when window is not available - ignoring");
627 		return;
628 	}
629 	SDL_SetWindowGrab(WZwindow, SDL_TRUE);
630 }
631 
wzReleaseMouse()632 void wzReleaseMouse()
633 {
634 	if (!WZbackend.has_value())
635 	{
636 		return;
637 	}
638 	if (WZwindow == nullptr)
639 	{
640 		debug(LOG_WARNING, "wzReleaseMouse called when window is not available - ignoring");
641 		return;
642 	}
643 	SDL_SetWindowGrab(WZwindow, SDL_FALSE);
644 }
645 
wzDelay(unsigned int delay)646 void wzDelay(unsigned int delay)
647 {
648 	SDL_Delay(delay);
649 }
650 
651 /**************************/
652 /***    Thread support  ***/
653 /**************************/
wzThreadCreate(int (* threadFunc)(void *),void * data)654 WZ_THREAD *wzThreadCreate(int (*threadFunc)(void *), void *data)
655 {
656 	return (WZ_THREAD *)SDL_CreateThread(threadFunc, "wzThread", data);
657 }
658 
wzThreadID(WZ_THREAD * thread)659 unsigned long wzThreadID(WZ_THREAD *thread)
660 {
661 	SDL_threadID threadID = SDL_GetThreadID((SDL_Thread *)thread);
662 	return threadID;
663 }
664 
wzThreadJoin(WZ_THREAD * thread)665 int wzThreadJoin(WZ_THREAD *thread)
666 {
667 	int result;
668 	SDL_WaitThread((SDL_Thread *)thread, &result);
669 	return result;
670 }
671 
wzThreadDetach(WZ_THREAD * thread)672 void wzThreadDetach(WZ_THREAD *thread)
673 {
674 	SDL_DetachThread((SDL_Thread *)thread);
675 }
676 
wzThreadStart(WZ_THREAD * thread)677 void wzThreadStart(WZ_THREAD *thread)
678 {
679 	(void)thread; // no-op
680 }
681 
wzYieldCurrentThread()682 void wzYieldCurrentThread()
683 {
684 	SDL_Delay(40);
685 }
686 
wzMutexCreate()687 WZ_MUTEX *wzMutexCreate()
688 {
689 	return (WZ_MUTEX *)SDL_CreateMutex();
690 }
691 
wzMutexDestroy(WZ_MUTEX * mutex)692 void wzMutexDestroy(WZ_MUTEX *mutex)
693 {
694 	SDL_DestroyMutex((SDL_mutex *)mutex);
695 }
696 
wzMutexLock(WZ_MUTEX * mutex)697 void wzMutexLock(WZ_MUTEX *mutex)
698 {
699 	SDL_LockMutex((SDL_mutex *)mutex);
700 }
701 
wzMutexUnlock(WZ_MUTEX * mutex)702 void wzMutexUnlock(WZ_MUTEX *mutex)
703 {
704 	SDL_UnlockMutex((SDL_mutex *)mutex);
705 }
706 
wzSemaphoreCreate(int startValue)707 WZ_SEMAPHORE *wzSemaphoreCreate(int startValue)
708 {
709 	return (WZ_SEMAPHORE *)SDL_CreateSemaphore(startValue);
710 }
711 
wzSemaphoreDestroy(WZ_SEMAPHORE * semaphore)712 void wzSemaphoreDestroy(WZ_SEMAPHORE *semaphore)
713 {
714 	SDL_DestroySemaphore((SDL_sem *)semaphore);
715 }
716 
wzSemaphoreWait(WZ_SEMAPHORE * semaphore)717 void wzSemaphoreWait(WZ_SEMAPHORE *semaphore)
718 {
719 	SDL_SemWait((SDL_sem *)semaphore);
720 }
721 
wzSemaphorePost(WZ_SEMAPHORE * semaphore)722 void wzSemaphorePost(WZ_SEMAPHORE *semaphore)
723 {
724 	SDL_SemPost((SDL_sem *)semaphore);
725 }
726 
727 // Asynchronously executes exec->doExecOnMainThread() on the main thread
728 // `exec` should be a subclass of `WZ_MAINTHREADEXEC`
729 //
730 // `exec` must be allocated on the heap since the main event loop takes ownership of it
731 // and will handle deleting it once it has been processed.
732 // It is not safe to access `exec` after calling wzAsyncExecOnMainThread.
733 //
734 // No guarantees are made about when execFunc() will be called relative to the
735 // calling of this function - this function may return before, during, or after
736 // execFunc()'s execution on the main thread.
wzAsyncExecOnMainThread(WZ_MAINTHREADEXEC * exec)737 void wzAsyncExecOnMainThread(WZ_MAINTHREADEXEC *exec)
738 {
739 	Uint32 _wzSDLAppEvent = wzSDLAppEvent.load();
740 	assert(_wzSDLAppEvent != ((Uint32)-1));
741 	if (_wzSDLAppEvent == ((Uint32)-1)) {
742 		// The app-defined event has not yet been registered with SDL
743 		return;
744 	}
745 	SDL_Event event;
746 	SDL_memset(&event, 0, sizeof(event));
747 	event.type = _wzSDLAppEvent;
748 	event.user.code = wzSDLAppEventCodes::MAINTHREADEXEC;
749 	event.user.data1 = exec;
750 	assert(event.user.data1 != nullptr);
751 	event.user.data2 = 0;
752 	SDL_PushEvent(&event);
753 	// receiver handles deleting `exec` on the main thread after doExecOnMainThread() has been called
754 }
755 
756 /*!
757 ** The keycodes we care about
758 **/
initKeycodes()759 static inline void initKeycodes()
760 {
761 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_ESC, SDLK_ESCAPE));
762 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_1, SDLK_1));
763 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_2, SDLK_2));
764 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_3, SDLK_3));
765 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_4, SDLK_4));
766 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_5, SDLK_5));
767 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_6, SDLK_6));
768 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_7, SDLK_7));
769 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_8, SDLK_8));
770 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_9, SDLK_9));
771 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_0, SDLK_0));
772 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_MINUS, SDLK_MINUS));
773 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_EQUALS, SDLK_EQUALS));
774 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKSPACE, SDLK_BACKSPACE));
775 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_TAB, SDLK_TAB));
776 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Q, SDLK_q));
777 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_W, SDLK_w));
778 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_E, SDLK_e));
779 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_R, SDLK_r));
780 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_T, SDLK_t));
781 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Y, SDLK_y));
782 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_U, SDLK_u));
783 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_I, SDLK_i));
784 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_O, SDLK_o));
785 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_P, SDLK_p));
786 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LBRACE, SDLK_LEFTBRACKET));
787 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RBRACE, SDLK_RIGHTBRACKET));
788 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RETURN, SDLK_RETURN));
789 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LCTRL, SDLK_LCTRL));
790 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_A, SDLK_a));
791 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_S, SDLK_s));
792 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_D, SDLK_d));
793 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F, SDLK_f));
794 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_G, SDLK_g));
795 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_H, SDLK_h));
796 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_J, SDLK_j));
797 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_K, SDLK_k));
798 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_L, SDLK_l));
799 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SEMICOLON, SDLK_SEMICOLON));
800 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_QUOTE, SDLK_QUOTE));
801 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKQUOTE, SDLK_BACKQUOTE));
802 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LSHIFT, SDLK_LSHIFT));
803 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LMETA, SDLK_LGUI));
804 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LSUPER, SDLK_LGUI));
805 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_BACKSLASH, SDLK_BACKSLASH));
806 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_Z, SDLK_z));
807 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_X, SDLK_x));
808 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_C, SDLK_c));
809 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_V, SDLK_v));
810 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_B, SDLK_b));
811 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_N, SDLK_n));
812 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_M, SDLK_m));
813 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_COMMA, SDLK_COMMA));
814 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_FULLSTOP, SDLK_PERIOD));
815 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_FORWARDSLASH, SDLK_SLASH));
816 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RSHIFT, SDLK_RSHIFT));
817 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RMETA, SDLK_RGUI));
818 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RSUPER, SDLK_RGUI));
819 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_STAR, SDLK_KP_MULTIPLY));
820 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LALT, SDLK_LALT));
821 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SPACE, SDLK_SPACE));
822 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_CAPSLOCK, SDLK_CAPSLOCK));
823 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F1, SDLK_F1));
824 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F2, SDLK_F2));
825 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F3, SDLK_F3));
826 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F4, SDLK_F4));
827 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F5, SDLK_F5));
828 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F6, SDLK_F6));
829 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F7, SDLK_F7));
830 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F8, SDLK_F8));
831 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F9, SDLK_F9));
832 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F10, SDLK_F10));
833 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_NUMLOCK, SDLK_NUMLOCKCLEAR));
834 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_SCROLLLOCK, SDLK_SCROLLLOCK));
835 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_7, SDLK_KP_7));
836 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_8, SDLK_KP_8));
837 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_9, SDLK_KP_9));
838 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_MINUS, SDLK_KP_MINUS));
839 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_4, SDLK_KP_4));
840 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_5, SDLK_KP_5));
841 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_6, SDLK_KP_6));
842 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_PLUS, SDLK_KP_PLUS));
843 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_1, SDLK_KP_1));
844 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_2, SDLK_KP_2));
845 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_3, SDLK_KP_3));
846 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_0, SDLK_KP_0));
847 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_FULLSTOP, SDLK_KP_PERIOD));
848 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F11, SDLK_F11));
849 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_F12, SDLK_F12));
850 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RCTRL, SDLK_RCTRL));
851 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KP_BACKSLASH, SDLK_KP_DIVIDE));
852 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RALT, SDLK_RALT));
853 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_HOME, SDLK_HOME));
854 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_UPARROW, SDLK_UP));
855 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_PAGEUP, SDLK_PAGEUP));
856 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_LEFTARROW, SDLK_LEFT));
857 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_RIGHTARROW, SDLK_RIGHT));
858 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_END, SDLK_END));
859 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_DOWNARROW, SDLK_DOWN));
860 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_PAGEDOWN, SDLK_PAGEDOWN));
861 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_INSERT, SDLK_INSERT));
862 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_DELETE, SDLK_DELETE));
863 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_KPENTER, SDLK_KP_ENTER));
864 	KEY_CODE_to_SDLKey.insert(std::pair<KEY_CODE, SDL_Keycode>(KEY_IGNORE, SDL_Keycode(5190)));
865 
866 	std::map<KEY_CODE, SDL_Keycode>::iterator it;
867 	for (it = KEY_CODE_to_SDLKey.begin(); it != KEY_CODE_to_SDLKey.end(); it++)
868 	{
869 		SDLKey_to_KEY_CODE.insert(std::pair<SDL_Keycode, KEY_CODE>(it->second, it->first));
870 	}
871 }
872 
sdlKeyToKeyCode(SDL_Keycode key)873 static inline KEY_CODE sdlKeyToKeyCode(SDL_Keycode key)
874 {
875 	std::map<SDL_Keycode, KEY_CODE >::iterator it;
876 
877 	it = SDLKey_to_KEY_CODE.find(key);
878 	if (it != SDLKey_to_KEY_CODE.end())
879 	{
880 		return it->second;
881 	}
882 	return (KEY_CODE)key;
883 }
884 
keyCodeToSDLKey(KEY_CODE code)885 static inline SDL_Keycode keyCodeToSDLKey(KEY_CODE code)
886 {
887 	std::map<KEY_CODE, SDL_Keycode>::iterator it;
888 
889 	it = KEY_CODE_to_SDLKey.find(code);
890 	if (it != KEY_CODE_to_SDLKey.end())
891 	{
892 		return it->second;
893 	}
894 	return (SDL_Keycode)code;
895 }
896 
897 // Cyclic increment.
inputPointerNext(InputKey * p)898 static InputKey *inputPointerNext(InputKey *p)
899 {
900 	return p + 1 == pInputBuffer + INPUT_MAXSTR ? pInputBuffer : p + 1;
901 }
902 
903 /* add count copies of the characater code to the input buffer */
inputAddBuffer(UDWORD key,utf_32_char unicode)904 static void inputAddBuffer(UDWORD key, utf_32_char unicode)
905 {
906 	/* Calculate what pEndBuffer will be set to next */
907 	InputKey	*pNext = inputPointerNext(pEndBuffer);
908 
909 	if (pNext == pStartBuffer)
910 	{
911 		return;	// Buffer full.
912 	}
913 
914 	// Add key to buffer.
915 	pEndBuffer->key = key;
916 	pEndBuffer->unicode = unicode;
917 	pEndBuffer = pNext;
918 }
919 
keyScanToString(KEY_CODE code,char * ascii,UDWORD maxStringSize)920 void keyScanToString(KEY_CODE code, char *ascii, UDWORD maxStringSize)
921 {
922 	if (code == KEY_LCTRL)
923 	{
924 		// shortcuts with modifier keys work with either key.
925 		strcpy(ascii, "Ctrl");
926 		return;
927 	}
928 	else if (code == KEY_LSHIFT)
929 	{
930 		// shortcuts with modifier keys work with either key.
931 		strcpy(ascii, "Shift");
932 		return;
933 	}
934 	else if (code == KEY_LALT)
935 	{
936 		// shortcuts with modifier keys work with either key.
937 		strcpy(ascii, "Alt");
938 		return;
939 	}
940 	else if (code == KEY_LMETA)
941 	{
942 		// shortcuts with modifier keys work with either key.
943 #ifdef WZ_OS_MAC
944 		strcpy(ascii, "Cmd");
945 #else
946 		strcpy(ascii, "Meta");
947 #endif
948 		return;
949 	}
950 
951 	if (code < KEY_MAXSCAN)
952 	{
953 		snprintf(ascii, maxStringSize, "%s", SDL_GetKeyName(keyCodeToSDLKey(code)));
954 		if (ascii[0] >= 'a' && ascii[0] <= 'z' && ascii[1] != 0)
955 		{
956 			// capitalize
957 			ascii[0] += 'A' - 'a';
958 			return;
959 		}
960 	}
961 	else
962 	{
963 		strcpy(ascii, "???");
964 	}
965 }
966 
967 /* Initialise the input module */
inputInitialise(void)968 void inputInitialise(void)
969 {
970 	for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
971 	{
972 		aKeyState[i].state = KEY_UP;
973 	}
974 
975 	for (unsigned int i = 0; i < MOUSE_END; i++)
976 	{
977 		aMouseState[i].state = KEY_UP;
978 	}
979 
980 	pStartBuffer = pInputBuffer;
981 	pEndBuffer = pInputBuffer;
982 
983 	dragX = mouseXPos = screenWidth / 2;
984 	dragY = mouseYPos = screenHeight / 2;
985 	dragKey = MOUSE_LMB;
986 
987 }
988 
989 /* Clear the input buffer */
inputClearBuffer(void)990 void inputClearBuffer(void)
991 {
992 	pStartBuffer = pInputBuffer;
993 	pEndBuffer = pInputBuffer;
994 }
995 
996 /* Return the next key press or 0 if no key in the buffer.
997  * The key returned will have been remapped to the correct ascii code for the
998  * windows key map.
999  * All key presses are buffered up (including windows auto repeat).
1000  */
inputGetKey(utf_32_char * unicode)1001 UDWORD inputGetKey(utf_32_char *unicode)
1002 {
1003 	UDWORD	 retVal;
1004 
1005 	if (pStartBuffer == pEndBuffer)
1006 	{
1007 		return 0;	// Buffer empty.
1008 	}
1009 
1010 	retVal = pStartBuffer->key;
1011 	if (unicode)
1012 	{
1013 		*unicode = pStartBuffer->unicode;
1014 	}
1015 	if (!retVal)
1016 	{
1017 		retVal = ' ';  // Don't return 0 if we got a virtual key, since that's interpreted as no input.
1018 	}
1019 	pStartBuffer = inputPointerNext(pStartBuffer);
1020 
1021 	return retVal;
1022 }
1023 
inputGetClicks()1024 MousePresses const &inputGetClicks()
1025 {
1026 	return mousePresses;
1027 }
1028 
1029 /*!
1030  * This is called once a frame so that the system can tell
1031  * whether a key was pressed this turn or held down from the last frame.
1032  */
inputNewFrame(void)1033 void inputNewFrame(void)
1034 {
1035 	// handle the keyboard
1036 	for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
1037 	{
1038 		if (aKeyState[i].state == KEY_PRESSED)
1039 		{
1040 			aKeyState[i].state = KEY_DOWN;
1041 			debug(LOG_NEVER, "This key is DOWN! %x, %d [%s]", i, i, SDL_GetKeyName(keyCodeToSDLKey((KEY_CODE)i)));
1042 		}
1043 		else if (aKeyState[i].state == KEY_RELEASED  ||
1044 		         aKeyState[i].state == KEY_PRESSRELEASE)
1045 		{
1046 			aKeyState[i].state = KEY_UP;
1047 			debug(LOG_NEVER, "This key is UP! %x, %d [%s]", i, i, SDL_GetKeyName(keyCodeToSDLKey((KEY_CODE)i)));
1048 		}
1049 	}
1050 
1051 	// handle the mouse
1052 	for (unsigned int i = 0; i < MOUSE_END; i++)
1053 	{
1054 		if (aMouseState[i].state == KEY_PRESSED)
1055 		{
1056 			aMouseState[i].state = KEY_DOWN;
1057 		}
1058 		else if (aMouseState[i].state == KEY_RELEASED
1059 		         || aMouseState[i].state == KEY_DOUBLECLICK
1060 		         || aMouseState[i].state == KEY_PRESSRELEASE)
1061 		{
1062 			aMouseState[i].state = KEY_UP;
1063 		}
1064 	}
1065 	mousePresses.clear();
1066 	mouseWheelSpeed = Vector2i(0, 0);
1067 }
1068 
1069 /*!
1070  * Release all keys (and buttons) when we lose focus
1071  */
inputLoseFocus(void)1072 void inputLoseFocus(void)
1073 {
1074 	/* Lost the window focus, have to take this as a global key up */
1075 	for (unsigned int i = 0; i < KEY_MAXSCAN; i++)
1076 	{
1077 		aKeyState[i].state = KEY_UP;
1078 	}
1079 	for (unsigned int i = 0; i < MOUSE_END; i++)
1080 	{
1081 		aMouseState[i].state = KEY_UP;
1082 	}
1083 }
1084 
1085 /* This returns true if the key is currently depressed */
keyDown(KEY_CODE code)1086 bool keyDown(KEY_CODE code)
1087 {
1088 	ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1089 	return (aKeyState[code].state != KEY_UP);
1090 }
1091 
1092 /* This returns true if the key went from being up to being down this frame */
keyPressed(KEY_CODE code)1093 bool keyPressed(KEY_CODE code)
1094 {
1095 	ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1096 	return ((aKeyState[code].state == KEY_PRESSED) || (aKeyState[code].state == KEY_PRESSRELEASE));
1097 }
1098 
1099 /* This returns true if the key went from being down to being up this frame */
keyReleased(KEY_CODE code)1100 bool keyReleased(KEY_CODE code)
1101 {
1102 	ASSERT_OR_RETURN(false, code < KEY_MAXSCAN, "Invalid keycode of %d!", (int)code);
1103 	return ((aKeyState[code].state == KEY_RELEASED) || (aKeyState[code].state == KEY_PRESSRELEASE));
1104 }
1105 
1106 /* Return the X coordinate of the mouse */
mouseX(void)1107 Uint16 mouseX(void)
1108 {
1109 	return mouseXPos;
1110 }
1111 
1112 /* Return the Y coordinate of the mouse */
mouseY(void)1113 Uint16 mouseY(void)
1114 {
1115 	return mouseYPos;
1116 }
1117 
wzMouseInWindow()1118 bool wzMouseInWindow()
1119 {
1120 	return mouseInWindow;
1121 }
1122 
getMouseWheelSpeed()1123 Vector2i const& getMouseWheelSpeed()
1124 {
1125 	return mouseWheelSpeed;
1126 }
1127 
mousePressPos_DEPRECATED(MOUSE_KEY_CODE code)1128 Vector2i mousePressPos_DEPRECATED(MOUSE_KEY_CODE code)
1129 {
1130 	return aMouseState[code].pressPos;
1131 }
1132 
mouseReleasePos_DEPRECATED(MOUSE_KEY_CODE code)1133 Vector2i mouseReleasePos_DEPRECATED(MOUSE_KEY_CODE code)
1134 {
1135 	return aMouseState[code].releasePos;
1136 }
1137 
1138 /* This returns true if the mouse key is currently depressed */
mouseDown(MOUSE_KEY_CODE code)1139 bool mouseDown(MOUSE_KEY_CODE code)
1140 {
1141 	return (aMouseState[code].state != KEY_UP) ||
1142 
1143 	       // holding down LMB and RMB counts as holding down MMB
1144 	       (code == MOUSE_MMB && aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state != KEY_UP);
1145 }
1146 
1147 /* This returns true if the mouse key was double clicked */
mouseDClicked(MOUSE_KEY_CODE code)1148 bool mouseDClicked(MOUSE_KEY_CODE code)
1149 {
1150 	return (aMouseState[code].state == KEY_DOUBLECLICK);
1151 }
1152 
1153 /* This returns true if the mouse key went from being up to being down this frame */
mousePressed(MOUSE_KEY_CODE code)1154 bool mousePressed(MOUSE_KEY_CODE code)
1155 {
1156 	return ((aMouseState[code].state == KEY_PRESSED) ||
1157 	        (aMouseState[code].state == KEY_DOUBLECLICK) ||
1158 	        (aMouseState[code].state == KEY_PRESSRELEASE));
1159 }
1160 
1161 /* This returns true if the mouse key went from being down to being up this frame */
mouseReleased(MOUSE_KEY_CODE code)1162 bool mouseReleased(MOUSE_KEY_CODE code)
1163 {
1164 	return ((aMouseState[code].state == KEY_RELEASED) ||
1165 	        (aMouseState[code].state == KEY_DOUBLECLICK) ||
1166 	        (aMouseState[code].state == KEY_PRESSRELEASE));
1167 }
1168 
1169 /* Check for a mouse drag, return the drag start coords if dragging */
mouseDrag(MOUSE_KEY_CODE code,UDWORD * px,UDWORD * py)1170 bool mouseDrag(MOUSE_KEY_CODE code, UDWORD *px, UDWORD *py)
1171 {
1172 	if ((aMouseState[code].state == KEY_DRAG) ||
1173 	    // dragging LMB and RMB counts as dragging MMB
1174 	    (code == MOUSE_MMB && ((aMouseState[MOUSE_LMB].state == KEY_DRAG && aMouseState[MOUSE_RMB].state != KEY_UP) ||
1175 	                           (aMouseState[MOUSE_LMB].state != KEY_UP && aMouseState[MOUSE_RMB].state == KEY_DRAG))))
1176 	{
1177 		*px = dragX;
1178 		*py = dragY;
1179 		return true;
1180 	}
1181 
1182 	return false;
1183 }
1184 
1185 /*!
1186  * Handle keyboard events
1187  */
inputHandleKeyEvent(SDL_KeyboardEvent * keyEvent)1188 static void inputHandleKeyEvent(SDL_KeyboardEvent *keyEvent)
1189 {
1190 	switch (keyEvent->type)
1191 	{
1192 	case SDL_KEYDOWN:
1193 	{
1194 		unsigned vk = 0;
1195 		switch (keyEvent->keysym.sym)
1196 		{
1197 		// our "editing" keys for text
1198 		case SDLK_LEFT:
1199 			vk = INPBUF_LEFT;
1200 			break;
1201 		case SDLK_RIGHT:
1202 			vk = INPBUF_RIGHT;
1203 			break;
1204 		case SDLK_UP:
1205 			vk = INPBUF_UP;
1206 			break;
1207 		case SDLK_DOWN:
1208 			vk = INPBUF_DOWN;
1209 			break;
1210 		case SDLK_HOME:
1211 			vk = INPBUF_HOME;
1212 			break;
1213 		case SDLK_END:
1214 			vk = INPBUF_END;
1215 			break;
1216 		case SDLK_INSERT:
1217 			vk = INPBUF_INS;
1218 			break;
1219 		case SDLK_DELETE:
1220 			vk = INPBUF_DEL;
1221 			break;
1222 		case SDLK_PAGEUP:
1223 			vk = INPBUF_PGUP;
1224 			break;
1225 		case SDLK_PAGEDOWN:
1226 			vk = INPBUF_PGDN;
1227 			break;
1228 		case KEY_BACKSPACE:
1229 			vk = INPBUF_BKSPACE;
1230 			break;
1231 		case KEY_TAB:
1232 			vk = INPBUF_TAB;
1233 			break;
1234 		case KEY_RETURN:
1235 			vk = INPBUF_CR;
1236 			break;
1237 		case KEY_ESC:
1238 			vk = INPBUF_ESC;
1239 			break;
1240 		default:
1241 			break;
1242 		}
1243 		// Keycodes without character representations are determined by their scancode bitwise OR-ed with 1<<30 (0x40000000).
1244 		unsigned currentKey = keyEvent->keysym.sym;
1245 		if (vk)
1246 		{
1247 			// Take care of 'editing' keys that were pressed
1248 			inputAddBuffer(vk, 0);
1249 			debug(LOG_INPUT, "Editing key: 0x%x, %d SDLkey=[%s] pressed", vk, vk, SDL_GetKeyName(currentKey));
1250 		}
1251 		else
1252 		{
1253 			// add everything else
1254 			inputAddBuffer(currentKey, 0);
1255 		}
1256 
1257 		debug(LOG_INPUT, "Key Code (pressed): 0x%x, %d, [%c] SDLkey=[%s]", currentKey, currentKey, currentKey < 128 && currentKey > 31 ? (char)currentKey : '?', SDL_GetKeyName(currentKey));
1258 
1259 		KEY_CODE code = sdlKeyToKeyCode(currentKey);
1260 		if (code >= KEY_MAXSCAN)
1261 		{
1262 			break;
1263 		}
1264 		if (aKeyState[code].state == KEY_UP ||
1265 		    aKeyState[code].state == KEY_RELEASED ||
1266 		    aKeyState[code].state == KEY_PRESSRELEASE)
1267 		{
1268 			// whether double key press or not
1269 			aKeyState[code].state = KEY_PRESSED;
1270 			aKeyState[code].lastdown = 0;
1271 		}
1272 		break;
1273 	}
1274 
1275 	case SDL_KEYUP:
1276 	{
1277 		unsigned currentKey = keyEvent->keysym.sym;
1278 		debug(LOG_INPUT, "Key Code (*Depressed*): 0x%x, %d, [%c] SDLkey=[%s]", currentKey, currentKey, currentKey < 128 && currentKey > 31 ? (char)currentKey : '?', SDL_GetKeyName(currentKey));
1279 		KEY_CODE code = sdlKeyToKeyCode(keyEvent->keysym.sym);
1280 		if (code >= KEY_MAXSCAN)
1281 		{
1282 			break;
1283 		}
1284 		if (aKeyState[code].state == KEY_PRESSED)
1285 		{
1286 			aKeyState[code].state = KEY_PRESSRELEASE;
1287 		}
1288 		else if (aKeyState[code].state == KEY_DOWN)
1289 		{
1290 			aKeyState[code].state = KEY_RELEASED;
1291 		}
1292 		break;
1293 	}
1294 	default:
1295 		break;
1296 	}
1297 }
1298 
1299 /*!
1300  * Handle text events (if we were to use SDL2)
1301 */
inputhandleText(SDL_TextInputEvent * Tevent)1302 void inputhandleText(SDL_TextInputEvent *Tevent)
1303 {
1304 	size_t size = SDL_strlen(Tevent->text);
1305 	if (size)
1306 	{
1307 		if (utf8Buf)
1308 		{
1309 			// clean up memory from last use.
1310 			free(utf8Buf);
1311 			utf8Buf = nullptr;
1312 		}
1313 		size_t newtextsize = 0;
1314 		utf8Buf = UTF8toUTF32(Tevent->text, &newtextsize);
1315 		debug(LOG_INPUT, "Keyboard: text input \"%s\"", Tevent->text);
1316 		for (unsigned i = 0; i < newtextsize / sizeof(utf_32_char); ++i)
1317 		{
1318 			inputAddBuffer(0, utf8Buf[i]);
1319 		}
1320 	}
1321 }
1322 
1323 /*!
1324  * Handle mouse wheel events
1325  */
inputHandleMouseWheelEvent(SDL_MouseWheelEvent * wheel)1326 static void inputHandleMouseWheelEvent(SDL_MouseWheelEvent *wheel)
1327 {
1328 	mouseWheelSpeed += Vector2i(wheel->x, wheel->y);
1329 
1330 	if (wheel->x > 0 || wheel->y > 0)
1331 	{
1332 		aMouseState[MOUSE_WUP].state = KEY_PRESSED;
1333 		aMouseState[MOUSE_WUP].lastdown = 0;
1334 	}
1335 	else if (wheel->x < 0 || wheel->y < 0)
1336 	{
1337 		aMouseState[MOUSE_WDN].state = KEY_PRESSED;
1338 		aMouseState[MOUSE_WDN].lastdown = 0;
1339 	}
1340 }
1341 
1342 /*!
1343  * Handle mouse button events (We can handle up to 5)
1344  */
inputHandleMouseButtonEvent(SDL_MouseButtonEvent * buttonEvent)1345 static void inputHandleMouseButtonEvent(SDL_MouseButtonEvent *buttonEvent)
1346 {
1347 	mouseXPos = (int)((float)buttonEvent->x / current_displayScaleFactor);
1348 	mouseYPos = (int)((float)buttonEvent->y / current_displayScaleFactor);
1349 
1350 	MOUSE_KEY_CODE mouseKeyCode;
1351 	switch (buttonEvent->button)
1352 	{
1353 	case SDL_BUTTON_LEFT: mouseKeyCode = MOUSE_LMB; break;
1354 	case SDL_BUTTON_MIDDLE: mouseKeyCode = MOUSE_MMB; break;
1355 	case SDL_BUTTON_RIGHT: mouseKeyCode = MOUSE_RMB; break;
1356 	case SDL_BUTTON_X1: mouseKeyCode = MOUSE_X1; break;
1357 	case SDL_BUTTON_X2: mouseKeyCode = MOUSE_X2; break;
1358 	default: return;  // Unknown button.
1359 	}
1360 
1361 	MousePress mousePress;
1362 	mousePress.key = mouseKeyCode;
1363 	mousePress.pos = Vector2i(mouseXPos, mouseYPos);
1364 
1365 	switch (buttonEvent->type)
1366 	{
1367 	case SDL_MOUSEBUTTONDOWN:
1368 		mousePress.action = MousePress::Press;
1369 		mousePresses.push_back(mousePress);
1370 
1371 		aMouseState[mouseKeyCode].pressPos.x = mouseXPos;
1372 		aMouseState[mouseKeyCode].pressPos.y = mouseYPos;
1373 		if (aMouseState[mouseKeyCode].state == KEY_UP
1374 		    || aMouseState[mouseKeyCode].state == KEY_RELEASED
1375 		    || aMouseState[mouseKeyCode].state == KEY_PRESSRELEASE)
1376 		{
1377 			// whether double click or not
1378 			if (realTime - aMouseState[mouseKeyCode].lastdown < DOUBLE_CLICK_INTERVAL)
1379 			{
1380 				aMouseState[mouseKeyCode].state = KEY_DOUBLECLICK;
1381 				aMouseState[mouseKeyCode].lastdown = 0;
1382 			}
1383 			else
1384 			{
1385 				aMouseState[mouseKeyCode].state = KEY_PRESSED;
1386 				aMouseState[mouseKeyCode].lastdown = realTime;
1387 			}
1388 
1389 			if (mouseKeyCode < MOUSE_X1) // Assume they are draggin' with either LMB|RMB|MMB
1390 			{
1391 				dragKey = mouseKeyCode;
1392 				dragX = mouseXPos;
1393 				dragY = mouseYPos;
1394 			}
1395 		}
1396 		break;
1397 	case SDL_MOUSEBUTTONUP:
1398 		mousePress.action = MousePress::Release;
1399 		mousePresses.push_back(mousePress);
1400 
1401 		aMouseState[mouseKeyCode].releasePos.x = mouseXPos;
1402 		aMouseState[mouseKeyCode].releasePos.y = mouseYPos;
1403 		if (aMouseState[mouseKeyCode].state == KEY_PRESSED)
1404 		{
1405 			aMouseState[mouseKeyCode].state = KEY_PRESSRELEASE;
1406 		}
1407 		else if (aMouseState[mouseKeyCode].state == KEY_DOWN
1408 		         || aMouseState[mouseKeyCode].state == KEY_DRAG
1409 		         || aMouseState[mouseKeyCode].state == KEY_DOUBLECLICK)
1410 		{
1411 			aMouseState[mouseKeyCode].state = KEY_RELEASED;
1412 		}
1413 		break;
1414 	default:
1415 		break;
1416 	}
1417 }
1418 
1419 /*!
1420  * Handle mousemotion events
1421  */
inputHandleMouseMotionEvent(SDL_MouseMotionEvent * motionEvent)1422 static void inputHandleMouseMotionEvent(SDL_MouseMotionEvent *motionEvent)
1423 {
1424 	switch (motionEvent->type)
1425 	{
1426 	case SDL_MOUSEMOTION:
1427 		/* store the current mouse position */
1428 		mouseXPos = (int)((float)motionEvent->x / current_displayScaleFactor);
1429 		mouseYPos = (int)((float)motionEvent->y / current_displayScaleFactor);
1430 
1431 		/* now see if a drag has started */
1432 		if ((aMouseState[dragKey].state == KEY_PRESSED ||
1433 		     aMouseState[dragKey].state == KEY_DOWN) &&
1434 		    (ABSDIF(dragX, mouseXPos) > DRAG_THRESHOLD ||
1435 		     ABSDIF(dragY, mouseYPos) > DRAG_THRESHOLD))
1436 		{
1437 			aMouseState[dragKey].state = KEY_DRAG;
1438 		}
1439 		break;
1440 	default:
1441 		break;
1442 	}
1443 }
1444 
1445 static int copied_argc = 0;
1446 static char** copied_argv = nullptr;
1447 
1448 // This stage, we only setup keycodes, and copy argc & argv for later use initializing stuff.
wzMain(int & argc,char ** argv)1449 void wzMain(int &argc, char **argv)
1450 {
1451 	initKeycodes();
1452 
1453 	// Create copies of argc and arv (for later use initializing QApplication for the script engine)
1454 	copied_argv = new char*[argc+1];
1455 	for(int i=0; i < argc; i++) {
1456 		size_t len = strlen(argv[i]) + 1;
1457 		copied_argv[i] = new char[len];
1458 		memcpy(copied_argv[i], argv[i], len);
1459 	}
1460 	copied_argv[argc] = NULL;
1461 	copied_argc = argc;
1462 }
1463 
1464 #define MIN_WZ_GAMESCREEN_WIDTH 640
1465 #define MIN_WZ_GAMESCREEN_HEIGHT 480
1466 
handleGameScreenSizeChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)1467 void handleGameScreenSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
1468 {
1469 	screenWidth = newWidth;
1470 	screenHeight = newHeight;
1471 
1472 	pie_SetVideoBufferWidth(screenWidth);
1473 	pie_SetVideoBufferHeight(screenHeight);
1474 	pie_UpdateSurfaceGeometry();
1475 
1476 	if (currentScreenResizingStatus == nullptr)
1477 	{
1478 		// The screen size change details are stored in scaled, logical units (points)
1479 		// i.e. the values expect by the game engine.
1480 		currentScreenResizingStatus = new ScreenSizeChange(oldWidth, oldHeight, screenWidth, screenHeight);
1481 	}
1482 	else
1483 	{
1484 		// update the new screen width / height, in case more than one resize message is processed this event loop
1485 		currentScreenResizingStatus->newWidth = screenWidth;
1486 		currentScreenResizingStatus->newHeight = screenHeight;
1487 	}
1488 }
1489 
handleWindowSizeChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)1490 void handleWindowSizeChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
1491 {
1492 	windowWidth = newWidth;
1493 	windowHeight = newHeight;
1494 
1495 	// NOTE: This function receives the window size in the window's logical units, but not accounting for the interface scale factor.
1496 	// Therefore, the provided old/newWidth/Height must be divided by the interface scale factor to calculate the new
1497 	// *game* screen logical width / height.
1498 	unsigned int oldScreenWidth = oldWidth / current_displayScaleFactor;
1499 	unsigned int oldScreenHeight = oldHeight / current_displayScaleFactor;
1500 	unsigned int newScreenWidth = newWidth / current_displayScaleFactor;
1501 	unsigned int newScreenHeight = newHeight / current_displayScaleFactor;
1502 
1503 	handleGameScreenSizeChange(oldScreenWidth, oldScreenHeight, newScreenWidth, newScreenHeight);
1504 
1505 	gfx_api::context::get().handleWindowSizeChange(oldWidth, oldHeight, newWidth, newHeight);
1506 }
1507 
1508 
wzGetMinimumWindowSizeForDisplayScaleFactor(unsigned int * minWindowWidth,unsigned int * minWindowHeight,float displayScaleFactor=current_displayScaleFactor)1509 void wzGetMinimumWindowSizeForDisplayScaleFactor(unsigned int *minWindowWidth, unsigned int *minWindowHeight, float displayScaleFactor = current_displayScaleFactor)
1510 {
1511 	if (minWindowWidth != nullptr)
1512 	{
1513 		*minWindowWidth = (int)ceil(MIN_WZ_GAMESCREEN_WIDTH * displayScaleFactor);
1514 	}
1515 	if (minWindowHeight != nullptr)
1516 	{
1517 		*minWindowHeight = (int)ceil(MIN_WZ_GAMESCREEN_HEIGHT * displayScaleFactor);
1518 	}
1519 }
1520 
wzGetMaximumDisplayScaleFactorsForWindowSize(unsigned int width,unsigned int height,float * horizScaleFactor,float * vertScaleFactor)1521 void wzGetMaximumDisplayScaleFactorsForWindowSize(unsigned int width, unsigned int height, float *horizScaleFactor, float *vertScaleFactor)
1522 {
1523 	if (horizScaleFactor != nullptr)
1524 	{
1525 		*horizScaleFactor = (float)width / (float)MIN_WZ_GAMESCREEN_WIDTH;
1526 	}
1527 	if (vertScaleFactor != nullptr)
1528 	{
1529 		*vertScaleFactor = (float)height / (float)MIN_WZ_GAMESCREEN_HEIGHT;
1530 	}
1531 }
1532 
wzGetMaximumDisplayScaleFactorForWindowSize(unsigned int width,unsigned int height)1533 float wzGetMaximumDisplayScaleFactorForWindowSize(unsigned int width, unsigned int height)
1534 {
1535 	float maxHorizScaleFactor = 0.f, maxVertScaleFactor = 0.f;
1536 	wzGetMaximumDisplayScaleFactorsForWindowSize(width, height, &maxHorizScaleFactor, &maxVertScaleFactor);
1537 	return std::min(maxHorizScaleFactor, maxVertScaleFactor);
1538 }
1539 
1540 // returns: the maximum display scale percentage (sourced from wzAvailableDisplayScales), or 0 if window is below the minimum required size for the minimum supported display scale
wzGetMaximumDisplayScaleForWindowSize(unsigned int width,unsigned int height)1541 unsigned int wzGetMaximumDisplayScaleForWindowSize(unsigned int width, unsigned int height)
1542 {
1543 	float maxDisplayScaleFactor = wzGetMaximumDisplayScaleFactorForWindowSize(width, height);
1544 	unsigned int maxDisplayScalePercentage = floor(maxDisplayScaleFactor * 100.f);
1545 
1546 	auto availableDisplayScales = wzAvailableDisplayScales();
1547 	std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1548 
1549 	auto maxDisplayScale = std::lower_bound(availableDisplayScales.begin(), availableDisplayScales.end(), maxDisplayScalePercentage);
1550 	if (maxDisplayScale == availableDisplayScales.end())
1551 	{
1552 		// return the largest available display scale
1553 		return availableDisplayScales.back();
1554 	}
1555 	if (*maxDisplayScale != maxDisplayScalePercentage)
1556 	{
1557 		if (maxDisplayScale == availableDisplayScales.begin())
1558 		{
1559 			// no lower display scale to return
1560 			return 0;
1561 		}
1562 		--maxDisplayScale;
1563 	}
1564 	return *maxDisplayScale;
1565 }
1566 
wzGetMaximumDisplayScaleForCurrentWindowSize()1567 unsigned int wzGetMaximumDisplayScaleForCurrentWindowSize()
1568 {
1569 	return wzGetMaximumDisplayScaleForWindowSize(windowWidth, windowHeight);
1570 }
1571 
wzGetSuggestedDisplayScaleForCurrentWindowSize(unsigned int desiredMaxScreenDimension)1572 unsigned int wzGetSuggestedDisplayScaleForCurrentWindowSize(unsigned int desiredMaxScreenDimension)
1573 {
1574 	unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForCurrentWindowSize();
1575 
1576 	auto availableDisplayScales = wzAvailableDisplayScales();
1577 	std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1578 
1579 	for (auto it = availableDisplayScales.begin(); it != availableDisplayScales.end() && (*it <= maxDisplayScale); it++)
1580 	{
1581 		auto resultingLogicalScreenWidth = (windowWidth * 100) / *it;
1582 		auto resultingLogicalScreenHeight = (windowHeight * 100) / *it;
1583 		if (resultingLogicalScreenWidth <= desiredMaxScreenDimension && resultingLogicalScreenHeight <= desiredMaxScreenDimension)
1584 		{
1585 			return *it;
1586 		}
1587 	}
1588 
1589 	return maxDisplayScale;
1590 }
1591 
wzWindowSizeIsSmallerThanMinimumRequired(unsigned int width,unsigned int height,float displayScaleFactor=current_displayScaleFactor)1592 bool wzWindowSizeIsSmallerThanMinimumRequired(unsigned int width, unsigned int height, float displayScaleFactor = current_displayScaleFactor)
1593 {
1594 	unsigned int minWindowWidth = 0, minWindowHeight = 0;
1595 	wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, displayScaleFactor);
1596 	return ((width < minWindowWidth) || (height < minWindowHeight));
1597 }
1598 
processScreenSizeChangeNotificationIfNeeded()1599 void processScreenSizeChangeNotificationIfNeeded()
1600 {
1601 	if (currentScreenResizingStatus != nullptr)
1602 	{
1603 		// WZ must process the screen size change
1604 		screen_updateGeometry(); // must come after gfx_api::context::handleWindowSizeChange
1605 		gameScreenSizeDidChange(currentScreenResizingStatus->oldWidth, currentScreenResizingStatus->oldHeight, currentScreenResizingStatus->newWidth, currentScreenResizingStatus->newHeight);
1606 		delete currentScreenResizingStatus;
1607 		currentScreenResizingStatus = nullptr;
1608 	}
1609 }
1610 
1611 #if defined(WZ_OS_WIN)
1612 
1613 # if defined(__has_include)
1614 #  if __has_include(<shellscalingapi.h>)
1615 #   include <shellscalingapi.h>
1616 #  endif
1617 # endif
1618 
1619 # if !defined(DPI_ENUMS_DECLARED)
1620 typedef enum PROCESS_DPI_AWARENESS
1621 {
1622 	PROCESS_DPI_UNAWARE = 0,
1623 	PROCESS_SYSTEM_DPI_AWARE = 1,
1624 	PROCESS_PER_MONITOR_DPI_AWARE = 2
1625 } PROCESS_DPI_AWARENESS;
1626 # endif
1627 typedef HRESULT (WINAPI *GetProcessDpiAwarenessFunction)(
1628 	HANDLE                hprocess,
1629 	PROCESS_DPI_AWARENESS *value
1630 );
1631 typedef BOOL (WINAPI *IsProcessDPIAwareFunction)();
1632 
win_IsProcessDPIAware()1633 static bool win_IsProcessDPIAware()
1634 {
1635 	HMODULE hShcore = LoadLibraryW(L"Shcore.dll");
1636 	if (hShcore != NULL)
1637 	{
1638 		const auto* func_getProcessDpiAwareness = reinterpret_cast<GetProcessDpiAwarenessFunction>(reinterpret_cast<void*>(GetProcAddress(hShcore, "GetProcessDpiAwareness")));
1639 		if (func_getProcessDpiAwareness)
1640 		{
1641 			PROCESS_DPI_AWARENESS result = PROCESS_DPI_UNAWARE;
1642 			if (func_getProcessDpiAwareness(nullptr, &result) == S_OK)
1643 			{
1644 				FreeLibrary(hShcore);
1645 				return result != PROCESS_DPI_UNAWARE;
1646 			}
1647 		}
1648 		FreeLibrary(hShcore);
1649 	}
1650 	HMODULE hUser32 = LoadLibraryW(L"User32.dll");
1651 	ASSERT_OR_RETURN(false, hUser32 != NULL, "Unable to get handle to User32?");
1652 	const auto* func_isProcessDPIAware = reinterpret_cast<IsProcessDPIAwareFunction>(reinterpret_cast<void*>(GetProcAddress(hUser32, "IsProcessDPIAware")));
1653 	bool bIsProcessDPIAware = false;
1654 	if (func_isProcessDPIAware)
1655 	{
1656 		bIsProcessDPIAware = (func_isProcessDPIAware() == TRUE);
1657 	}
1658 	FreeLibrary(hUser32);
1659 	return bIsProcessDPIAware;
1660 }
1661 #endif
1662 
1663 #if defined(WZ_OS_WIN) // currently only used on Windows
wzGetDisplayDPI(int displayIndex,float * dpi,float * baseDpi)1664 static bool wzGetDisplayDPI(int displayIndex, float* dpi, float* baseDpi)
1665 {
1666 	const float systemBaseDpi =
1667 #if defined(WZ_OS_WIN) || defined(_WIN32)
1668 	96.f;
1669 #elif defined(WZ_OS_MAC) || defined(__APPLE__)
1670 	72.f;
1671 #elif defined(__ANDROID__)
1672 	160.f;
1673 #else
1674 	// default to 96, but possibly extend if needed
1675 	96.f;
1676 #endif
1677 	if (baseDpi)
1678 	{
1679 		*baseDpi = systemBaseDpi;
1680 	}
1681 
1682 	float hdpi, vdpi;
1683 	if (SDL_GetDisplayDPI(displayIndex, nullptr, &hdpi, &vdpi) != 0)
1684 	{
1685 		debug(LOG_WARNING, "Failed to get the display (%d) DPI because : %s", displayIndex, SDL_GetError());
1686 		return false;
1687 	}
1688 	if (dpi)
1689 	{
1690 		*dpi = std::min(hdpi, vdpi);
1691 	}
1692 	return true;
1693 }
1694 #endif
1695 
wzGetDefaultBaseDisplayScale(int displayIndex)1696 unsigned int wzGetDefaultBaseDisplayScale(int displayIndex)
1697 {
1698 #if defined(WZ_OS_WIN)
1699 	// SDL does not yet have built-in "high-DPI display" support on Windows (as of SDL 2.0.14)
1700 	// Thus, all adjustments must be made using the Display Scale feature in WZ itself
1701 	// Calculate a good base game Display Scale based on the actual screen display scale
1702 	if (!win_IsProcessDPIAware())
1703 	{
1704 		return 100;
1705 	}
1706 	float dpi, baseDpi;
1707 	if (!wzGetDisplayDPI(displayIndex, &dpi, &baseDpi))
1708 	{
1709 		// Failed to get the display DPI
1710 		return 100;
1711 	}
1712 	unsigned int approxActualDisplayScale = static_cast<unsigned int>(ceil((dpi / baseDpi) * 100.f));
1713 	auto availableDisplayScales = wzAvailableDisplayScales();
1714 	std::sort(availableDisplayScales.begin(), availableDisplayScales.end());
1715 	auto displayScale = std::lower_bound(availableDisplayScales.begin(), availableDisplayScales.end(), approxActualDisplayScale);
1716 	if (displayScale == availableDisplayScales.end())
1717 	{
1718 		// return the largest available display scale
1719 		return availableDisplayScales.back();
1720 	}
1721 	if (*displayScale != approxActualDisplayScale)
1722 	{
1723 		if (displayScale == availableDisplayScales.begin())
1724 		{
1725 			// no lower display scale to return
1726 			return 100;
1727 		}
1728 		--displayScale;
1729 	}
1730 	return *displayScale;
1731 #elif defined(WZ_OS_MAC)
1732 	// SDL has built-in "high-DPI display" support on Apple platforms
1733 	// Just return 100%
1734 	return 100;
1735 #else
1736 	// SDL has built-in "high-DPI display" support on many other platforms (Wayland [SDL 2.0.10+])
1737 	// For now, rely on that, and just return 100%
1738 	return 100;
1739 #endif
1740 }
1741 
wzChangeDisplayScale(unsigned int displayScale)1742 bool wzChangeDisplayScale(unsigned int displayScale)
1743 {
1744 	if (WZwindow == nullptr)
1745 	{
1746 		debug(LOG_WARNING, "wzChangeDisplayScale called when window is not available");
1747 		return false;
1748 	}
1749 
1750 	float newDisplayScaleFactor = (float)displayScale / 100.f;
1751 
1752 	if (wzWindowSizeIsSmallerThanMinimumRequired(windowWidth, windowHeight, newDisplayScaleFactor))
1753 	{
1754 		// The current window width and/or height are below the required minimum window size
1755 		// for this display scale factor.
1756 		return false;
1757 	}
1758 
1759 	// Store the new display scale factor
1760 	setDisplayScale(displayScale);
1761 
1762 	// Set the new minimum window size
1763 	unsigned int minWindowWidth = 0, minWindowHeight = 0;
1764 	wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, newDisplayScaleFactor);
1765 	SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
1766 
1767 	// Update the game's logical screen size
1768 	unsigned int oldScreenWidth = screenWidth, oldScreenHeight = screenHeight;
1769 	unsigned int newScreenWidth = windowWidth, newScreenHeight = windowHeight;
1770 	if (newDisplayScaleFactor > 1.0)
1771 	{
1772 		newScreenWidth = windowWidth / newDisplayScaleFactor;
1773 		newScreenHeight = windowHeight / newDisplayScaleFactor;
1774 	}
1775 	handleGameScreenSizeChange(oldScreenWidth, oldScreenHeight, newScreenWidth, newScreenHeight);
1776 	gameDisplayScaleFactorDidChange(newDisplayScaleFactor);
1777 
1778 	// Update the current mouse coordinates
1779 	// (The prior stored mouseXPos / mouseYPos apply to the old coordinate system, and must be translated to the
1780 	// new game coordinate system. Since the mouse hasn't moved - or it would generate events that override this -
1781 	// the current position with respect to the window (which hasn't changed size) can be queried and used to
1782 	// calculate the new game coordinate system mouse position.)
1783 	//
1784 	int windowMouseXPos = 0, windowMouseYPos = 0;
1785 	SDL_GetMouseState(&windowMouseXPos, &windowMouseYPos);
1786 	debug(LOG_WZ, "Old mouse position: %d, %d", mouseXPos, mouseYPos);
1787 	mouseXPos = (int)((float)windowMouseXPos / current_displayScaleFactor);
1788 	mouseYPos = (int)((float)windowMouseYPos / current_displayScaleFactor);
1789 	debug(LOG_WZ, "New mouse position: %d, %d", mouseXPos, mouseYPos);
1790 
1791 
1792 	processScreenSizeChangeNotificationIfNeeded();
1793 
1794 	return true;
1795 }
1796 
wzChangeWindowResolution(int screen,unsigned int width,unsigned int height)1797 bool wzChangeWindowResolution(int screen, unsigned int width, unsigned int height)
1798 {
1799 	if (WZwindow == nullptr)
1800 	{
1801 		debug(LOG_WARNING, "wzChangeWindowResolution called when window is not available");
1802 		return false;
1803 	}
1804 	debug(LOG_WZ, "Attempt to change resolution to [%d] %dx%d", screen, width, height);
1805 
1806 #if defined(WZ_OS_MAC)
1807 	// Workaround for SDL (2.0.5) quirk on macOS:
1808 	//	When the green titlebar button is used to fullscreen the app in a new space:
1809 	//		- SDL does not return SDL_WINDOW_MAXIMIZED nor SDL_WINDOW_FULLSCREEN.
1810 	//		- Attempting to change the window resolution "succeeds" (in that the new window size is "set" and returned
1811 	//		  by the SDL GetWindowSize functions).
1812 	//		- But other things break (ex. mouse coordinate translation) if the resolution is changed while the window
1813 	//        is maximized in this way.
1814 	//		- And the GL drawable size remains unchanged.
1815 	//		- So if it's been fullscreened by the user like this, but doesn't show as SDL_WINDOW_FULLSCREEN,
1816 	//		  prevent window resolution changes.
1817 	if (cocoaIsSDLWindowFullscreened(WZwindow) && !wzIsFullscreen())
1818 	{
1819 		debug(LOG_WZ, "The main window is fullscreened, but SDL doesn't think it is. Changing window resolution is not possible in this state. (SDL Bug).");
1820 		return false;
1821 	}
1822 #endif
1823 
1824 	// Get current window size + position + bounds
1825 	int prev_x = 0, prev_y = 0, prev_width = 0, prev_height = 0;
1826 	SDL_GetWindowPosition(WZwindow, &prev_x, &prev_y);
1827 	SDL_GetWindowSize(WZwindow, &prev_width, &prev_height);
1828 
1829 	// Get the usable bounds for the current screen
1830 	SDL_Rect bounds;
1831 	if (wzIsFullscreen())
1832 	{
1833 		// When in fullscreen mode, obtain the screen's overall bounds
1834 		if (SDL_GetDisplayBounds(screen, &bounds) != 0) {
1835 			debug(LOG_ERROR, "Failed to get display bounds for screen: %d", screen);
1836 			return false;
1837 		}
1838 		debug(LOG_WZ, "SDL_GetDisplayBounds for screen [%d]: pos %d x %d : res %d x %d", screen, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
1839 	}
1840 	else
1841 	{
1842 		// When in windowed mode, obtain the screen's *usable* display bounds
1843 		if (SDL_GetDisplayUsableBounds(screen, &bounds) != 0) {
1844 			debug(LOG_ERROR, "Failed to get usable display bounds for screen: %d", screen);
1845 			return false;
1846 		}
1847 		debug(LOG_WZ, "SDL_GetDisplayUsableBounds for screen [%d]: pos %d x %d : WxH %d x %d", screen, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
1848 
1849 		// Verify that the desired window size does not exceed the usable bounds of the specified display.
1850 		if ((width > bounds.w) || (height > bounds.h))
1851 		{
1852 			debug(LOG_WZ, "Unable to change window size to (%d x %d) because it is larger than the screen's usable bounds", width, height);
1853 			return false;
1854 		}
1855 	}
1856 
1857 	// Check whether the desired window size is smaller than the minimum required for the current Display Scale
1858 	unsigned int priorDisplayScale = current_displayScale;
1859 	if (wzWindowSizeIsSmallerThanMinimumRequired(width, height))
1860 	{
1861 		// The new window size is smaller than the minimum required size for the current display scale level.
1862 
1863 		unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(width, height);
1864 		if (maxDisplayScale < 100)
1865 		{
1866 			// Cannot adjust display scale factor below 1. Desired window size is below the minimum supported.
1867 			debug(LOG_WZ, "Unable to change window size to (%d x %d) because it is smaller than the minimum supported at a 100%% display scale", width, height);
1868 			return false;
1869 		}
1870 
1871 		// Adjust the current display scale level to the nearest supported level.
1872 		debug(LOG_WZ, "The current Display Scale (%d%%) is too high for the desired window size. Reducing the current Display Scale to the maximum possible for the desired window size: %d%%.", current_displayScale, maxDisplayScale);
1873 		wzChangeDisplayScale(maxDisplayScale);
1874 
1875 		// Store the new display scale
1876 		war_SetDisplayScale(maxDisplayScale);
1877 	}
1878 
1879 	// Position the window (centered) on the screen (for its upcoming new size)
1880 	SDL_SetWindowPosition(WZwindow, SDL_WINDOWPOS_CENTERED_DISPLAY(screen), SDL_WINDOWPOS_CENTERED_DISPLAY(screen));
1881 
1882 	// Change the window size
1883 	// NOTE: Changing the window size will trigger an SDL window size changed event which will handle recalculating layout.
1884 	SDL_SetWindowSize(WZwindow, width, height);
1885 
1886 	// Check that the new size is the desired size
1887 	int resultingWidth, resultingHeight = 0;
1888 	SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
1889 	if (resultingWidth != width || resultingHeight != height) {
1890 		// Attempting to set the resolution failed
1891 		debug(LOG_WZ, "Attempting to change the resolution to %dx%d seems to have failed (result: %dx%d).", width, height, resultingWidth, resultingHeight);
1892 
1893 		// Revert to the prior position + resolution + display scale, and return false
1894 		SDL_SetWindowSize(WZwindow, prev_width, prev_height);
1895 		SDL_SetWindowPosition(WZwindow, prev_x, prev_y);
1896 		if (current_displayScale != priorDisplayScale)
1897 		{
1898 			// Reverse the correction applied to the Display Scale to support the desired resolution.
1899 			wzChangeDisplayScale(priorDisplayScale);
1900 			war_SetDisplayScale(priorDisplayScale);
1901 		}
1902 		return false;
1903 	}
1904 
1905 	// Store the updated screenIndex
1906 	screenIndex = screen;
1907 
1908 	return true;
1909 }
1910 
1911 // Returns the current window screen, width, and height
wzGetWindowResolution(int * screen,unsigned int * width,unsigned int * height)1912 void wzGetWindowResolution(int *screen, unsigned int *width, unsigned int *height)
1913 {
1914 	ASSERT_OR_RETURN(, WZwindow != nullptr, "wzGetWindowResolution called when window is not available");
1915 
1916 	if (screen != nullptr)
1917 	{
1918 		*screen = screenIndex;
1919 	}
1920 
1921 	int currentWidth = 0, currentHeight = 0;
1922 	SDL_GetWindowSize(WZwindow, &currentWidth, &currentHeight);
1923 	assert(currentWidth >= 0);
1924 	assert(currentHeight >= 0);
1925 	if (width != nullptr)
1926 	{
1927 		*width = currentWidth;
1928 	}
1929 	if (height != nullptr)
1930 	{
1931 		*height = currentHeight;
1932 	}
1933 }
1934 
SDL_backend(const video_backend & backend)1935 static SDL_WindowFlags SDL_backend(const video_backend& backend)
1936 {
1937 	switch (backend)
1938 	{
1939 #if defined(WZ_BACKEND_DIRECTX)
1940 		case video_backend::directx: // because DirectX is supported via OpenGLES (LibANGLE)
1941 #endif
1942 		case video_backend::opengl:
1943 		case video_backend::opengles:
1944 			return SDL_WINDOW_OPENGL;
1945 		case video_backend::vulkan:
1946 #if SDL_VERSION_ATLEAST(2, 0, 6)
1947 			return SDL_WINDOW_VULKAN;
1948 #else
1949 			debug(LOG_FATAL, "The version of SDL used for compilation does not support SDL_WINDOW_VULKAN");
1950 			break;
1951 #endif
1952 		case video_backend::num_backends:
1953 			debug(LOG_FATAL, "Should never happen");
1954 			break;
1955 	}
1956 	return SDL_WindowFlags{};
1957 }
1958 
shouldResetGfxBackendPrompt(video_backend currentBackend,video_backend newBackend,std::string failedToInitializeObject="graphics",std::string additionalErrorDetails="")1959 bool shouldResetGfxBackendPrompt(video_backend currentBackend, video_backend newBackend, std::string failedToInitializeObject = "graphics", std::string additionalErrorDetails = "")
1960 {
1961 	// Offer to reset to the specified gfx backend
1962 	std::string resetString = std::string("Reset to ") + to_display_string(newBackend) + "";
1963 	const SDL_MessageBoxButtonData buttons[] = {
1964 	   { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, resetString.c_str() },
1965 	   { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 2, "Not Now" },
1966 	};
1967 	std::string titleString = std::string("Warzone: Failed to initialize ") + failedToInitializeObject;
1968 	std::string messageString = std::string("Failed to initialize ") + failedToInitializeObject + " for backend: " + to_display_string(currentBackend) + ".\n\n";
1969 	if (!additionalErrorDetails.empty())
1970 	{
1971 		messageString += "Error Details: \n\"" + additionalErrorDetails + "\"\n\n";
1972 	}
1973 	messageString += "Do you want to reset the graphics backend to: " + to_display_string(newBackend) + "?";
1974 	const SDL_MessageBoxData messageboxdata = {
1975 		SDL_MESSAGEBOX_ERROR, /* .flags */
1976 		WZwindow, /* .window */
1977 		titleString.c_str(), /* .title */
1978 		messageString.c_str(), /* .message */
1979 		SDL_arraysize(buttons), /* .numbuttons */
1980 		buttons, /* .buttons */
1981 		nullptr /* .colorScheme */
1982 	};
1983 	int buttonid;
1984 	if (SDL_ShowMessageBox(&messageboxdata, &buttonid) < 0) {
1985 		// error displaying message box
1986 		debug(LOG_FATAL, "Failed to display message box");
1987 		return false;
1988 	}
1989 	if (buttonid == 1)
1990 	{
1991 		return true;
1992 	}
1993 	return false;
1994 }
1995 
resetGfxBackend(video_backend newBackend,bool displayRestartMessage=true)1996 void resetGfxBackend(video_backend newBackend, bool displayRestartMessage = true)
1997 {
1998 	war_setGfxBackend(newBackend);
1999 	if (displayRestartMessage)
2000 	{
2001 		std::string title = std::string("Backend reset to: ") + to_display_string(newBackend);
2002 		wzDisplayDialog(Dialog_Information, title.c_str(), "(Note: Do not specify a --gfxbackend option, or it will override this new setting.)\n\nPlease restart Warzone 2100 to use the new graphics setting.");
2003 	}
2004 }
2005 
wzSDLOneTimeInit()2006 bool wzSDLOneTimeInit()
2007 {
2008 	const Uint32 sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_TIMER;
2009 	if (!(SDL_WasInit(sdl_init_flags) == sdl_init_flags))
2010 	{
2011 		if (SDL_Init(sdl_init_flags) != 0)
2012 		{
2013 			debug(LOG_ERROR, "Error: Could not initialise SDL (%s).", SDL_GetError());
2014 			return false;
2015 		}
2016 
2017 		if ((SDL_IsTextInputActive() != SDL_FALSE) && (GetTextEventsOwner == nullptr))
2018 		{
2019 			// start text input disabled
2020 			SDL_StopTextInput();
2021 		}
2022 	}
2023 
2024 	if (wzSDLAppEvent == ((Uint32)-1))
2025 	{
2026 		wzSDLAppEvent = SDL_RegisterEvents(1);
2027 		if (wzSDLAppEvent == ((Uint32)-1))
2028 		{
2029 			// Failed to register app-defined event with SDL
2030 			debug(LOG_ERROR, "Error: Failed to register app-defined SDL event (%s).", SDL_GetError());
2031 			return false;
2032 		}
2033 	}
2034 
2035 	return true;
2036 }
2037 
wzSDLOneTimeInitSubsystem(uint32_t subsystem_flag)2038 static bool wzSDLOneTimeInitSubsystem(uint32_t subsystem_flag)
2039 {
2040 	if (SDL_WasInit(subsystem_flag) == subsystem_flag)
2041 	{
2042 		// already initialized
2043 		return true;
2044 	}
2045 	if (SDL_InitSubSystem(subsystem_flag) != 0)
2046 	{
2047 		debug(LOG_WZ, "SDL_InitSubSystem(%" PRIu32 ") failed", subsystem_flag);
2048 		return false;
2049 	}
2050 	if ((SDL_IsTextInputActive() != SDL_FALSE) && (GetTextEventsOwner == nullptr))
2051 	{
2052 		// start text input disabled
2053 		SDL_StopTextInput();
2054 	}
2055 	return true;
2056 }
2057 
wzSDLPreWindowCreate_InitOpenGLAttributes(bool antialiasing,bool useOpenGLES,bool useOpenGLESLibrary)2058 void wzSDLPreWindowCreate_InitOpenGLAttributes(bool antialiasing, bool useOpenGLES, bool useOpenGLESLibrary)
2059 {
2060 	// Set OpenGL attributes before creating the SDL Window
2061 
2062 	// Set minimum number of bits for the RGB channels of the color buffer
2063 	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
2064 	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
2065 	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
2066 #if defined(WZ_OS_WIN)
2067 	if (useOpenGLES)
2068 	{
2069 		// Always force minimum 8-bit color channels when using OpenGL ES on Windows
2070 		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
2071 		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
2072 		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
2073 	}
2074 #endif
2075 
2076 	// Set the double buffer OpenGL attribute.
2077 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
2078 
2079 	// Request a 24-bit depth buffer.
2080 	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
2081 
2082 	// Enable stencil buffer, needed for shadows to work.
2083 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
2084 
2085 	if (antialiasing)
2086 	{
2087 		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
2088 		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing);
2089 	}
2090 
2091 	if (!sdl_OpenGL_Impl::configureOpenGLContextRequest(sdl_OpenGL_Impl::getInitialContextRequest(useOpenGLES), useOpenGLESLibrary))
2092 	{
2093 		// Failed to configure OpenGL context request
2094 		debug(LOG_FATAL, "Failed to configure OpenGL context request");
2095 		SDL_Quit();
2096 		exit(EXIT_FAILURE);
2097 	}
2098 }
2099 
wzSDLPreWindowCreate_InitVulkanLibrary()2100 void wzSDLPreWindowCreate_InitVulkanLibrary()
2101 {
2102 #if defined(WZ_OS_MAC)
2103 	// Attempt to explicitly load libvulkan.dylib with a full path
2104 	// to support situations where run-time search paths using @executable_path are prohibited
2105 	std::string fullPathToVulkanLibrary = cocoaGetFrameworksPath("libvulkan.dylib");
2106 	if (SDL_Vulkan_LoadLibrary(fullPathToVulkanLibrary.c_str()) != 0)
2107 	{
2108 		debug(LOG_ERROR, "Failed to explicitly load Vulkan library");
2109 	}
2110 #else
2111 	// rely on SDL's built-in loading paths / behavior
2112 #endif
2113 }
2114 
2115 // This stage, we handle display mode setting
wzMainScreenSetup_CreateVideoWindow(const video_backend & backend,int antialiasing,WINDOW_MODE fullscreen,int vsync,bool highDPI)2116 optional<SDL_gfx_api_Impl_Factory::Configuration> wzMainScreenSetup_CreateVideoWindow(const video_backend& backend, int antialiasing, WINDOW_MODE fullscreen, int vsync, bool highDPI)
2117 {
2118 	const bool useOpenGLES = (backend == video_backend::opengles)
2119 #if defined(WZ_BACKEND_DIRECTX)
2120 		|| (backend == video_backend::directx)
2121 #endif
2122 	;
2123 	const bool useOpenGLESLibrary = false
2124 #if defined(WZ_BACKEND_DIRECTX)
2125 		|| (backend == video_backend::directx)
2126 #endif
2127 	;
2128 	const bool usesSDLBackend_OpenGL = useOpenGLES || (backend == video_backend::opengl);
2129 
2130 	// populate with the saved configuration values (if we had any)
2131 	int width = war_GetWidth();
2132 	int height = war_GetHeight();
2133 	// NOTE: Prior to wzMainScreenSetup being run, the display system is populated with the window width + height
2134 	// (i.e. not taking into account the game display scale). This function later sets the display system
2135 	// to the *game screen* width and height (taking into account the display scale).
2136 
2137 	// Initialize video subsystem (if not yet initialized)
2138 	if (!wzSDLOneTimeInitSubsystem(SDL_INIT_VIDEO))
2139 	{
2140 		debug(LOG_FATAL, "Error: Could not initialise SDL video subsystem (%s).", SDL_GetError());
2141 		SDL_Quit();
2142 		exit(EXIT_FAILURE);
2143 	}
2144 
2145 	if (usesSDLBackend_OpenGL)
2146 	{
2147 		wzSDLPreWindowCreate_InitOpenGLAttributes(antialiasing, useOpenGLES, useOpenGLESLibrary);
2148 	}
2149 	else if (backend == video_backend::vulkan)
2150 	{
2151 		wzSDLPreWindowCreate_InitVulkanLibrary();
2152 	}
2153 
2154 	// Populated our resolution list (does all displays now)
2155 	SDL_DisplayMode	displaymode;
2156 	struct screeninfo screenlist;
2157 	for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i)		// How many monitors we got
2158 	{
2159 		int numdisplaymodes = SDL_GetNumDisplayModes(i);	// Get the number of display modes on this monitor
2160 		for (int j = 0; j < numdisplaymodes; j++)
2161 		{
2162 			displaymode.format = displaymode.w = displaymode.h = displaymode.refresh_rate = 0;
2163 			displaymode.driverdata = 0;
2164 			if (SDL_GetDisplayMode(i, j, &displaymode) < 0)
2165 			{
2166 				debug(LOG_FATAL, "SDL_LOG_CATEGORY_APPLICATION error:%s", SDL_GetError());
2167 				SDL_Quit();
2168 				exit(EXIT_FAILURE);
2169 			}
2170 
2171 			debug(LOG_WZ, "Monitor [%d] %dx%d %d %s", i, displaymode.w, displaymode.h, displaymode.refresh_rate, SDL_GetPixelFormatName(displaymode.format));
2172 			if ((displaymode.w < MIN_WZ_GAMESCREEN_WIDTH) || (displaymode.h < MIN_WZ_GAMESCREEN_HEIGHT))
2173 			{
2174 				debug(LOG_WZ, "Monitor mode resolution < %d x %d -- discarding entry", MIN_WZ_GAMESCREEN_WIDTH, MIN_WZ_GAMESCREEN_HEIGHT);
2175 			}
2176 			else if (displaymode.refresh_rate < 59)
2177 			{
2178 				debug(LOG_WZ, "Monitor mode refresh rate < 59 -- discarding entry");
2179 				// only store 60Hz & higher modes, some display report 59 on Linux
2180 			}
2181 			else
2182 			{
2183 				screenlist.width = displaymode.w;
2184 				screenlist.height = displaymode.h;
2185 				screenlist.refresh_rate = displaymode.refresh_rate;
2186 				screenlist.screen = i;		// which monitor this belongs to
2187 				displaylist.push_back(screenlist);
2188 			}
2189 		}
2190 	}
2191 
2192 	SDL_DisplayMode current = { 0, 0, 0, 0, 0 };
2193 	for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i)
2194 	{
2195 		int display = SDL_GetCurrentDisplayMode(i, &current);
2196 		if (display != 0)
2197 		{
2198 			debug(LOG_FATAL, "Can't get the current display mode, because: %s", SDL_GetError());
2199 			SDL_Quit();
2200 			exit(EXIT_FAILURE);
2201 		}
2202 		debug(LOG_WZ, "Monitor [%d] %dx%d %d", i, current.w, current.h, current.refresh_rate);
2203 	}
2204 
2205 	if (width == 0 || height == 0)
2206 	{
2207 		width = windowWidth = current.w;
2208 		height = windowHeight = current.h;
2209 	}
2210 	else
2211 	{
2212 		windowWidth = width;
2213 		windowHeight = height;
2214 	}
2215 
2216 	setDisplayScale(war_GetDisplayScale());
2217 
2218 	SDL_Rect bounds;
2219 	for (int i = 0; i < SDL_GetNumVideoDisplays(); i++)
2220 	{
2221 		SDL_GetDisplayBounds(i, &bounds);
2222 		debug(LOG_WZ, "Monitor %d: pos %d x %d : res %d x %d", i, (int)bounds.x, (int)bounds.y, (int)bounds.w, (int)bounds.h);
2223 	}
2224 	screenIndex = war_GetScreen();
2225 	const int currentNumDisplays = SDL_GetNumVideoDisplays();
2226 	if (currentNumDisplays < 1)
2227 	{
2228 		debug(LOG_FATAL, "SDL_GetNumVideoDisplays returned: %d, with error: %s", currentNumDisplays, SDL_GetError());
2229 		SDL_Quit();
2230 		exit(EXIT_FAILURE);
2231 	}
2232 	if (screenIndex > currentNumDisplays)
2233 	{
2234 		debug(LOG_WARNING, "Invalid screen [%d] defined in configuration; there are only %d displays; falling back to display 0", screenIndex, currentNumDisplays);
2235 		screenIndex = 0;
2236 		war_SetScreen(0);
2237 	}
2238 
2239 	if (war_getAutoAdjustDisplayScale())
2240 	{
2241 		// Since SDL (at least <= 2.0.14) does not provide built-in support for high-DPI displays on *some* platforms
2242 		// (example: Windows), do our best to bump up the game's Display Scale setting to be a better match if the screen
2243 		// on which the game starts has a higher effective Display Scale than the game's starting Display Scale setting.
2244 		unsigned int screenBaseDisplayScale = wzGetDefaultBaseDisplayScale(screenIndex);
2245 		if (screenBaseDisplayScale > wzGetCurrentDisplayScale())
2246 		{
2247 			// When bumping up the display scale, also increase the target window size proportionally
2248 			debug(LOG_WZ, "Increasing game Display Scale to better match calculated default base display scale: %u%% -> %u%%", wzGetCurrentDisplayScale(), screenBaseDisplayScale);
2249 			unsigned int priorDisplayScale = wzGetCurrentDisplayScale();
2250 			setDisplayScale(screenBaseDisplayScale);
2251 			float displayScaleDiff = (float)screenBaseDisplayScale / (float)priorDisplayScale;
2252 			windowWidth = static_cast<unsigned int>(ceil((float)windowWidth * displayScaleDiff));
2253 			windowHeight = static_cast<unsigned int>(ceil((float)windowHeight * displayScaleDiff));
2254 			war_SetDisplayScale(screenBaseDisplayScale); // save the new display scale configuration
2255 		}
2256 	}
2257 
2258 	// Calculate the minimum window size given the current display scale
2259 	unsigned int minWindowWidth = 0, minWindowHeight = 0;
2260 	wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2261 
2262 	if ((windowWidth < minWindowWidth) || (windowHeight < minWindowHeight))
2263 	{
2264 		// The desired window width and/or height is lower than the required minimum for the current display scale.
2265 		// Reduce the display scale to the maximum supported (for the desired window size), and recalculate the required minimum window size.
2266 		unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(windowWidth, windowHeight);
2267 		maxDisplayScale = std::max(100u, maxDisplayScale); // if wzGetMaximumDisplayScaleForWindowSize fails, it returns < 100
2268 		setDisplayScale(maxDisplayScale);
2269 		war_SetDisplayScale(maxDisplayScale); // save the new display scale configuration
2270 		wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2271 	}
2272 
2273 	windowWidth = std::max(windowWidth, minWindowWidth);
2274 	windowHeight = std::max(windowHeight, minWindowHeight);
2275 
2276 	auto supportedFullscreenModes = wzSupportedWindowModes();
2277 	if (std::find(supportedFullscreenModes.begin(), supportedFullscreenModes.end(), fullscreen) == supportedFullscreenModes.end())
2278 	{
2279 		debug(LOG_ERROR, "Unsupported fullscreen mode specified: %d; using default", static_cast<int>(fullscreen));
2280 		fullscreen = WINDOW_MODE::windowed;
2281 		war_setWindowMode(fullscreen); // persist the change
2282 	}
2283 
2284 	if (fullscreen == WINDOW_MODE::windowed)
2285 	{
2286 		// Determine the maximum usable windowed size for this display/screen
2287 		SDL_Rect displayUsableBounds = { 0, 0, 0, 0 };
2288 		if (SDL_GetDisplayUsableBounds(screenIndex, &displayUsableBounds) == 0)
2289 		{
2290 			if (displayUsableBounds.w > 0 && displayUsableBounds.h > 0)
2291 			{
2292 				windowWidth = std::min((unsigned int)displayUsableBounds.w, windowWidth);
2293 				windowHeight = std::min((unsigned int)displayUsableBounds.h, windowHeight);
2294 			}
2295 		}
2296 	}
2297 
2298 	//// The flags to pass to SDL_CreateWindow
2299 	int video_flags  = SDL_backend(backend) | SDL_WINDOW_SHOWN;
2300 
2301 	switch (fullscreen)
2302 	{
2303 		case WINDOW_MODE::desktop_fullscreen:
2304 			video_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
2305 			break;
2306 		case WINDOW_MODE::fullscreen:
2307 			video_flags |= SDL_WINDOW_FULLSCREEN;
2308 			break;
2309 		case WINDOW_MODE::windowed:
2310 			// Allow the window to be manually resized, if not fullscreen
2311 			video_flags |= SDL_WINDOW_RESIZABLE;
2312 			break;
2313 	}
2314 
2315 	if (highDPI)
2316 	{
2317 		// Allow SDL to enable its built-in High-DPI display support.
2318 		// This flag is ignored on some platforms (ex. Windows is not supported as of SDL 2.0.10),
2319 		// but does support: macOS, Wayland [SDL 2.0.10+].
2320 		video_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
2321 	}
2322 
2323 	WZwindow = SDL_CreateWindow(PACKAGE_NAME, SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), windowWidth, windowHeight, video_flags);
2324 
2325 	if (!WZwindow)
2326 	{
2327 		std::string createWindowErrorStr = SDL_GetError();
2328 		video_backend defaultBackend = wzGetNextFallbackGfxBackendForCurrentSystem(backend);
2329 		if ((backend != defaultBackend) && shouldResetGfxBackendPrompt(backend, defaultBackend, "window", createWindowErrorStr))
2330 		{
2331 			resetGfxBackend(defaultBackend);
2332 			return nullopt; // must return so new configuration will be saved
2333 		}
2334 		else
2335 		{
2336 			debug(LOG_FATAL, "Can't create a window, because: %s", createWindowErrorStr.c_str());
2337 		}
2338 		SDL_Quit();
2339 		exit(EXIT_FAILURE);
2340 	}
2341 
2342 	// Check that the actual window size matches the desired window size
2343 	int resultingWidth, resultingHeight = 0;
2344 	SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
2345 	if (resultingWidth < minWindowWidth || resultingHeight < minWindowHeight)
2346 	{
2347 		// The created window size (that the system returned) is less than the minimum required window size (for this display scale)
2348 		debug(LOG_WARNING, "Failed to create window at desired resolution: [%d] %d x %d; instead, received window of resolution: [%d] %d x %d; which is below the required minimum size of %d x %d for the current display scale level", war_GetScreen(), windowWidth, windowHeight, war_GetScreen(), resultingWidth, resultingHeight, minWindowWidth, minWindowHeight);
2349 
2350 		// Adjust the display scale (if possible)
2351 		unsigned int maxDisplayScale = wzGetMaximumDisplayScaleForWindowSize(resultingWidth, resultingHeight);
2352 		if (maxDisplayScale >= 100)
2353 		{
2354 			// Reduce the display scale
2355 			debug(LOG_WARNING, "Reducing the display scale level to the maximum supported for this window size: %u", maxDisplayScale);
2356 			setDisplayScale(maxDisplayScale);
2357 			war_SetDisplayScale(maxDisplayScale); // save the new display scale configuration
2358 			wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2359 		}
2360 		else
2361 		{
2362 			// Problem: The resulting window size that the system provided is below the minimum required
2363 			//          for the lowest possible display scale - i.e. the window is just too small
2364 			debug(LOG_ERROR, "The window size created by the system is too small!");
2365 
2366 			// TODO: Should probably exit with a fatal error here?
2367 			// For now...
2368 
2369 			// Attempt to default to base resolution at 100%
2370 			setDisplayScale(100);
2371 			war_SetDisplayScale(100); // save the new display scale configuration
2372 			wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight);
2373 
2374 			SDL_SetWindowSize(WZwindow, minWindowWidth, minWindowHeight);
2375 			windowWidth = minWindowWidth;
2376 			windowHeight = minWindowHeight;
2377 
2378 			// Center window on screen
2379 			SDL_SetWindowPosition(WZwindow, SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(screenIndex));
2380 
2381 			// Re-request resulting window size
2382 			SDL_GetWindowSize(WZwindow, &resultingWidth, &resultingHeight);
2383 		}
2384 	}
2385 	ASSERT(resultingWidth > 0 && resultingHeight > 0, "Invalid - SDL returned window width: %d, window height: %d", resultingWidth, resultingHeight);
2386 	windowWidth = (unsigned int)resultingWidth;
2387 	windowHeight = (unsigned int)resultingHeight;
2388 
2389 	// Calculate the game screen's logical dimensions
2390 	screenWidth = windowWidth;
2391 	screenHeight = windowHeight;
2392 	if (current_displayScaleFactor > 1.0f)
2393 	{
2394 		screenWidth = windowWidth / current_displayScaleFactor;
2395 		screenHeight = windowHeight / current_displayScaleFactor;
2396 	}
2397 	pie_SetVideoBufferWidth(screenWidth);
2398 	pie_SetVideoBufferHeight(screenHeight);
2399 
2400 	// Set the minimum window size
2401 	SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
2402 
2403 #if !defined(WZ_OS_MAC) // Do not use this method to set the window icon on macOS.
2404 
2405 	#if SDL_BYTEORDER == SDL_BIG_ENDIAN
2406 		uint32_t rmask = 0xff000000;
2407 		uint32_t gmask = 0x00ff0000;
2408 		uint32_t bmask = 0x0000ff00;
2409 		uint32_t amask = 0x000000ff;
2410 	#else
2411 		uint32_t rmask = 0x000000ff;
2412 		uint32_t gmask = 0x0000ff00;
2413 		uint32_t bmask = 0x00ff0000;
2414 		uint32_t amask = 0xff000000;
2415 	#endif
2416 
2417 #if defined(__GNUC__)
2418 	#pragma GCC diagnostic push
2419 	// ignore warning: cast from type 'const unsigned char*' to type 'void*' casts away qualifiers [-Wcast-qual]
2420 	// FIXME?
2421 	#pragma GCC diagnostic ignored "-Wcast-qual"
2422 #endif
2423 	SDL_Surface *surface_icon = SDL_CreateRGBSurfaceFrom((void *)wz2100icon.pixel_data, wz2100icon.width, wz2100icon.height, wz2100icon.bytes_per_pixel * 8,
2424 	                            wz2100icon.width * wz2100icon.bytes_per_pixel, rmask, gmask, bmask, amask);
2425 #if defined(__GNUC__)
2426 	#pragma GCC diagnostic pop
2427 #endif
2428 
2429 	if (surface_icon)
2430 	{
2431 		SDL_SetWindowIcon(WZwindow, surface_icon);
2432 		SDL_FreeSurface(surface_icon);
2433 	}
2434 	else
2435 	{
2436 		debug(LOG_ERROR, "Could not set window icon because %s", SDL_GetError());
2437 	}
2438 #endif
2439 
2440 	SDL_SetWindowTitle(WZwindow, PACKAGE_NAME);
2441 
2442 	SDL_gfx_api_Impl_Factory::Configuration sdl_impl_config;
2443 	sdl_impl_config.useOpenGLES = useOpenGLES;
2444 	sdl_impl_config.useOpenGLESLibrary = useOpenGLESLibrary;
2445 	return sdl_impl_config;
2446 }
2447 
wzMainScreenSetup_VerifyWindow()2448 bool wzMainScreenSetup_VerifyWindow()
2449 {
2450 	int bitDepth = war_GetVideoBufferDepth();
2451 
2452 	int bpp = SDL_BITSPERPIXEL(SDL_GetWindowPixelFormat(WZwindow));
2453 	debug(LOG_WZ, "Bpp = %d format %s" , bpp, SDL_GetPixelFormatName(SDL_GetWindowPixelFormat(WZwindow)));
2454 	if (!bpp)
2455 	{
2456 		debug(LOG_ERROR, "Video mode %ux%u@%dbpp is not supported!", windowWidth, windowHeight, bitDepth);
2457 		return false;
2458 	}
2459 	switch (bpp)
2460 	{
2461 	case 32:
2462 	case 24:		// all is good...
2463 		break;
2464 	case 16:
2465 		wz_info("Using colour depth of %i instead of a 32/24 bit depth (True color).", bpp);
2466 		wz_info("You will experience graphics glitches!");
2467 		break;
2468 	case 8:
2469 		debug(LOG_FATAL, "You don't want to play Warzone with a bit depth of %i, do you?", bpp);
2470 		SDL_Quit();
2471 		exit(1);
2472 		break;
2473 	default:
2474 		debug(LOG_FATAL, "Unsupported bit depth: %i", bpp);
2475 		exit(1);
2476 		break;
2477 	}
2478 
2479 	return true;
2480 }
2481 
wzMainScreenSetup(optional<video_backend> backend,int antialiasing,WINDOW_MODE fullscreen,int vsync,bool highDPI)2482 bool wzMainScreenSetup(optional<video_backend> backend, int antialiasing, WINDOW_MODE fullscreen, int vsync, bool highDPI)
2483 {
2484 	// Output linked SDL version
2485 	char buf[512];
2486 	SDL_version linked_sdl_version;
2487 	SDL_GetVersion(&linked_sdl_version);
2488 	ssprintf(buf, "Linked SDL version: %u.%u.%u\n", (unsigned int)linked_sdl_version.major, (unsigned int)linked_sdl_version.minor, (unsigned int)linked_sdl_version.patch);
2489 	addDumpInfo(buf);
2490 	debug(LOG_WZ, "%s", buf);
2491 
2492 	if (!wzSDLOneTimeInit())
2493 	{
2494 		// wzSDLOneTimeInit already logged an error on failure
2495 		return false;
2496 	}
2497 
2498 #if defined(WZ_OS_MAC)
2499 	// on macOS, support maximizing to a fullscreen space (modern behavior)
2500 	if (SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "1") == SDL_FALSE)
2501 	{
2502 		debug(LOG_WARNING, "Failed to set hint: SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES");
2503 	}
2504 #endif
2505 
2506 	WZbackend = backend;
2507 
2508 	SDL_gfx_api_Impl_Factory::Configuration sdl_impl_config;
2509 
2510 	if (backend.has_value())
2511 	{
2512 		auto result = wzMainScreenSetup_CreateVideoWindow(backend.value(), antialiasing, fullscreen, vsync, highDPI);
2513 		if (!result.has_value())
2514 		{
2515 			return false; // must return so new configuration will be saved
2516 		}
2517 		sdl_impl_config = result.value();
2518 
2519 		/* initialise all cursors */
2520 		if (war_GetColouredCursor())
2521 		{
2522 			sdlInitColoredCursors();
2523 		}
2524 		else
2525 		{
2526 			sdlInitCursors();
2527 		}
2528 	}
2529 
2530 	setlocale(LC_NUMERIC, "C"); // set radix character to the period (".")
2531 
2532 #if defined(WZ_OS_MAC)
2533 	if (backend.has_value())
2534 	{
2535 		cocoaSetupWZMenus();
2536 	}
2537 #endif
2538 
2539 	const auto vsyncMode = to_swap_mode(vsync);
2540 
2541 	gfx_api::backend_type gfxapi_backend = gfx_api::backend_type::null_backend;
2542 	if (backend.has_value())
2543 	{
2544 		if (backend.value() == video_backend::vulkan)
2545 		{
2546 			gfxapi_backend = gfx_api::backend_type::vulkan_backend;
2547 		}
2548 		else
2549 		{
2550 			gfxapi_backend = gfx_api::backend_type::opengl_backend;
2551 		}
2552 	}
2553 
2554 	if (!gfx_api::context::initialize(SDL_gfx_api_Impl_Factory(WZwindow, sdl_impl_config), antialiasing, vsyncMode, gfxapi_backend))
2555 	{
2556 		// Failed to initialize desired backend / renderer settings
2557 		if (backend.has_value())
2558 		{
2559 			video_backend defaultBackend = wzGetNextFallbackGfxBackendForCurrentSystem(backend.value());
2560 			if ((backend.value() != defaultBackend) && shouldResetGfxBackendPrompt(backend.value(), defaultBackend))
2561 			{
2562 				resetGfxBackend(defaultBackend);
2563 				return false; // must return so new configuration will be saved
2564 			}
2565 			else
2566 			{
2567 				debug(LOG_FATAL, "gfx_api::context::get().initialize failed for backend: %s", to_string(backend.value()).c_str());
2568 			}
2569 		}
2570 		else
2571 		{
2572 			// headless mode failed in gfx_api::context::initialize??
2573 			debug(LOG_FATAL, "gfx_api::context::get().initialize failed for headless-mode backend?");
2574 		}
2575 		SDL_Quit();
2576 		exit(EXIT_FAILURE);
2577 	}
2578 
2579 	if (backend.has_value())
2580 	{
2581 		wzMainScreenSetup_VerifyWindow();
2582 	}
2583 
2584 	return true;
2585 }
2586 
2587 
2588 // Calculates and returns the scale factor from the SDL window's coordinate system (in points) to the raw
2589 // underlying pixels of the viewport / renderer.
2590 //
2591 // IMPORTANT: This value is *non-inclusive* of any user-configured Game Display Scale.
2592 //
2593 // This exposes what is effectively the SDL window's "High-DPI Scale Factor", if SDL's high-DPI support is enabled and functioning.
2594 //
2595 // In the normal, non-high-DPI-supported case, (in which the context's drawable size in pixels and the window's logical
2596 // size in points are equal) this will return 1.0 for both values.
2597 //
wzGetWindowToRendererScaleFactor(float * horizScaleFactor,float * vertScaleFactor)2598 void wzGetWindowToRendererScaleFactor(float *horizScaleFactor, float *vertScaleFactor)
2599 {
2600 	if (!WZbackend.has_value())
2601 	{
2602 		if (horizScaleFactor != nullptr)
2603 		{
2604 			*horizScaleFactor = 1.0f;
2605 		}
2606 		if (vertScaleFactor != nullptr)
2607 		{
2608 			*vertScaleFactor = 1.0f;
2609 		}
2610 		return;
2611 	}
2612 	ASSERT_OR_RETURN(, WZwindow != nullptr, "wzGetWindowToRendererScaleFactor called when window is not available");
2613 
2614 	// Obtain the window context's drawable size in pixels
2615 	int drawableWidth, drawableHeight = 0;
2616 	SDL_WZBackend_GetDrawableSize(WZwindow, &drawableWidth, &drawableHeight);
2617 
2618 	// Obtain the logical window size (in points)
2619 	int logicalWindowWidth, logicalWindowHeight = 0;
2620 	SDL_GetWindowSize(WZwindow, &logicalWindowWidth, &logicalWindowHeight);
2621 
2622 	debug(LOG_WZ, "Window Logical Size (%d, %d) vs Drawable Size in Pixels (%d, %d)", logicalWindowWidth, logicalWindowHeight, drawableWidth, drawableHeight);
2623 
2624 	if (horizScaleFactor != nullptr)
2625 	{
2626 		*horizScaleFactor = ((float)drawableWidth / (float)logicalWindowWidth) * current_displayScaleFactor;
2627 	}
2628 	if (vertScaleFactor != nullptr)
2629 	{
2630 		*vertScaleFactor = ((float)drawableHeight / (float)logicalWindowHeight) * current_displayScaleFactor;
2631 	}
2632 
2633 	int displayIndex = SDL_GetWindowDisplayIndex(WZwindow);
2634 	if (displayIndex >= 0)
2635 	{
2636 		float hdpi, vdpi;
2637 		if (SDL_GetDisplayDPI(displayIndex, nullptr, &hdpi, &vdpi) < 0)
2638 		{
2639 			debug(LOG_WARNING, "Failed to get the display (%d) DPI because : %s", displayIndex, SDL_GetError());
2640 		}
2641 		else
2642 		{
2643 			debug(LOG_WZ, "Display (%d) DPI: %f, %f", displayIndex, hdpi, vdpi);
2644 		}
2645 	}
2646 	else
2647 	{
2648 		debug(LOG_WARNING, "Failed to get the display index for the window because : %s", SDL_GetError());
2649 	}
2650 }
2651 
2652 // Calculates and returns the total scale factor from the game's coordinate system (in points)
2653 // to the raw underlying pixels of the viewport / renderer.
2654 //
2655 // IMPORTANT: This value is *inclusive* of both the user-configured "Display Scale" *AND* any underlying
2656 // high-DPI / "Retina" display support provided by SDL.
2657 //
2658 // It is equivalent to: (SDL Window's High-DPI Scale Factor) x (WZ Game Display Scale Factor)
2659 //
2660 // Therefore, if SDL is providing a supported high-DPI window / context, this value will be greater
2661 // than the WZ (user-configured) Display Scale Factor.
2662 //
2663 // It should be used only for internal (non-user-displayed) cases in which the full scaling factor from
2664 // the game system's coordinate system (in points) to the underlying display pixels is required.
2665 // (For example, when rasterizing text for best display.)
2666 //
wzGetGameToRendererScaleFactor(float * horizScaleFactor,float * vertScaleFactor)2667 void wzGetGameToRendererScaleFactor(float *horizScaleFactor, float *vertScaleFactor)
2668 {
2669 	float horizWindowScaleFactor = 0.f, vertWindowScaleFactor = 0.f;
2670 	wzGetWindowToRendererScaleFactor(&horizWindowScaleFactor, &vertWindowScaleFactor);
2671 	assert(horizWindowScaleFactor != 0.f);
2672 	assert(vertWindowScaleFactor != 0.f);
2673 
2674 	if (horizScaleFactor != nullptr)
2675 	{
2676 		*horizScaleFactor = horizWindowScaleFactor * current_displayScaleFactor;
2677 	}
2678 	if (vertScaleFactor != nullptr)
2679 	{
2680 		*vertScaleFactor = vertWindowScaleFactor * current_displayScaleFactor;
2681 	}
2682 }
2683 
wzSetWindowIsResizable(bool resizable)2684 void wzSetWindowIsResizable(bool resizable)
2685 {
2686 	if (WZwindow == nullptr)
2687 	{
2688 		debug(LOG_WARNING, "wzSetWindowIsResizable called when window is not available");
2689 		return;
2690 	}
2691 	SDL_bool sdl_resizable = (resizable) ? SDL_TRUE : SDL_FALSE;
2692 	SDL_SetWindowResizable(WZwindow, sdl_resizable);
2693 
2694 	if (resizable)
2695 	{
2696 		// Set the minimum window size
2697 		unsigned int minWindowWidth = 0, minWindowHeight = 0;
2698 		wzGetMinimumWindowSizeForDisplayScaleFactor(&minWindowWidth, &minWindowHeight, current_displayScaleFactor);
2699 		SDL_SetWindowMinimumSize(WZwindow, minWindowWidth, minWindowHeight);
2700 	}
2701 }
2702 
wzIsWindowResizable()2703 bool wzIsWindowResizable()
2704 {
2705 	if (WZwindow == nullptr)
2706 	{
2707 		debug(LOG_WARNING, "wzIsWindowResizable called when window is not available");
2708 		return false;
2709 	}
2710 	Uint32 flags = SDL_GetWindowFlags(WZwindow);
2711 	if (flags & SDL_WINDOW_RESIZABLE)
2712 	{
2713 		return true;
2714 	}
2715 	return false;
2716 }
2717 
2718 /*!
2719  * Activation (focus change ... and) eventhandler.  Mainly for debugging.
2720  */
handleActiveEvent(SDL_Event * event)2721 static void handleActiveEvent(SDL_Event *event)
2722 {
2723 	if (event->type == SDL_WINDOWEVENT)
2724 	{
2725 		switch (event->window.event)
2726 		{
2727 		case SDL_WINDOWEVENT_SHOWN:
2728 			debug(LOG_WZ, "Window %d shown", event->window.windowID);
2729 			break;
2730 		case SDL_WINDOWEVENT_HIDDEN:
2731 			debug(LOG_WZ, "Window %d hidden", event->window.windowID);
2732 			break;
2733 		case SDL_WINDOWEVENT_EXPOSED:
2734 			debug(LOG_WZ, "Window %d exposed", event->window.windowID);
2735 			break;
2736 		case SDL_WINDOWEVENT_MOVED:
2737 			debug(LOG_WZ, "Window %d moved to %d,%d", event->window.windowID, event->window.data1, event->window.data2);
2738 				// FIXME: Handle detecting which screen the window was moved to, and update saved war_SetScreen?
2739 			break;
2740 		case SDL_WINDOWEVENT_RESIZED:
2741 		case SDL_WINDOWEVENT_SIZE_CHANGED:
2742 			debug(LOG_WZ, "Window %d resized to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
2743 			{
2744 				unsigned int oldWindowWidth = windowWidth;
2745 				unsigned int oldWindowHeight = windowHeight;
2746 
2747 				Uint32 windowFlags = SDL_GetWindowFlags(WZwindow);
2748 				debug(LOG_WZ, "Window resized to window flags: %u", windowFlags);
2749 
2750 				int newWindowWidth = 0, newWindowHeight = 0;
2751 				SDL_GetWindowSize(WZwindow, &newWindowWidth, &newWindowHeight);
2752 
2753 				if ((event->window.data1 != newWindowWidth) || (event->window.data2 != newWindowHeight))
2754 				{
2755 					// This can happen - so we use the values retrieved from SDL_GetWindowSize in any case - but
2756 					// log it for tracking down the SDL-related causes later.
2757 					debug(LOG_WARNING, "Received width and height (%d x %d) do not match those from GetWindowSize (%d x %d)", event->window.data1, event->window.data2, newWindowWidth, newWindowHeight);
2758 				}
2759 
2760 				handleWindowSizeChange(oldWindowWidth, oldWindowHeight, newWindowWidth, newWindowHeight);
2761 
2762 				// Store the new values (in case the user manually resized the window bounds)
2763 				war_SetWidth(newWindowWidth);
2764 				war_SetHeight(newWindowHeight);
2765 			}
2766 			break;
2767 		case SDL_WINDOWEVENT_MINIMIZED:
2768 			debug(LOG_WZ, "Window %d minimized", event->window.windowID);
2769 			break;
2770 		case SDL_WINDOWEVENT_MAXIMIZED:
2771 			debug(LOG_WZ, "Window %d maximized", event->window.windowID);
2772 			break;
2773 		case SDL_WINDOWEVENT_RESTORED:
2774 			debug(LOG_WZ, "Window %d restored", event->window.windowID);
2775 			break;
2776 		case SDL_WINDOWEVENT_ENTER:
2777 			debug(LOG_WZ, "Mouse entered window %d", event->window.windowID);
2778 			break;
2779 		case SDL_WINDOWEVENT_LEAVE:
2780 			debug(LOG_WZ, "Mouse left window %d", event->window.windowID);
2781 			break;
2782 		case SDL_WINDOWEVENT_FOCUS_GAINED:
2783 			mouseInWindow = SDL_TRUE;
2784 			debug(LOG_WZ, "Window %d gained keyboard focus", event->window.windowID);
2785 			break;
2786 		case SDL_WINDOWEVENT_FOCUS_LOST:
2787 			mouseInWindow = SDL_FALSE;
2788 			debug(LOG_WZ, "Window %d lost keyboard focus", event->window.windowID);
2789 			break;
2790 		case SDL_WINDOWEVENT_CLOSE:
2791 			debug(LOG_WZ, "Window %d closed", event->window.windowID);
2792 			WZwindow = nullptr;
2793 			break;
2794 		case SDL_WINDOWEVENT_TAKE_FOCUS:
2795 			debug(LOG_WZ, "Window %d is being offered focus", event->window.windowID);
2796 			break;
2797 		default:
2798 			debug(LOG_WZ, "Window %d got unknown event %d", event->window.windowID, event->window.event);
2799 			break;
2800 		}
2801 	}
2802 }
2803 
2804 // Actual mainloop
wzMainEventLoop(void)2805 void wzMainEventLoop(void)
2806 {
2807 	SDL_Event event;
2808 
2809 	while (true)
2810 	{
2811 		/* Deal with any windows messages */
2812 		while (SDL_PollEvent(&event))
2813 		{
2814 			switch (event.type)
2815 			{
2816 			case SDL_KEYUP:
2817 			case SDL_KEYDOWN:
2818 				inputHandleKeyEvent(&event.key);
2819 				break;
2820 			case SDL_MOUSEBUTTONUP:
2821 			case SDL_MOUSEBUTTONDOWN:
2822 				inputHandleMouseButtonEvent(&event.button);
2823 				break;
2824 			case SDL_MOUSEMOTION:
2825 				inputHandleMouseMotionEvent(&event.motion);
2826 				break;
2827 			case SDL_MOUSEWHEEL:
2828 				inputHandleMouseWheelEvent(&event.wheel);
2829 				break;
2830 			case SDL_WINDOWEVENT:
2831 				handleActiveEvent(&event);
2832 				break;
2833 			case SDL_TEXTINPUT:	// SDL now handles text input differently
2834 				inputhandleText(&event.text);
2835 				break;
2836 			case SDL_QUIT:
2837 				return;
2838 			default:
2839 				break;
2840 			}
2841 
2842 			if (wzSDLAppEvent == event.type)
2843 			{
2844 				// Custom WZ App Event
2845 				switch (event.user.code)
2846 				{
2847 					case wzSDLAppEventCodes::MAINTHREADEXEC:
2848 						if (event.user.data1 != nullptr)
2849 						{
2850 							WZ_MAINTHREADEXEC * pExec = static_cast<WZ_MAINTHREADEXEC *>(event.user.data1);
2851 							pExec->doExecOnMainThread();
2852 							delete pExec;
2853 						}
2854 						break;
2855 					default:
2856 						break;
2857 				}
2858 			}
2859 		}
2860 
2861 		processScreenSizeChangeNotificationIfNeeded();
2862 		mainLoop();				// WZ does its thing
2863 		inputNewFrame();			// reset input states
2864 	}
2865 }
2866 
wzPumpEventsWhileLoading()2867 void wzPumpEventsWhileLoading()
2868 {
2869 	SDL_PumpEvents();
2870 }
2871 
wzShutdown()2872 void wzShutdown()
2873 {
2874 	// order is important!
2875 	sdlFreeCursors();
2876 	if (WZwindow != nullptr)
2877 	{
2878 		SDL_DestroyWindow(WZwindow);
2879 		WZwindow = nullptr;
2880 	}
2881 	SDL_Quit();
2882 
2883 	// delete copies of argc, argv
2884 	if (copied_argv != nullptr)
2885 	{
2886 		for(int i=0; i < copied_argc; i++) {
2887 			delete [] copied_argv[i];
2888 		}
2889 		delete [] copied_argv;
2890 		copied_argv = nullptr;
2891 		copied_argc = 0;
2892 	}
2893 }
2894