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