2021-01-30 11:55:24 +00:00
|
|
|
#include "sdl_host_interface.h"
|
2021-01-30 16:28:14 +00:00
|
|
|
#include "frontend-common/controller_interface.h"
|
2021-01-30 11:55:24 +00:00
|
|
|
#include "frontend-common/icon.h"
|
|
|
|
#include "frontend-common/ini_settings_interface.h"
|
|
|
|
#include "frontend-common/sdl_controller_interface.h"
|
2021-01-30 16:28:14 +00:00
|
|
|
#include "frontend-common/sdl_initializer.h"
|
2021-01-30 11:55:24 +00:00
|
|
|
#include "imgui.h"
|
|
|
|
#include "imgui_impl_sdl.h"
|
|
|
|
#include "scmversion/scmversion.h"
|
|
|
|
#include "sdl_key_names.h"
|
2021-01-30 16:28:14 +00:00
|
|
|
#include <SDL_syswm.h>
|
2021-01-30 11:55:24 +00:00
|
|
|
#include <cinttypes>
|
|
|
|
#include <cmath>
|
|
|
|
Log_SetChannel(SDLHostInterface);
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
#include <objc/message.h>
|
|
|
|
struct NSView;
|
|
|
|
|
|
|
|
static NSView* GetContentViewFromWindow(NSWindow* window)
|
|
|
|
{
|
|
|
|
// window.contentView
|
|
|
|
return reinterpret_cast<NSView* (*)(id, SEL)>(objc_msgSend)(reinterpret_cast<id>(window), sel_getUid("contentView"));
|
|
|
|
}
|
2021-01-30 11:55:24 +00:00
|
|
|
#endif
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
static float GetDPIScaleFactor(SDL_Window* window)
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
#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;
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
SDLHostInterface::SDLHostInterface() = default;
|
|
|
|
|
2021-01-30 11:55:24 +00:00
|
|
|
SDLHostInterface::~SDLHostInterface() = default;
|
|
|
|
|
|
|
|
const char* SDLHostInterface::GetFrontendName() const
|
|
|
|
{
|
|
|
|
return "DuckStation NoGUI Frontend";
|
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
|
|
|
|
{
|
|
|
|
return std::make_unique<SDLHostInterface>();
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
int window_width, window_height;
|
|
|
|
SDL_GetWindowSize(m_window, &window_width, &window_height);
|
|
|
|
m_display->ResizeRenderWindow(window_width, window_height);
|
2021-02-21 16:38:16 +00:00
|
|
|
OnHostDisplayResized(window_width, window_height, GetDPIScaleFactor(m_window));
|
2021-01-30 16:28:14 +00:00
|
|
|
|
|
|
|
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<s32>(static_cast<s32>(std::ceil(static_cast<float>(new_window_width) * dpi_scale)), 1);
|
|
|
|
const s32 scaled_height = std::max<s32>(
|
|
|
|
static_cast<s32>(std::ceil(static_cast<float>(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(),
|
|
|
|
1);
|
|
|
|
|
|
|
|
SDL_SetWindowSize(m_window, scaled_width, scaled_height);
|
|
|
|
|
|
|
|
s32 window_width, window_height;
|
|
|
|
SDL_GetWindowSize(m_window, &window_width, &window_height);
|
|
|
|
m_display->ResizeRenderWindow(window_width, window_height);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-30 11:55:24 +00:00
|
|
|
ALWAYS_INLINE static TinyString GetWindowTitle()
|
|
|
|
{
|
|
|
|
return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:02:54 +00:00
|
|
|
bool SDLHostInterface::CreatePlatformWindow(bool fullscreen)
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
|
|
|
// 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
|
2021-01-30 16:28:14 +00:00
|
|
|
float scale = GetDPIScaleFactor(nullptr);
|
2021-01-30 11:55:24 +00:00
|
|
|
window_width = static_cast<u32>(std::round(static_cast<float>(window_width) * scale));
|
|
|
|
window_height = static_cast<u32>(std::round(static_cast<float>(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<unsigned int*>(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);
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:02:54 +00:00
|
|
|
if (fullscreen || m_fullscreen)
|
|
|
|
{
|
2021-01-30 11:55:24 +00:00
|
|
|
SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
2021-01-31 15:02:54 +00:00
|
|
|
m_fullscreen = true;
|
|
|
|
}
|
2021-01-30 11:55:24 +00:00
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
ImGui_ImplSDL2_Init(m_window);
|
|
|
|
|
2021-01-30 11:55:24 +00:00
|
|
|
// Process events so that we have everything sorted out before creating a child window for the GL context (X11).
|
|
|
|
SDL_PumpEvents();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
void SDLHostInterface::DestroyPlatformWindow()
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
ImGui_ImplSDL2_Shutdown();
|
2021-01-30 11:55:24 +00:00
|
|
|
SDL_DestroyWindow(m_window);
|
|
|
|
m_window = nullptr;
|
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
std::optional<WindowInfo> SDLHostInterface::GetPlatformWindowInfo()
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
SDL_SysWMinfo syswm = {};
|
|
|
|
SDL_VERSION(&syswm.version);
|
|
|
|
if (!SDL_GetWindowWMInfo(m_window, &syswm))
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
|
|
|
|
return std::nullopt;
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
int window_width, window_height;
|
|
|
|
SDL_GetWindowSize(m_window, &window_width, &window_height);
|
2021-01-30 11:55:24 +00:00
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
WindowInfo wi;
|
|
|
|
wi.surface_width = static_cast<u32>(window_width);
|
|
|
|
wi.surface_height = static_cast<u32>(window_height);
|
|
|
|
wi.surface_scale = GetDPIScaleFactor(m_window);
|
|
|
|
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
|
2021-01-30 11:55:24 +00:00
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
switch (syswm.subsystem)
|
|
|
|
{
|
|
|
|
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
|
|
|
case SDL_SYSWM_WINDOWS:
|
|
|
|
wi.type = WindowInfo::Type::Win32;
|
|
|
|
wi.window_handle = syswm.info.win.window;
|
2021-01-30 11:55:24 +00:00
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
#ifdef SDL_VIDEO_DRIVER_COCOA
|
|
|
|
case SDL_SYSWM_COCOA:
|
|
|
|
wi.type = WindowInfo::Type::MacOS;
|
|
|
|
wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
|
2021-01-30 11:55:24 +00:00
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
#ifdef SDL_VIDEO_DRIVER_X11
|
|
|
|
case SDL_SYSWM_X11:
|
|
|
|
wi.type = WindowInfo::Type::X11;
|
|
|
|
wi.window_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(syswm.info.x11.window));
|
|
|
|
wi.display_connection = syswm.info.x11.display;
|
2021-01-30 11:55:24 +00:00
|
|
|
break;
|
2021-01-30 16:28:14 +00:00
|
|
|
#endif
|
2021-01-30 11:55:24 +00:00
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
#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;
|
2021-01-30 11:55:24 +00:00
|
|
|
break;
|
2021-01-30 16:28:14 +00:00
|
|
|
#endif
|
2021-01-30 11:55:24 +00:00
|
|
|
|
|
|
|
default:
|
2021-01-30 16:28:14 +00:00
|
|
|
Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast<u32>(syswm.subsystem));
|
|
|
|
return std::nullopt;
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
return wi;
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
|
|
|
{
|
|
|
|
const std::optional<u32> code = SDLKeyNames::ParseKeyString(key_code);
|
|
|
|
if (!code)
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return static_cast<HostKeyCode>(*code);
|
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
void SDLHostInterface::PollAndUpdate()
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
// 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);
|
2021-01-30 11:55:24 +00:00
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
for (;;)
|
2021-01-30 11:55:24 +00:00
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
SDL_Event ev;
|
|
|
|
if (!SDL_PollEvent(&ev))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (is_sdl_controller_interface &&
|
|
|
|
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(&ev))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
HandleSDLEvent(&ev);
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
|
2021-01-30 16:28:14 +00:00
|
|
|
ImGui_ImplSDL2_NewFrame();
|
|
|
|
CommonHostInterface::PollAndUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
|
|
|
|
{
|
|
|
|
ImGui_ImplSDL2_ProcessEvent(event);
|
|
|
|
|
2021-01-30 11:55:24 +00:00
|
|
|
switch (event->type)
|
|
|
|
{
|
|
|
|
case SDL_WINDOWEVENT:
|
|
|
|
{
|
|
|
|
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
|
|
|
|
{
|
|
|
|
m_display->ResizeRenderWindow(event->window.data1, event->window.data2);
|
2021-02-21 16:38:16 +00:00
|
|
|
OnHostDisplayResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window));
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
else if (event->window.event == SDL_WINDOWEVENT_MOVED)
|
|
|
|
{
|
2021-01-30 16:28:14 +00:00
|
|
|
// TODO: Do we want to update DPI scale here?
|
2021-01-30 11:55:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_QUIT:
|
|
|
|
m_quit_request = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_KEYDOWN:
|
|
|
|
case SDL_KEYUP:
|
|
|
|
{
|
|
|
|
if (!ImGui::GetIO().WantCaptureKeyboard && event->key.repeat == 0)
|
|
|
|
{
|
|
|
|
const HostKeyCode code = static_cast<HostKeyCode>(SDLKeyNames::KeyEventToInt(event));
|
|
|
|
const bool pressed = (event->type == SDL_KEYDOWN);
|
|
|
|
HandleHostKeyEvent(code, 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<s32>(ZeroExtend32(event->button.button));
|
|
|
|
const bool pressed = (event->type == SDL_MOUSEBUTTONDOWN);
|
|
|
|
HandleHostMouseEvent(button, pressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|