From b0624f2bc93e1bde39b8bf94efe596964a99a0f8 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Tue, 18 Aug 2020 17:48:21 +0200
Subject: [PATCH] Updates to make the application work correctly (more or less)
 on macOS.

---
 NEWS.md                                 |  5 ++++-
 es-app/src/guis/GuiMenu.cpp             |  4 ++++
 es-app/src/main.cpp                     |  6 +++---
 es-core/src/InputManager.cpp            |  4 ++++
 es-core/src/Settings.cpp                |  2 +-
 es-core/src/ThemeData.h                 |  2 +-
 es-core/src/guis/GuiInputConfig.cpp     |  6 +++---
 es-core/src/renderers/Renderer.cpp      |  9 ++++++++-
 es-core/src/renderers/Renderer_GL21.cpp | 11 +++++++++++
 es-core/src/utils/FileSystemUtil.cpp    |  6 ++++--
 10 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/NEWS.md b/NEWS.md
index 4fac0b4fa..85eb0cd42 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -4,7 +4,9 @@
 
 ### Release overview
 
-First release, a major update to the application compared to the RetroPie version on which it is based. This includes new gamelist sorting logic, new game media handling and a completely updated Windows port (which now works about as well as the Unix version). The menu system has also been completely overhauled and the scraper has been expanded to support multiple media types (including videos) as well as providing detailed scraping configuration options.
+First release, a major update to the application compared to the RetroPie version on which it is based. This includes new gamelist sorting logic, new game media handling and a completely updated Windows port (which works about as well as the Unix version). The menu system has also been completely overhauled and the scraper has been expanded to support multiple media types (including videos) as well as providing detailed scraping configuration options.
+
+Work has started on a macOS port too. It already runs more or less correctly, but is not completely ready for general use as of this version.
 
 Full navigation sound support has been implemented, and the metadata editor has seen a lot of updates including color coding of all changes done by the user and by the scraper. Favorite games can now also be sorted on top of the gamelists and game collections.
 
@@ -27,6 +29,7 @@ Many bugs have been fixed, and numerous features that were only partially implem
 * New default theme rbsimple-DE bundled with the software, this theme is largely based on recalbox-multi by the Recalbox community
 * Added extensive es_systems.cfg templates for Unix and Windows
 * Updated the application to compile and work on Microsoft Windows, including full UTF-16 (Unicode) support
+* Updated the application to compile and work (more or less) on Apple macOS
 * Seamless (almost) launch of games without showing the desktop when starting and when returning from RetroArch and other emulators
 * Per-game launch command override, so that different cores or emulators can be used on a per-game basis (saved to gamelist.xml)
 * Core location can be defined relative to the emulator binary using the %EMUPATH% variable in es_systems.cfg (mostly useful for Windows)
diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp
index 8d7f1afb7..e627c66ec 100644
--- a/es-app/src/guis/GuiMenu.cpp
+++ b/es-app/src/guis/GuiMenu.cpp
@@ -78,12 +78,16 @@ void GuiMenu::openSoundSettings()
 {
     auto s = new GuiSettings(mWindow, "SOUND SETTINGS");
 
+    // TEMPORARY - Hide the volume slider on macOS until the volume control logic
+    // has been implemented for this operating system.
+    #if !defined(__APPLE__)
     // System volume.
     auto volume = std::make_shared<SliderComponent>(mWindow, 0.f, 100.f, 1.f, "%");
     volume->setValue((float)VolumeControl::getInstance()->getVolume());
     s->addWithLabel("SYSTEM VOLUME", volume);
     s->addSaveFunc([volume] { VolumeControl::getInstance()->
             setVolume((int)Math::round(volume->getValue())); });
+    #endif
 
     if (UIModeController::getInstance()->isUIModeFull()) {
     // The ALSA Audio Card and Audio Device selection code is disabled at the moment.
diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp
index f7c10dccf..a7f69dca2 100644
--- a/es-app/src/main.cpp
+++ b/es-app/src/main.cpp
@@ -594,12 +594,12 @@ int main(int argc, char* argv[])
         }
     }
 
-    // Check if the media directory exists, and if not, log a warning.
+    // Check if the media directory exists, otherwise log an information entry.
     if (!Utils::FileSystem::isDirectory(FileData::getMediaDirectory()) ||
             Utils::FileSystem::isSymlink(FileData::getMediaDirectory())) {
-        LOG(LogWarning) << "Game media directory does not exist "
+        LOG(LogInfo) << "Game media directory does not exist "
                 "(or is not a directory or a symlink):";
-        LOG(LogWarning) << FileData::getMediaDirectory();
+        LOG(LogInfo) << FileData::getMediaDirectory();
     }
 
     // Generate joystick events since we're done loading.
diff --git a/es-core/src/InputManager.cpp b/es-core/src/InputManager.cpp
index 609f21556..41eb23d30 100644
--- a/es-core/src/InputManager.cpp
+++ b/es-core/src/InputManager.cpp
@@ -388,7 +388,11 @@ void InputManager::loadDefaultKBConfig()
     cfg->mapInput("a", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_RETURN, 1, true));
     cfg->mapInput("b", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_BACKSPACE, 1, true));
     cfg->mapInput("x", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_DELETE, 1, true));
+    #if defined(__APPLE__)
+    cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PRINTSCREEN, 1, true));
+    #else
     cfg->mapInput("y", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_INSERT, 1, true));
+    #endif
     cfg->mapInput("start", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_ESCAPE, 1, true));
     cfg->mapInput("select", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F1, 1, true));
 
diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp
index 0af5466b9..e5d74fcba 100644
--- a/es-core/src/Settings.cpp
+++ b/es-core/src/Settings.cpp
@@ -172,7 +172,7 @@ void Settings::setDefaults()
     #else
         mIntMap["MaxVRAM"] = 128;
     #endif
-    #if defined (__unix__) || defined (__APPLE__)
+    #if defined (__unix__)
     mStringMap["FullscreenMode"] = "normal";
     #endif
     mStringMap["PowerSaverMode"] = "disabled";
diff --git a/es-core/src/ThemeData.h b/es-core/src/ThemeData.h
index 2fde3d553..ef9b46cdd 100644
--- a/es-core/src/ThemeData.h
+++ b/es-core/src/ThemeData.h
@@ -66,7 +66,7 @@ public:
 
     inline void setFiles(const std::deque<std::string>& deque)
     {
-        *this << "from theme \"" << deque.front() << "\"\n";
+        *this << "From theme \"" << deque.front() << "\"\n";
         for (auto it = deque.cbegin() + 1; it != deque.cend(); it++)
             *this << "  (from included file \"" << (*it) << "\")\n";
         *this << "    ";
diff --git a/es-core/src/guis/GuiInputConfig.cpp b/es-core/src/guis/GuiInputConfig.cpp
index 7d6d94f1e..9bf5ee7e5 100755
--- a/es-core/src/guis/GuiInputConfig.cpp
+++ b/es-core/src/guis/GuiInputConfig.cpp
@@ -376,9 +376,9 @@ void GuiInputConfig::clearAssignment(int inputId)
 
 bool GuiInputConfig::filterTrigger(Input input, InputConfig* config, int inputId)
 {
-    #if defined(__linux__)
-    // On Linux, some gamepads return both an analog axis and a digital button for the trigger;
-    // we want the analog axis only, so this function removes the button press event.
+    #if defined(__linux__) || defined(__APPLE__)
+    // On Linux and macOS, some gamepads return both an analog axis and a digital button for
+    // the trigger; we want the analog axis only, so this function removes the button press event.
     // This is relevant mostly for Sony Dual Shock controllers.
     if (InputManager::getInstance()->getAxisCountByDevice(config->getDeviceId()) == 6) {
         if (config->getDeviceName().find("PLAYSTATION") != std::string::npos ||
diff --git a/es-core/src/renderers/Renderer.cpp b/es-core/src/renderers/Renderer.cpp
index ea6f024c6..91259f4fd 100644
--- a/es-core/src/renderers/Renderer.cpp
+++ b/es-core/src/renderers/Renderer.cpp
@@ -113,15 +113,22 @@ namespace Renderer
         #else
         if (Settings::getInstance()->getBool("Windowed"))
             windowFlags = getWindowFlags();
+        #if defined(__APPLE__)
+        else
+            // This seems to be the best fullscreen mode on macOS as the taskbar switcher
+            // works etc. while still filling the entire screen with the application window.
+            windowFlags = SDL_WINDOW_FULLSCREEN_DESKTOP | getWindowFlags();
+        #else
         else if (Settings::getInstance()->getString("FullscreenMode") == "borderless")
             windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP | getWindowFlags();
         else
             windowFlags = SDL_WINDOW_FULLSCREEN | getWindowFlags();
         #endif
+        #endif
 
         if ((sdlWindow = SDL_CreateWindow("EmulationStation", SDL_WINDOWPOS_UNDEFINED,
                 SDL_WINDOWPOS_UNDEFINED, windowWidth, windowHeight, windowFlags)) == nullptr) {
-            LOG(LogError) << "Error creating SDL window!\n\t" << SDL_GetError();
+            LOG(LogError) << "Couldn't create SDL window. " << SDL_GetError();
             return false;
         }
 
diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp
index c7c509d26..012ef35d0 100644
--- a/es-core/src/renderers/Renderer_GL21.cpp
+++ b/es-core/src/renderers/Renderer_GL21.cpp
@@ -84,7 +84,13 @@ namespace Renderer
 
     void setupWindow()
     {
+        #if defined(__APPLE__)
+        // This is required on macOS, as the operating system will otherwise insist on using
+        // a newer OpenGL version which completely breaks the application.
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
+        #else
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+        #endif
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
 
@@ -99,6 +105,11 @@ namespace Renderer
     void createContext()
     {
         sdlContext = SDL_GL_CreateContext(getSDLWindow());
+
+        if (!sdlContext) {
+            LOG(LogError) << "Error creating OpenGL context. " << SDL_GetError();
+        }
+
         SDL_GL_MakeCurrent(getSDLWindow(), sdlContext);
 
         std::string vendor = glGetString(GL_VENDOR) ?
diff --git a/es-core/src/utils/FileSystemUtil.cpp b/es-core/src/utils/FileSystemUtil.cpp
index 0d2417f00..9f100854a 100644
--- a/es-core/src/utils/FileSystemUtil.cpp
+++ b/es-core/src/utils/FileSystemUtil.cpp
@@ -751,7 +751,8 @@ namespace Utils
 
             #if defined(__APPLE__)
             struct stat info;
-            return (stat(path.c_str(), &info) == 0);
+            if (stat(path.c_str(), &info) != 0)
+                return false;
             #elif defined(_WIN64)
             struct stat64 info;
             if (_wstat64(Utils::String::stringToWideString(path).c_str(), &info) != 0)
@@ -772,7 +773,8 @@ namespace Utils
 
             #if defined(__APPLE__)
             struct stat info;
-            return (stat(path.c_str(), &info) == 0);
+            if (stat(path.c_str(), &info) != 0)
+                return false;
             #elif defined(_WIN64)
             struct stat64 info;
             if (_wstat64(Utils::String::stringToWideString(path).c_str(), &info) != 0)