#include "sdl_host_interface.h" #include "frontend-common/controller_interface.h" #include "frontend-common/fullscreen_ui.h" #include "frontend-common/icon.h" #include "frontend-common/ini_settings_interface.h" #include "frontend-common/sdl_controller_interface.h" #include "frontend-common/sdl_initializer.h" #include "imgui.h" #include "imgui_impl_sdl.h" #include "scmversion/scmversion.h" #include "sdl_key_names.h" #include #include #include Log_SetChannel(SDLHostInterface); #ifdef __APPLE__ #include struct NSView; static NSView* GetContentViewFromWindow(NSWindow* window) { // window.contentView return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView")); } #endif static float GetDPIScaleFactor(SDL_Window* window) { #ifdef __APPLE__ static constexpr float DEFAULT_DPI = 72.0f; #else static constexpr float DEFAULT_DPI = 96.0f; #endif if (!window) { SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); if (!dummy_window) return 1.0f; const float scale = GetDPIScaleFactor(dummy_window); SDL_DestroyWindow(dummy_window); return scale; } int display_index = SDL_GetWindowDisplayIndex(window); float display_dpi = DEFAULT_DPI; if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0) return 1.0f; return display_dpi / DEFAULT_DPI; } SDLHostInterface::SDLHostInterface() = default; SDLHostInterface::~SDLHostInterface() = default; const char* SDLHostInterface::GetFrontendName() const { return "DuckStation NoGUI Frontend"; } std::unique_ptr SDLHostInterface::Create() { return std::make_unique(); } bool SDLHostInterface::Initialize() { FrontendCommon::EnsureSDLInitialized(); if (!NoGUIHostInterface::Initialize()) return false; return true; } void SDLHostInterface::Shutdown() { NoGUIHostInterface::Shutdown(); } bool SDLHostInterface::IsFullscreen() const { return m_fullscreen; } bool SDLHostInterface::SetFullscreen(bool enabled) { if (m_fullscreen == enabled) return true; SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); m_fullscreen = enabled; return true; } bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) { if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen) return false; // use imgui scale as the dpr const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x; const s32 scaled_width = std::max(static_cast(std::ceil(static_cast(new_window_width) * dpi_scale)), 1); const s32 scaled_height = std::max( static_cast(std::ceil(static_cast(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(), 1); SDL_SetWindowSize(m_window, scaled_width, scaled_height); return true; } ALWAYS_INLINE static TinyString GetWindowTitle() { return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str); } bool SDLHostInterface::CreatePlatformWindow(bool fullscreen) { // Create window. const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; u32 window_width = DEFAULT_WINDOW_WIDTH; u32 window_height = DEFAULT_WINDOW_HEIGHT; // macOS does DPI scaling differently.. #ifndef __APPLE__ { // scale by default monitor's DPI float scale = GetDPIScaleFactor(nullptr); window_width = static_cast(std::round(static_cast(window_width) * scale)); window_height = static_cast(std::round(static_cast(window_height) * scale)); } #endif m_window = SDL_CreateWindow(GetWindowTitle(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, window_flags); if (!m_window) return false; // Set window icon. SDL_Surface* icon_surface = SDL_CreateRGBSurfaceFrom(const_cast(WINDOW_ICON_DATA), WINDOW_ICON_WIDTH, WINDOW_ICON_HEIGHT, 32, WINDOW_ICON_WIDTH * sizeof(u32), UINT32_C(0x000000FF), UINT32_C(0x0000FF00), UINT32_C(0x00FF0000), UINT32_C(0xFF000000)); if (icon_surface) { SDL_SetWindowIcon(m_window, icon_surface); SDL_FreeSurface(icon_surface); } if (fullscreen || m_fullscreen) { SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP); m_fullscreen = true; } ImGui_ImplSDL2_Init(m_window); // Process events so that we have everything sorted out before creating a child window for the GL context (X11). SDL_PumpEvents(); return true; } void SDLHostInterface::DestroyPlatformWindow() { ImGui_ImplSDL2_Shutdown(); SDL_DestroyWindow(m_window); m_window = nullptr; } std::optional SDLHostInterface::GetPlatformWindowInfo() { SDL_SysWMinfo syswm = {}; SDL_VERSION(&syswm.version); if (!SDL_GetWindowWMInfo(m_window, &syswm)) { Log_ErrorPrintf("SDL_GetWindowWMInfo failed"); return std::nullopt; } int window_width, window_height; SDL_GetWindowSize(m_window, &window_width, &window_height); WindowInfo wi; wi.surface_width = static_cast(window_width); wi.surface_height = static_cast(window_height); wi.surface_scale = GetDPIScaleFactor(m_window); wi.surface_format = WindowInfo::SurfaceFormat::RGB8; switch (syswm.subsystem) { #ifdef SDL_VIDEO_DRIVER_WINDOWS case SDL_SYSWM_WINDOWS: wi.type = WindowInfo::Type::Win32; wi.window_handle = syswm.info.win.window; break; #endif #ifdef SDL_VIDEO_DRIVER_COCOA case SDL_SYSWM_COCOA: wi.type = WindowInfo::Type::MacOS; wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window); break; #endif #ifdef SDL_VIDEO_DRIVER_X11 case SDL_SYSWM_X11: wi.type = WindowInfo::Type::X11; wi.window_handle = reinterpret_cast(static_cast(syswm.info.x11.window)); wi.display_connection = syswm.info.x11.display; break; #endif #ifdef SDL_VIDEO_DRIVER_WAYLAND case SDL_SYSWM_WAYLAND: wi.type = WindowInfo::Type::Wayland; wi.window_handle = syswm.info.wl.surface; wi.display_connection = syswm.info.wl.display; break; #endif default: Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem)); return std::nullopt; } return wi; } std::optional SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const { const std::optional code = SDLKeyNames::ParseKeyString(key_code); if (!code) return std::nullopt; return static_cast(*code); } void SDLHostInterface::PollAndUpdate() { // Process SDL events before the controller interface can steal them. const bool is_sdl_controller_interface = (m_controller_interface && m_controller_interface->GetBackend() == ControllerInterface::Backend::SDL); for (;;) { SDL_Event ev; if (!SDL_PollEvent(&ev)) break; if (is_sdl_controller_interface && static_cast(m_controller_interface.get())->ProcessSDLEvent(&ev)) { continue; } HandleSDLEvent(&ev); } ImGui_ImplSDL2_NewFrame(); NoGUIHostInterface::PollAndUpdate(); } void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) { ImGui_ImplSDL2_ProcessEvent(event); switch (event->type) { case SDL_WINDOWEVENT: { if (event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { s32 window_width, window_height; SDL_GetWindowSize(m_window, &window_width, &window_height); m_display->ResizeRenderWindow(window_width, window_height); OnHostDisplayResized(); } } break; case SDL_QUIT: m_quit_request = true; break; case SDL_KEYDOWN: case SDL_KEYUP: { const bool pressed = (event->type == SDL_KEYDOWN); // Binding mode if (m_fullscreen_ui_enabled && FullscreenUI::IsBindingInput()) { if (event->key.repeat > 0) return; TinyString key_string; if (SDLKeyNames::KeyEventToString(event, key_string)) { if (FullscreenUI::HandleKeyboardBinding(key_string, pressed)) return; } } if (!ImGui::GetIO().WantCaptureKeyboard && event->key.repeat == 0) { const u32 code = SDLKeyNames::KeyEventToInt(event); HandleHostKeyEvent(code & SDLKeyNames::KEY_MASK, code & SDLKeyNames::MODIFIER_MASK, pressed); } } break; case SDL_MOUSEMOTION: { m_display->SetMousePosition(event->motion.x, event->motion.y); } break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { if (!ImGui::GetIO().WantCaptureMouse) { const s32 button = static_cast(ZeroExtend32(event->button.button)); const bool pressed = (event->type == SDL_MOUSEBUTTONDOWN); HandleHostMouseEvent(button, pressed); } } break; } }