1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8  */
9 
10 #include "freespace.h"
11 
12 #include "gamesequence/gamesequence.h"
13 #include "globalincs/pstypes.h"
14 #include "parse/parselo.h"
15 
16 #include <fcntl.h>
17 #include <utf8.h>
18 
19 #ifdef SCP_UNIX
20 #include <sys/stat.h>
21 #elif defined(WIN32)
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #endif
25 
26 namespace
27 {
28 	const char* ORGANIZATION_NAME = "HardLightProductions";
29 	const char* APPLICATION_NAME = "FreeSpaceOpen";
30 
31 	char* preferencesPath = nullptr;
32 
33 	bool checkedLegacyMode = false;
34 	bool legacyMode = false;
35 
36 	SCP_vector<std::unique_ptr<os::Viewport>> viewports;
37 	os::Viewport* mainViewPort = nullptr;
38 	SDL_Window* mainSDLWindow = nullptr;
39 
getPreferencesPath()40 	const char* getPreferencesPath()
41 	{
42 		// Lazily initialize the preferences path
43 		if (!preferencesPath) {
44 		    preferencesPath = SDL_GetPrefPath(ORGANIZATION_NAME, APPLICATION_NAME);
45 
46 			// this section will at least tell the user if something is seriously wrong instead of just crashing without a message or debug log.
47 			// It may crash later, especially when trying to load sound. But let's let it *try* to run in the current directory at least.
48 		    if (preferencesPath == nullptr) {
49 				static bool sdl_is_borked_warning = false;
50 				if (!sdl_is_borked_warning) {
51 					ReleaseWarning(LOCATION, "%s\n\nSDL and Windows are unable to get the preferred path for the reason above. "
52 						"Installing FSO, its executables and DLLs in another non-protected folder may fix the issue.\n\n"
53 						"You may experience issues if you continue playing, and FSO may crash. Please report this error if it persists.\n\n"
54 						"Report at www.hard-light.net or the hard-light discord.", SDL_GetError());
55 					sdl_is_borked_warning = true;
56 				}
57 				// No preferences path, try current directory.
58 				Cmdline_portable_mode = true;
59 				return "." DIR_SEPARATOR_STR;
60 		    }
61 #ifdef WIN32
62 		    try {
63 			    auto current           = preferencesPath;
64 			    const auto prefPathEnd = preferencesPath + strlen(preferencesPath);
65 			    while (current != prefPathEnd) {
66 				    const auto cp = utf8::next(current, prefPathEnd);
67 				    if (cp > 127) {
68 					    // On Windows, we currently do not support Unicode paths so force portable mode let the user
69 					    // know
70 					    const auto invalid_end = current;
71 						static bool force_portable_warning = false;
72 						if (!force_portable_warning) {
73 							utf8::prior(current, preferencesPath);
74 							ReleaseWarning(LOCATION,
75 								"Determined the preferences path as \"%s\". That path is not supported since it "
76 								"contains a Unicode character (%s). Using portable mode. Set -portable_mode in "
77 								"the commandline to avoid this message in the future.",
78 								preferencesPath, std::string(current, invalid_end).c_str());
79 							force_portable_warning = true;
80 						}
81 						Cmdline_portable_mode = true;
82 						return "." DIR_SEPARATOR_STR;
83 				    }
84 			    }
85 		    } catch (const std::exception& e) {
86 			    Error(LOCATION, "UTF-8 error while checking the preferences path \"%s\": %s", preferencesPath,
87 			          e.what());
88 		    }
89 #endif
90 	    }
91 
92 	    if (preferencesPath) {
93 			return preferencesPath;
94 		}
95 		else {
96 			// No preferences path, try current directory
97 			return "." DIR_SEPARATOR_STR;
98 		}
99 
100 	}
101 
102 	bool fAppActive = false;
window_event_handler(const SDL_Event & e)103 	bool window_event_handler(const SDL_Event& e)
104 	{
105 		Assertion(mainSDLWindow != nullptr, "This function may only be called with a valid SDL Window.");
106 		if (os::events::isWindowEvent(e, mainSDLWindow)) {
107 			switch (e.window.event) {
108 			case SDL_WINDOWEVENT_MINIMIZED:
109 			case SDL_WINDOWEVENT_FOCUS_LOST:
110 			{
111 				if (fAppActive) {
112 					if (!Cmdline_no_unfocus_pause) {
113 						game_pause();
114 					}
115 
116 					fAppActive = false;
117 				}
118 				break;
119 			}
120 			case SDL_WINDOWEVENT_MAXIMIZED:
121 			case SDL_WINDOWEVENT_RESTORED:
122 			case SDL_WINDOWEVENT_FOCUS_GAINED:
123 			{
124 				if (!fAppActive) {
125 					if (!Cmdline_no_unfocus_pause) {
126 						game_unpause();
127 					}
128 
129 					fAppActive = true;
130 				}
131 				break;
132 			}
133 			case SDL_WINDOWEVENT_CLOSE:
134 				gameseq_post_event(GS_EVENT_QUIT_GAME);
135 				break;
136 			}
137 
138 			gr_activate(fAppActive);
139 
140 			return true;
141 		}
142 
143 		return false;
144 	}
145 
quit_handler(const SDL_Event &)146 	bool quit_handler(const SDL_Event&  /*e*/) {
147 		mprintf(("Recevied quit signal\n"));
148 		gameseq_post_event(GS_EVENT_QUIT_GAME);
149 		return true;
150 	}
151 
mapCategory(int category)152 	const char* mapCategory(int category) {
153 		switch (category) {
154 			case SDL_LOG_CATEGORY_APPLICATION:
155 				return "APPLICATION";
156 			case SDL_LOG_CATEGORY_ERROR:
157 				return "ERROR";
158 			case SDL_LOG_CATEGORY_ASSERT:
159 				return "ASSERT";
160 			case SDL_LOG_CATEGORY_SYSTEM:
161 				return "SYSTEM";
162 			case SDL_LOG_CATEGORY_AUDIO:
163 				return "AUDIO";
164 			case SDL_LOG_CATEGORY_VIDEO:
165 				return "VIDEO";
166 			case SDL_LOG_CATEGORY_RENDER:
167 				return "RENDER";
168 			case SDL_LOG_CATEGORY_INPUT:
169 				return "INPUT";
170 			case SDL_LOG_CATEGORY_TEST:
171 				return "TEST";
172 
173 			default:
174 				return "UNKNOWN";
175 		}
176 	}
177 
mapPriority(SDL_LogPriority prio)178 	const char* mapPriority(SDL_LogPriority prio)
179 	{
180 		switch (prio) {
181 		case SDL_LOG_PRIORITY_VERBOSE:
182 			return "VRB";
183 		case SDL_LOG_PRIORITY_DEBUG:
184 			return "DBG";
185 		case SDL_LOG_PRIORITY_INFO:
186 			return "INF";
187 		case SDL_LOG_PRIORITY_WARN:
188 			return "WRN";
189 		case SDL_LOG_PRIORITY_ERROR:
190 			return "ERR";
191 		case SDL_LOG_PRIORITY_CRITICAL:
192 			return "CRI";
193 
194 		default:
195 			return "UNK";
196 		}
197 	}
198 
logHandler(void *,int category,SDL_LogPriority priority,const char * message)199 	void SDLCALL logHandler(void*, int category, SDL_LogPriority priority, const char* message) {
200 		if (priority >= SDL_LOG_PRIORITY_INFO) {
201 			mprintf(("SDL [%s][%s]: %s\n", mapPriority(priority), mapCategory(category), message));
202 		} else {
203 			nprintf(("SDL", "SDL [%s][%s]: %s\n", mapPriority(priority), mapCategory(category), message));
204 		}
205 	}
206 }
207 
208 
209 // ----------------------------------------------------------------------------------------------------
210 // PLATFORM SPECIFIC FUNCTION FOLLOWING
211 //
212 
213 #ifdef WIN32
214 
215 // Windows specific includes
216 #define WIN32_LEAN_AND_MEAN
217 #include <windows.h>
218 
219 // go through all windows and try and find the one that matches the search string
os_enum_windows(HWND hwnd,LPARAM param)220 BOOL __stdcall os_enum_windows( HWND hwnd, LPARAM param )
221 {
222 	const char* search_string = reinterpret_cast<const char*>(param);
223 	char tmp[128];
224 	int len;
225 
226 	len = GetWindowText( hwnd, tmp, 127 );
227 
228 	if ( len )	{
229 		if ( strstr( tmp, search_string ))	{
230 			Os_debugger_running = 1;		// found the search string!
231 			return FALSE;	// stop enumerating windows
232 		}
233 	}
234 
235 	return TRUE;	// continue enumeration
236 }
237 
238 // Fills in the Os_debugger_running with non-zero if debugger detected.
os_check_debugger()239 void os_check_debugger()
240 {
241 	HMODULE hMod;
242 	char search_string[256];
243 	char myname[128];
244 	int namelen;
245 	char * p;
246 
247 	Os_debugger_running = 0;		// Assume its not
248 
249 	// Find my EXE file name
250 	hMod = GetModuleHandle(NULL);
251 	if ( !hMod ) return;
252 	namelen = GetModuleFileName( hMod, myname, 127 );
253 	if ( namelen < 1 ) return;
254 
255 	// Strip off the .EXE
256 	p = strstr( myname, ".exe" );
257 	if (!p) return;
258 	*p = '\0';
259 
260 	// Move p to point to first letter of EXE filename
261 	while( (*p!='\\') && (*p!='/') && (*p!=':') )
262 		p--;
263 	p++;
264 	if ( strlen(p) < 1 ) return;
265 
266 	// Build what the debugger's window title would be if the debugger is running...
267 	sprintf( search_string, "[run] - %s -", p );
268 
269 	// ... and then search for it.
270 	EnumWindows(os_enum_windows, reinterpret_cast<LPARAM>(&search_string));
271 }
272 
os_set_process_affinity()273 void os_set_process_affinity()
274 {
275 	HANDLE pHandle = GetCurrentProcess();
276 	DWORD_PTR pMaskProcess = 0, pMaskSystem = 0;
277 
278 	if ( GetProcessAffinityMask(pHandle, &pMaskProcess, &pMaskSystem) ) {
279 		// only do this if we have at least 2 procs
280 		if (pMaskProcess >= 3) {
281 			// prefer running on the second processor by default
282 			pMaskProcess = os_config_read_uint(NULL, "ProcessorAffinity", 2);
283 
284 			if (pMaskProcess > 0) {
285 				SetProcessAffinityMask(pHandle, pMaskProcess);
286 			}
287 		}
288 	}
289 }
290 
291 #endif // WIN32
292 
293 
294 // ----------------------------------------------------------------------------------------------------
295 // OSAPI DEFINES/VARS
296 //
297 
298 // os-wide globals
299 static char			szWinTitle[128];
300 static char			szWinClass[128];
301 static int			Os_inited = 0;
302 
303 static SCP_vector<SDL_Event> buffered_events;
304 
305 int Os_debugger_running = 0;
306 
307 #ifdef SCP_UNIX
308 static bool user_dir_initialized = false;
309 static SCP_string Os_user_dir_legacy;
310 
os_get_legacy_user_dir()311 const char* os_get_legacy_user_dir() {
312 	if (user_dir_initialized) {
313 		return Os_user_dir_legacy.c_str();
314 	}
315 
316 	extern const char* Osreg_user_dir_legacy;
317 	sprintf(Os_user_dir_legacy, "%s/%s", getenv("HOME"), Osreg_user_dir_legacy);
318 	user_dir_initialized = true;
319 
320 	return Os_user_dir_legacy.c_str();
321 }
322 #endif
323 
324 // ----------------------------------------------------------------------------------------------------
325 // OSAPI FORWARD DECLARATIONS
326 //
327 void os_deinit();
328 
329 // ----------------------------------------------------------------------------------------------------
330 // OSAPI FUNCTIONS
331 //
332 
333 // initialization/shutdown functions -----------------------------------------------
334 
335 // If app_name is NULL or ommited, then TITLE is used
336 // for the app name, which is where registry keys are stored.
os_init(const char * wclass,const char * title,const char * app_name)337 void os_init(const char * wclass, const char * title, const char * app_name)
338 {
339 	if (app_name == nullptr || !app_name[0])
340 	{
341 		os_init_registry_stuff(Osreg_company_name, title);
342 	}
343 	else
344 	{
345 		os_init_registry_stuff(Osreg_company_name, app_name);
346 	}
347 
348 	strcpy_s( szWinTitle, title );
349 	strcpy_s( szWinClass, wclass );
350 
351 	SDL_version compiled;
352 	SDL_version linked;
353 
354 	SDL_VERSION(&compiled);
355 	SDL_GetVersion(&linked);
356 
357 	mprintf(("  Initializing SDL %d.%d.%d (compiled with %d.%d.%d)...\n", linked.major, linked.minor, linked.patch,
358 	         compiled.major, compiled.minor, compiled.patch));
359 
360 	if (LoggingEnabled) {
361 		SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
362 		SDL_LogSetOutputFunction(&logHandler, nullptr);
363 	}
364 
365 	if (SDL_Init(SDL_INIT_EVENTS) < 0)
366 	{
367 		fprintf(stderr, "Couldn't init SDL: %s", SDL_GetError());
368 		mprintf(("Couldn't init SDL: %s\n", SDL_GetError()));
369 
370 		exit(1);
371 		return;
372 	}
373 
374 #ifdef FS2_VOICER
375 	SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // We currently only need this for voice recognition
376 #endif
377 
378 	// initialized
379 	Os_inited = 1;
380 
381 #ifdef WIN32
382 	// check to see if we're running under msdev
383 	os_check_debugger();
384 
385 	if (Cmdline_set_cpu_affinity)
386 	{
387 		// deal with processor affinity
388 		os_set_process_affinity();
389 	}
390 #endif // WIN32
391 
392 	os::events::addEventListener(SDL_WINDOWEVENT, os::events::DEFAULT_LISTENER_WEIGHT, window_event_handler);
393 	os::events::addEventListener(SDL_QUIT, os::events::DEFAULT_LISTENER_WEIGHT, quit_handler);
394 }
395 
396 // set the main window title
os_set_title(const char * title)397 void os_set_title( const char * title )
398 {
399 	Assertion(mainSDLWindow != nullptr, "This function may only be called with a valid SDL Window.");
400 	strcpy_s( szWinTitle, title );
401 
402 	SDL_SetWindowTitle(mainSDLWindow, szWinTitle);
403 }
404 
405 // call at program end
os_cleanup()406 void os_cleanup()
407 {
408 	os_deinit_registry_stuff();
409 
410 	if (LoggingEnabled) {
411 		outwnd_close();
412 	}
413 
414 	os_deinit();
415 }
416 
417 // window management -----------------------------------------------------------------
418 
419 // Returns 1 if app is not the foreground app.
os_foreground()420 int os_foreground()
421 {
422 	return fAppActive;
423 }
424 
425 // process management -----------------------------------------------------------------
426 
427 // Sleeps for n milliseconds or until app becomes active.
os_sleep(uint ms)428 void os_sleep(uint ms)
429 {
430 #ifdef __APPLE__
431 	// ewwww, I hate this!!  SDL_Delay() is causing issues for us though and this
432 	// basically matches Apple examples of the same thing.  Same as SDL_Delay() but
433 	// we aren't hitting up the system for anything during the process
434 	uint then = SDL_GetTicks() + ms;
435 
436 	while (then > SDL_GetTicks());
437 #else
438 	SDL_Delay(ms);
439 #endif
440 }
441 
file_exists(const SCP_string & path)442 static bool file_exists(const SCP_string& path) {
443 	std::ofstream str(path, std::ios::in);
444 	return str.good();
445 }
446 
get_file_modification_time(const SCP_string & path)447 static time_t get_file_modification_time(const SCP_string& path) {
448 #ifdef SCP_UNIX
449 	struct stat file_stats{};
450 	if(stat(path.c_str(), &file_stats) < 0) {
451 		return 0;
452 	}
453 
454 	return file_stats.st_mtime;
455 #elif defined(WIN32)
456 	struct _stat buf{};
457 	if (_stat(path.c_str(), &buf) != 0) {
458 		return 0;
459 	}
460 	return buf.st_mtime;
461 #else
462 #error Unsupported platform!
463 #endif
464 }
465 
466 const char* Osapi_legacy_mode_reason = nullptr;
467 
os_is_legacy_mode()468 bool os_is_legacy_mode()
469 {
470 	// Make this check a little faster by caching the result
471 	if (checkedLegacyMode)
472 	{
473 		return legacyMode;
474 	}
475 
476 	if (Cmdline_portable_mode) {
477 		// When the portable mode option is given, non-legacy is implied
478 		legacyMode = false;
479 		checkedLegacyMode = true;
480 
481 		Osapi_legacy_mode_reason = "Legacy mode disabled since portable mode was enabled.";
482 	}
483 	else {
484 		SCP_stringstream path_stream;
485 		path_stream << getPreferencesPath() << Osreg_config_file_name;
486 
487 		auto new_config_exists = file_exists(path_stream.str());
488 		time_t new_config_time = 0;
489 		if (new_config_exists) {
490 			new_config_time = get_file_modification_time(path_stream.str());
491 		}
492 
493 		// Also check the modification times of the command line files in case the launcher did not change the settings
494 		// file
495 		path_stream.str("");
496 		path_stream << getPreferencesPath() << "data" << DIR_SEPARATOR_CHAR << "cmdline_fso.cfg";
497 		new_config_time = std::max(new_config_time, get_file_modification_time(path_stream.str()));
498 #ifdef SCP_UNIX
499         path_stream.str("");
500 		path_stream << os_get_legacy_user_dir() << DIR_SEPARATOR_CHAR << Osreg_config_file_name;
501 
502 		auto old_config_exists = file_exists(path_stream.str());
503 		time_t old_config_time = 0;
504 		if (old_config_exists) {
505 			old_config_time = get_file_modification_time(path_stream.str());
506 		}
507 
508 		path_stream.str("");
509 		path_stream << os_get_legacy_user_dir() << DIR_SEPARATOR_CHAR << "data" << DIR_SEPARATOR_CHAR
510 					<< "cmdline_fso.cfg";
511 		old_config_time = std::max(old_config_time, get_file_modification_time(path_stream.str()));
512 #else
513 		// At this point we can't determine if the old config exists so just assume that it does
514 		auto old_config_exists = true;
515 		time_t old_config_time = os_registry_get_last_modification_time();
516 
517 		// On Windows the cmdline_fso file was stored in the game root directory which should be in the current directory
518 		path_stream.str("");
519 		path_stream << "." << DIR_SEPARATOR_CHAR << "data" << DIR_SEPARATOR_CHAR << "cmdline_fso.cfg";
520 		old_config_time = std::max(old_config_time, get_file_modification_time(path_stream.str()));
521 #endif
522 
523 		if (new_config_exists && old_config_exists) {
524 			// Both config files exists so we need to decide which to use based on their last modification times
525 			// if the old config was modified more recently than the new config then we use the legacy mode since the
526 			// user probably used an outdated launcher after using a more recent one
527 			legacyMode = old_config_time > new_config_time;
528 
529 			if (legacyMode) {
530 				Osapi_legacy_mode_reason = "Legacy mode enabled since the old config location was used more recently than the new location.";
531 			} else {
532 				Osapi_legacy_mode_reason = "Legacy mode disabled since the new config location was used more recently than the old location.";
533 			}
534 		} else if (new_config_exists) {
535 			// If the new config exists and the old one doesn't then we can safely disable the legacy mode
536 			legacyMode = false;
537 
538 			Osapi_legacy_mode_reason = "Legacy mode disabled since the old config does not exist while the new config exists.";
539 		} else if (old_config_exists) {
540 			// Old config exists but new doesn't -> use legacy mode
541 			legacyMode = true;
542 
543 			Osapi_legacy_mode_reason = "Legacy mode enabled since the old config exists while the new config does not exist.";
544 		} else {
545 			// Neither old nor new config exists -> this is a new install
546 			legacyMode = false;
547 
548 			Osapi_legacy_mode_reason = "Legacy mode disabled since no existing config was detected.";
549 		}
550 	}
551 
552 	if (legacyMode) {
553 		// Print a message for the people running it from the terminal
554 		fprintf(stdout, "FSO is running in legacy config mode. Please either update your launcher or"
555 			" copy the configuration and pilot files to '%s' for better future compatibility.\n", getPreferencesPath());
556 	}
557 
558 	checkedLegacyMode = true;
559 	return legacyMode;
560 }
561 
562 // ----------------------------------------------------------------------------------------------------
563 // OSAPI FORWARD DECLARATIONS
564 //
565 
566 // called at shutdown. Makes sure all thread processing terminates.
os_deinit()567 void os_deinit()
568 {
569 	// Free the view ports
570 	os::closeAllViewports();
571 
572 	if (preferencesPath) {
573 		SDL_free(preferencesPath);
574 		preferencesPath = nullptr;
575 	}
576 
577 	SDL_Quit();
578 }
579 
debug_int3(const char * file,int line)580 void debug_int3(const char *file, int line)
581 {
582 	mprintf(("Int3(): From %s at line %d\n", file, line));
583 
584 	gr_activate(0);
585 
586 	mprintf(("%s\n", dump_stacktrace().c_str()));
587 
588 #ifndef NDEBUG
589 	SDL_TriggerBreakpoint();
590 #endif
591 
592 	gr_activate(1);
593 }
594 
595 namespace os
596 {
addViewport(std::unique_ptr<Viewport> && viewport)597 	Viewport* addViewport(std::unique_ptr<Viewport>&& viewport) {
598 		auto port = viewport.get();
599 		viewports.push_back(std::move(viewport));
600 		return port;
601 	}
setMainViewPort(Viewport * mainView)602 	void setMainViewPort(Viewport* mainView) {
603 		mainViewPort = mainView;
604 		mainSDLWindow = mainView->toSDLWindow();
605 	}
getSDLMainWindow()606 	SDL_Window* getSDLMainWindow() {
607 		return mainSDLWindow;
608 	}
getMainViewport()609 	Viewport* getMainViewport() {
610 		return mainViewPort;
611 	}
closeAllViewports()612 	void closeAllViewports() {
613 		viewports.clear();
614 	}
615 
616 	namespace events
617 	{
618 		namespace
619 		{
620 			ListenerIdentifier nextListenerIdentifier;
621 
622 			struct EventListenerData
623 			{
624 				ListenerIdentifier identifier;
625 				Listener listener;
626 
627 				uint32_t type;
628 				int weight;
629 
operator <os::events::__anona0dc44d10211::EventListenerData630 				bool operator<(const EventListenerData& other) const
631 				{
632 					if (type < other.type)
633 					{
634 						return true;
635 					}
636 					if (type > other.type)
637 					{
638 						return false;
639 					}
640 
641 					// Type is the same
642 					return weight < other.weight;
643 				}
644 			};
645 
compare_type(const EventListenerData & left,const EventListenerData & right)646 			bool compare_type(const EventListenerData& left, const EventListenerData& right)
647 			{
648 				return left.type < right.type;
649 			}
650 
651 			SCP_vector<EventListenerData> eventListeners;
652 		}
653 
addEventListener(SDL_EventType type,int weight,const Listener & listener)654 		ListenerIdentifier addEventListener(SDL_EventType type, int weight, const Listener& listener)
655 		{
656 			Assertion(listener, "Invalid event handler passed!");
657 
658 			EventListenerData data;
659 			data.identifier = ++nextListenerIdentifier;
660 			data.listener = listener;
661 
662 			data.weight = weight;
663 			data.type = static_cast<uint32_t>(type);
664 
665 			eventListeners.push_back(data);
666 			// This is suboptimal for runtime but we will iterate that vector often so cache hits are more important
667 			std::sort(eventListeners.begin(), eventListeners.end());
668 
669 			return data.identifier;
670 		}
671 
removeEventListener(ListenerIdentifier identifier)672 		bool removeEventListener(ListenerIdentifier identifier)
673 		{
674 			auto endIter = end(eventListeners);
675 			for (auto iter = begin(eventListeners); iter != endIter; ++iter)
676 			{
677 				if (iter->identifier == identifier)
678 				{
679 					eventListeners.erase(iter);
680 					return true; // Identifiers are unique
681 				}
682 			}
683 
684 			return false;
685 		}
686 
isWindowEvent(const SDL_Event & e,SDL_Window * window)687 		bool isWindowEvent(const SDL_Event& e, SDL_Window* window)
688 		{
689 			auto mainId = SDL_GetWindowID(window);
690 			switch(e.type)
691 			{
692 			case SDL_WINDOWEVENT:
693 				return mainId == e.window.windowID;
694 			case SDL_KEYDOWN:
695 			case SDL_KEYUP:
696 				return mainId == e.key.windowID;
697 			case SDL_TEXTEDITING:
698 				return mainId == e.edit.windowID;
699 			case SDL_TEXTINPUT:
700 				return mainId == e.text.windowID;
701 			case SDL_MOUSEMOTION:
702 				return mainId == e.motion.windowID;
703 			case SDL_MOUSEBUTTONDOWN:
704 			case SDL_MOUSEBUTTONUP:
705 				return mainId == e.button.windowID;
706 			case SDL_MOUSEWHEEL:
707 				return mainId == e.wheel.windowID;
708 			default:
709 				// Event doesn't have a window ID
710 				return true;
711 			}
712 		}
713 	}
714 }
715 
os_ignore_events()716 void os_ignore_events() {
717 	SDL_Event event;
718 	while (SDL_PollEvent(&event)) {
719 		// Add event to buffer
720 		buffered_events.push_back(event);
721 	}
722 }
723 
handle_sdl_event(const SDL_Event & event)724 static void handle_sdl_event(const SDL_Event& event) {
725 	using namespace os::events;
726 
727 	EventListenerData data;
728 	data.type = event.type;
729 
730 	auto iter = std::lower_bound(eventListeners.begin(), eventListeners.end(), data, compare_type);
731 
732 	if (iter != eventListeners.end())
733 	{
734 		// The vector contains all event listeners, the listeners are sorted for type and weight
735 		// -> iterating through all listeners will yield them in increasing weight order
736 		// but we can only do this until we have reached the end of the vector or the type has changed
737 		for(; iter != eventListeners.end() && iter->type == event.type; ++iter)
738 		{
739 			if (iter->listener(event))
740 			{
741 				// Listener has handled the event
742 				break;
743 			}
744 		}
745 	}
746 }
747 
os_poll()748 void os_poll()
749 {
750 	// Replay the buffered events
751 	auto end = buffered_events.end();
752 	for (auto it = buffered_events.begin(); it != end; ++it) {
753 		handle_sdl_event(*it);
754 	}
755 	buffered_events.clear();
756 
757 	SDL_Event event;
758 
759 	while (SDL_PollEvent(&event)) {
760 		handle_sdl_event(event);
761 	}
762 }
763 
os_get_config_path(const SCP_string & subpath)764 SCP_string os_get_config_path(const SCP_string& subpath)
765 {
766 	// Make path platform compatible
767 	SCP_string compatiblePath(subpath);
768 	std::replace(compatiblePath.begin(), compatiblePath.end(), '/', DIR_SEPARATOR_CHAR);
769 
770 	SCP_stringstream ss;
771 
772 	if (Cmdline_portable_mode) {
773 		// Use the current directory
774 		ss << "." << DIR_SEPARATOR_CHAR << compatiblePath;
775 		return ss.str();
776 	}
777 
778 	// Avoid infinite recursion when checking legacy mode
779 	if (os_is_legacy_mode()) {
780 #ifdef WIN32
781 		// Use the current directory
782 		ss << ".";
783 #else
784 		extern const char* Osreg_user_dir_legacy;
785 		// Use the home directory
786 		ss << getenv("HOME") << DIR_SEPARATOR_CHAR << Osreg_user_dir_legacy;
787 #endif
788 
789 		ss << DIR_SEPARATOR_CHAR << compatiblePath;
790 		return ss.str();
791 	}
792 
793 	ss << getPreferencesPath() << compatiblePath;
794 
795 	return ss.str();
796 }
797 
798