NoGUI: Merge in old branch and use base NoGUI class for SDL

This commit is contained in:
Connor McLaughlin 2021-01-31 02:28:14 +10:00
parent b09da307b5
commit 98bad30af8
17 changed files with 1610 additions and 698 deletions

View file

@ -1,16 +1,35 @@
add_executable(duckstation-nogui
imgui_impl_sdl.cpp
imgui_impl_sdl.h
main.cpp
sdl_host_interface.cpp
sdl_host_interface.h
sdl_key_names.h
sdl_util.cpp
sdl_util.h
nogui_host_interface.cpp
nogui_host_interface.h
)
target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(duckstation-nogui PRIVATE core common imgui glad frontend-common scmversion vulkan-loader ${SDL2_LIBRARIES})
target_link_libraries(duckstation-nogui PRIVATE core common imgui glad frontend-common scmversion vulkan-loader)
if(USE_SDL2)
target_sources(duckstation-nogui PRIVATE
imgui_impl_sdl.cpp
imgui_impl_sdl.h
sdl_host_interface.cpp
sdl_host_interface.h
sdl_key_names.h
)
target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(duckstation-nogui PRIVATE ${SDL2_LIBRARIES})
endif()
if(USE_DRMKMS)
find_package(LIBEVDEV REQUIRED)
target_sources(duckstation-nogui PRIVATE
drm_host_interface.cpp
drm_host_interface.h
)
target_compile_definitions(duckstation-nogui PRIVATE "-DUSE_DRMKMS=1")
target_compile_definitions(duckstation-nogui PRIVATE "-DUSE_LIBEVDEV=1")
target_include_directories(duckstation-nogui PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
target_link_libraries(duckstation-nogui PRIVATE ${LIBEVDEV_LIBRARIES})
endif()
if(WIN32)
target_sources(duckstation-nogui PRIVATE

View file

@ -0,0 +1,211 @@
#include "drm_host_interface.h"
#include "common/log.h"
#include "common/string_util.h"
#include "evdev_key_names.h"
#include "imgui.h"
#include <fcntl.h>
#include <linux/input-event-codes.h>
#include <signal.h>
#include <unistd.h>
Log_SetChannel(DRMHostInterface);
DRMHostInterface::DRMHostInterface() = default;
DRMHostInterface::~DRMHostInterface()
{
CloseEVDevFDs();
}
std::unique_ptr<NoGUIHostInterface> DRMHostInterface::Create()
{
return std::make_unique<DRMHostInterface>();
}
bool DRMHostInterface::Initialize()
{
if (!NoGUIHostInterface::Initialize())
return false;
OpenEVDevFDs();
signal(SIGTERM, SIGTERMHandler);
signal(SIGINT, SIGTERMHandler);
signal(SIGQUIT, SIGTERMHandler);
return true;
}
void DRMHostInterface::Shutdown()
{
CloseEVDevFDs();
NoGUIHostInterface::Shutdown();
}
bool DRMHostInterface::IsFullscreen() const
{
return true;
}
bool DRMHostInterface::SetFullscreen(bool enabled)
{
return enabled;
}
void DRMHostInterface::FixIncompatibleSettings(bool display_osd_messages)
{
NoGUIHostInterface::FixIncompatibleSettings(display_osd_messages);
// Some things we definitely don't want.
g_settings.confim_power_off = false;
}
bool DRMHostInterface::CreatePlatformWindow()
{
Assert(!m_drm_display);
m_drm_display = std::make_unique<DRMDisplay>();
if (!m_drm_display->Initialize())
{
m_drm_display.reset();
return false;
}
SetImGuiKeyMap();
return true;
}
void DRMHostInterface::DestroyPlatformWindow()
{
m_drm_display.reset();
}
std::optional<WindowInfo> DRMHostInterface::GetPlatformWindowInfo()
{
WindowInfo wi;
wi.type = WindowInfo::Type::DRM;
wi.display_connection = m_drm_display.get();
wi.surface_width = m_drm_display->GetWidth();
wi.surface_height = m_drm_display->GetHeight();
wi.surface_format = WindowInfo::SurfaceFormat::Auto;
return wi;
}
void DRMHostInterface::PollAndUpdate()
{
PollEvDevKeyboards();
NoGUIHostInterface::PollAndUpdate();
}
void DRMHostInterface::OpenEVDevFDs()
{
for (int i = 0; i < 1000; i++)
{
TinyString path;
path.Format("/dev/input/event%d", i);
int fd = open(path, O_RDONLY | O_NONBLOCK);
if (fd < 0)
break;
struct libevdev* obj;
if (libevdev_new_from_fd(fd, &obj) != 0)
{
Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray());
close(fd);
continue;
}
Log_DevPrintf("Input path: %s", path.GetCharArray());
Log_DevPrintf("Input device name: \"%s\"", libevdev_get_name(obj));
Log_DevPrintf("Input device ID: bus %#x vendor %#x product %#x", libevdev_get_id_bustype(obj),
libevdev_get_id_vendor(obj), libevdev_get_id_product(obj));
if (!libevdev_has_event_code(obj, EV_KEY, KEY_SPACE))
{
Log_DevPrintf("This device does not look like a keyboard");
libevdev_free(obj);
close(fd);
continue;
}
const int grab_res = libevdev_grab(obj, LIBEVDEV_GRAB);
if (grab_res != 0)
Log_WarningPrintf("Failed to grab '%s' (%s): %d", libevdev_get_name(obj), path.GetCharArray(), grab_res);
m_evdev_keyboards.push_back({obj, fd});
}
}
void DRMHostInterface::CloseEVDevFDs()
{
for (const EvDevKeyboard& kb : m_evdev_keyboards)
{
libevdev_grab(kb.obj, LIBEVDEV_UNGRAB);
libevdev_free(kb.obj);
close(kb.fd);
}
m_evdev_keyboards.clear();
}
void DRMHostInterface::PollEvDevKeyboards()
{
for (const EvDevKeyboard& kb : m_evdev_keyboards)
{
struct input_event ev;
while (libevdev_next_event(kb.obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0)
{
// auto-repeat
if (ev.value == 2)
continue;
const bool pressed = (ev.value == 1);
const HostKeyCode code = static_cast<HostKeyCode>(ev.code);
if (code >= 0 && code < countof(ImGuiIO::KeysDown))
ImGui::GetIO().KeysDown[code] = pressed;
HandleHostKeyEvent(code, pressed);
}
}
}
void DRMHostInterface::SetImGuiKeyMap()
{
ImGuiIO& io = ImGui::GetIO();
io.KeyMap[ImGuiKey_Tab] = KEY_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = KEY_UP;
io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN;
io.KeyMap[ImGuiKey_PageUp] = KEY_PAGEUP;
io.KeyMap[ImGuiKey_PageDown] = KEY_PAGEDOWN;
io.KeyMap[ImGuiKey_Home] = KEY_HOME;
io.KeyMap[ImGuiKey_End] = KEY_END;
io.KeyMap[ImGuiKey_Insert] = KEY_INSERT;
io.KeyMap[ImGuiKey_Delete] = KEY_DELETE;
io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSPACE;
io.KeyMap[ImGuiKey_Space] = KEY_SPACE;
io.KeyMap[ImGuiKey_Enter] = KEY_ENTER;
io.KeyMap[ImGuiKey_Escape] = KEY_ESC;
io.KeyMap[ImGuiKey_KeyPadEnter] = KEY_KPENTER;
io.KeyMap[ImGuiKey_A] = KEY_A;
io.KeyMap[ImGuiKey_C] = KEY_C;
io.KeyMap[ImGuiKey_V] = KEY_V;
io.KeyMap[ImGuiKey_X] = KEY_X;
io.KeyMap[ImGuiKey_Y] = KEY_Y;
io.KeyMap[ImGuiKey_Z] = KEY_Z;
}
std::optional<DRMHostInterface::HostKeyCode> DRMHostInterface::GetHostKeyCode(const std::string_view key_code) const
{
std::optional<int> kc = EvDevKeyNames::GetKeyCodeForName(key_code);
if (!kc.has_value())
return std::nullopt;
return static_cast<HostKeyCode>(kc.value());
}
void DRMHostInterface::SIGTERMHandler(int sig)
{
Log_InfoPrintf("Recieved SIGTERM");
static_cast<DRMHostInterface*>(g_host_interface)->m_quit_request = true;
signal(sig, SIG_DFL);
}

View file

@ -0,0 +1,50 @@
#pragma once
#include "common/drm_display.h"
#include "nogui_host_interface.h"
#include <memory>
#include <vector>
#include <libevdev/libevdev.h>
class DRMHostInterface final : public NoGUIHostInterface
{
public:
DRMHostInterface();
~DRMHostInterface();
bool Initialize();
void Shutdown();
bool IsFullscreen() const override;
bool SetFullscreen(bool enabled) override;
static std::unique_ptr<NoGUIHostInterface> Create();
protected:
virtual void FixIncompatibleSettings(bool display_osd_messages) override;
bool CreatePlatformWindow() override;
void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override;
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
void PollAndUpdate() override;
private:
static void SIGTERMHandler(int sig);
void OpenEVDevFDs();
void CloseEVDevFDs();
void PollEvDevKeyboards();
void SetImGuiKeyMap();
std::unique_ptr<DRMDisplay> m_drm_display;
struct EvDevKeyboard
{
struct libevdev* obj;
int fd;
};
std::vector<EvDevKeyboard> m_evdev_keyboards;
};

View file

@ -68,17 +68,61 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="drm_host_interface.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="imgui_impl_sdl.cpp" />
<ClCompile Include="nogui_host_interface.cpp" />
<ClCompile Include="sdl_host_interface.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="sdl_util.cpp" />
<ClCompile Include="win32_host_interface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="drm_host_interface.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="evdev_key_names.h">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="imgui_impl_sdl.h" />
<ClInclude Include="nogui_host_interface.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="sdl_host_interface.h" />
<ClInclude Include="sdl_key_names.h" />
<ClInclude Include="sdl_util.h" />
<ClInclude Include="win32_host_interface.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-nogui.manifest" />
@ -103,44 +147,44 @@
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|ARM64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
@ -148,7 +192,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
@ -156,7 +200,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
@ -164,7 +208,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
@ -172,7 +216,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|ARM64'" Label="Configuration">
@ -180,7 +224,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@ -311,7 +355,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -333,7 +377,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
@ -355,7 +399,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
@ -380,7 +424,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
@ -405,7 +449,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|ARM64'">
@ -430,7 +474,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -453,7 +497,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
@ -478,7 +522,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
@ -502,7 +546,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
@ -526,7 +570,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
@ -551,7 +595,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
@ -576,11 +620,11 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -4,22 +4,27 @@
<ClCompile Include="main.cpp" />
<ClCompile Include="sdl_host_interface.cpp" />
<ClCompile Include="imgui_impl_sdl.cpp" />
<ClCompile Include="sdl_util.cpp" />
<ClCompile Include="nogui_host_interface.cpp" />
<ClCompile Include="win32_host_interface.cpp" />
<ClCompile Include="drm_host_interface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="sdl_host_interface.h" />
<ClInclude Include="imgui_impl_sdl.h" />
<ClInclude Include="sdl_key_names.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="sdl_util.h" />
<ClInclude Include="nogui_host_interface.h" />
<ClInclude Include="win32_host_interface.h" />
<ClInclude Include="drm_host_interface.h" />
<ClInclude Include="evdev_key_names.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-sdl.manifest" />
<Manifest Include="duckstation-nogui.manifest" />
</ItemGroup>
<ItemGroup>
<Image Include="duckstation-sdl.ico" />
<Image Include="duckstation-nogui.ico" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="duckstation-sdl.rc" />
<ResourceCompile Include="duckstation-nogui.rc" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,278 @@
#pragma once
#include "common/string.h"
#include "common/types.h"
#include <array>
#include <cstring>
#include <linux/input-event-codes.h>
#include <map>
#include <optional>
#include <string_view>
namespace EvDevKeyNames {
static const std::map<int, const char*> s_evdev_key_names = {{KEY_ESC, "Escape"},
{KEY_1, "1"},
{KEY_2, "2"},
{KEY_3, "3"},
{KEY_4, "4"},
{KEY_5, "5"},
{KEY_6, "6"},
{KEY_7, "7"},
{KEY_8, "8"},
{KEY_9, "9"},
{KEY_0, "0"},
{KEY_MINUS, "Minus"},
{KEY_EQUAL, "Equal"},
{KEY_BACKSPACE, "Backspace"},
{KEY_TAB, "Tab"},
{KEY_Q, "Q"},
{KEY_W, "W"},
{KEY_E, "E"},
{KEY_R, "R"},
{KEY_T, "T"},
{KEY_Y, "Y"},
{KEY_U, "U"},
{KEY_I, "I"},
{KEY_O, "O"},
{KEY_P, "P"},
{KEY_LEFTBRACE, "Leftbrace"},
{KEY_RIGHTBRACE, "Rightbrace"},
{KEY_ENTER, "Return"},
{KEY_LEFTCTRL, "Leftctrl"},
{KEY_A, "A"},
{KEY_S, "S"},
{KEY_D, "D"},
{KEY_F, "F"},
{KEY_G, "G"},
{KEY_H, "H"},
{KEY_J, "J"},
{KEY_K, "K"},
{KEY_L, "L"},
{KEY_SEMICOLON, "Semicolon"},
{KEY_APOSTROPHE, "Apostrophe"},
{KEY_GRAVE, "Grave"},
{KEY_LEFTSHIFT, "Leftshift"},
{KEY_BACKSLASH, "Backslash"},
{KEY_Z, "Z"},
{KEY_X, "X"},
{KEY_C, "C"},
{KEY_V, "V"},
{KEY_B, "B"},
{KEY_N, "N"},
{KEY_M, "M"},
{KEY_COMMA, "Comma"},
{KEY_DOT, "Dot"},
{KEY_SLASH, "Slash"},
{KEY_RIGHTSHIFT, "Rightshift"},
{KEY_KPASTERISK, "Kpasterisk"},
{KEY_LEFTALT, "Leftalt"},
{KEY_SPACE, "Space"},
{KEY_CAPSLOCK, "Capslock"},
{KEY_F1, "F1"},
{KEY_F2, "F2"},
{KEY_F3, "F3"},
{KEY_F4, "F4"},
{KEY_F5, "F5"},
{KEY_F6, "F6"},
{KEY_F7, "F7"},
{KEY_F8, "F8"},
{KEY_F9, "F9"},
{KEY_F10, "F10"},
{KEY_NUMLOCK, "Numlock"},
{KEY_SCROLLLOCK, "Scrolllock"},
{KEY_KP7, "Kp7"},
{KEY_KP8, "Kp8"},
{KEY_KP9, "Kp9"},
{KEY_KPMINUS, "Kpminus"},
{KEY_KP4, "Kp4"},
{KEY_KP5, "Kp5"},
{KEY_KP6, "Kp6"},
{KEY_KPPLUS, "Kpplus"},
{KEY_KP1, "Kp1"},
{KEY_KP2, "Kp2"},
{KEY_KP3, "Kp3"},
{KEY_KP0, "Kp0"},
{KEY_KPDOT, "Kpdot"},
{KEY_ZENKAKUHANKAKU, "Zenkakuhankaku"},
{KEY_102ND, "102nd"},
{KEY_F11, "F11"},
{KEY_F12, "F12"},
{KEY_RO, "Ro"},
{KEY_KATAKANA, "Katakana"},
{KEY_HIRAGANA, "Hiragana"},
{KEY_HENKAN, "Henkan"},
{KEY_KATAKANAHIRAGANA, "Katakanahiragana"},
{KEY_MUHENKAN, "Muhenkan"},
{KEY_KPJPCOMMA, "Kpjpcomma"},
{KEY_KPENTER, "Kpenter"},
{KEY_RIGHTCTRL, "Rightctrl"},
{KEY_KPSLASH, "Kpslash"},
{KEY_SYSRQ, "Sysrq"},
{KEY_RIGHTALT, "RightAlt"},
{KEY_LINEFEED, "Linefeed"},
{KEY_HOME, "Home"},
{KEY_UP, "Up"},
{KEY_PAGEUP, "PageUp"},
{KEY_LEFT, "Left"},
{KEY_RIGHT, "Right"},
{KEY_END, "End"},
{KEY_DOWN, "Down"},
{KEY_PAGEDOWN, "PageDown"},
{KEY_INSERT, "Insert"},
{KEY_DELETE, "Delete"},
{KEY_MACRO, "Macro"},
{KEY_MUTE, "Mute"},
{KEY_VOLUMEDOWN, "VolumeDown"},
{KEY_VOLUMEUP, "VolumeUp"},
{KEY_POWER, "Power"},
{KEY_KPEQUAL, "Kpequal"},
{KEY_KPPLUSMINUS, "Kpplusminus"},
{KEY_PAUSE, "Pause"},
{KEY_SCALE, "Scale"},
{KEY_KPCOMMA, "Kpcomma"},
{KEY_HANGEUL, "Hangeul"},
{KEY_HANGUEL, "Hanguel"},
{KEY_HANJA, "Hanja"},
{KEY_YEN, "Yen"},
{KEY_LEFTMETA, "Leftmeta"},
{KEY_RIGHTMETA, "Rightmeta"},
{KEY_COMPOSE, "Compose"},
{KEY_STOP, "Stop"},
{KEY_AGAIN, "Again"},
{KEY_PROPS, "Props"},
{KEY_UNDO, "Undo"},
{KEY_FRONT, "Front"},
{KEY_COPY, "Copy"},
{KEY_OPEN, "Open"},
{KEY_PASTE, "Paste"},
{KEY_FIND, "Find"},
{KEY_CUT, "Cut"},
{KEY_HELP, "Help"},
{KEY_MENU, "Menu"},
{KEY_CALC, "Calc"},
{KEY_SETUP, "Setup"},
{KEY_SLEEP, "Sleep"},
{KEY_WAKEUP, "Wakeup"},
{KEY_FILE, "File"},
{KEY_SENDFILE, "Sendfile"},
{KEY_DELETEFILE, "Deletefile"},
{KEY_XFER, "Xfer"},
{KEY_PROG1, "Prog1"},
{KEY_PROG2, "Prog2"},
{KEY_WWW, "Www"},
{KEY_MSDOS, "Msdos"},
{KEY_COFFEE, "Coffee"},
{KEY_SCREENLOCK, "Screenlock"},
{KEY_ROTATE_DISPLAY, "Rotate_display"},
{KEY_DIRECTION, "Direction"},
{KEY_CYCLEWINDOWS, "Cyclewindows"},
{KEY_MAIL, "Mail"},
{KEY_BOOKMARKS, "Bookmarks"},
{KEY_COMPUTER, "Computer"},
{KEY_BACK, "Back"},
{KEY_FORWARD, "Forward"},
{KEY_CLOSECD, "Closecd"},
{KEY_EJECTCD, "Ejectcd"},
{KEY_EJECTCLOSECD, "Ejectclosecd"},
{KEY_NEXTSONG, "Nextsong"},
{KEY_PLAYPAUSE, "Playpause"},
{KEY_PREVIOUSSONG, "Previoussong"},
{KEY_STOPCD, "Stopcd"},
{KEY_RECORD, "Record"},
{KEY_REWIND, "Rewind"},
{KEY_PHONE, "Phone"},
{KEY_ISO, "Iso"},
{KEY_CONFIG, "Config"},
{KEY_HOMEPAGE, "Homepage"},
{KEY_REFRESH, "Refresh"},
{KEY_EXIT, "Exit"},
{KEY_MOVE, "Move"},
{KEY_EDIT, "Edit"},
{KEY_SCROLLUP, "Scrollup"},
{KEY_SCROLLDOWN, "Scrolldown"},
{KEY_KPLEFTPAREN, "Kpleftparen"},
{KEY_KPRIGHTPAREN, "Kprightparen"},
{KEY_NEW, "New"},
{KEY_REDO, "Redo"},
{KEY_F13, "F13"},
{KEY_F14, "F14"},
{KEY_F15, "F15"},
{KEY_F16, "F16"},
{KEY_F17, "F17"},
{KEY_F18, "F18"},
{KEY_F19, "F19"},
{KEY_F20, "F20"},
{KEY_F21, "F21"},
{KEY_F22, "F22"},
{KEY_F23, "F23"},
{KEY_F24, "F24"},
{KEY_PLAYCD, "Playcd"},
{KEY_PAUSECD, "Pausecd"},
{KEY_PROG3, "Prog3"},
{KEY_PROG4, "Prog4"},
{KEY_DASHBOARD, "Dashboard"},
{KEY_SUSPEND, "Suspend"},
{KEY_CLOSE, "Close"},
{KEY_PLAY, "Play"},
{KEY_FASTFORWARD, "Fastforward"},
{KEY_BASSBOOST, "Bassboost"},
{KEY_PRINT, "Print"},
{KEY_HP, "Hp"},
{KEY_CAMERA, "Camera"},
{KEY_SOUND, "Sound"},
{KEY_QUESTION, "Question"},
{KEY_EMAIL, "Email"},
{KEY_CHAT, "Chat"},
{KEY_SEARCH, "Search"},
{KEY_CONNECT, "Connect"},
{KEY_FINANCE, "Finance"},
{KEY_SPORT, "Sport"},
{KEY_SHOP, "Shop"},
{KEY_ALTERASE, "Alterase"},
{KEY_CANCEL, "Cancel"},
{KEY_BRIGHTNESSDOWN, "Brightnessdown"},
{KEY_BRIGHTNESSUP, "Brightnessup"},
{KEY_MEDIA, "Media"},
{KEY_SWITCHVIDEOMODE, "Switchvideomode"},
{KEY_KBDILLUMTOGGLE, "Kbdillumtoggle"},
{KEY_KBDILLUMDOWN, "Kbdillumdown"},
{KEY_KBDILLUMUP, "Kbdillumup"},
{KEY_SEND, "Send"},
{KEY_REPLY, "Reply"},
{KEY_FORWARDMAIL, "Forwardmail"},
{KEY_SAVE, "Save"},
{KEY_DOCUMENTS, "Documents"},
{KEY_BATTERY, "Battery"},
{KEY_BLUETOOTH, "Bluetooth"},
{KEY_WLAN, "Wlan"},
{KEY_UWB, "Uwb"},
{KEY_UNKNOWN, "Unknown"},
{KEY_VIDEO_NEXT, "Video_next"},
{KEY_VIDEO_PREV, "Video_prev"},
{KEY_BRIGHTNESS_CYCLE, "Brightness_cycle"},
{KEY_BRIGHTNESS_AUTO, "Brightness_auto"},
{KEY_BRIGHTNESS_ZERO, "Brightness_zero"},
{KEY_DISPLAY_OFF, "Display_off"},
{KEY_WWAN, "Wwan"},
{KEY_WIMAX, "Wimax"},
{KEY_RFKILL, "Rfkill"},
{KEY_MICMUTE, "Micmute"}};
const char* GetKeyName(int key)
{
const auto it = s_evdev_key_names.find(key);
return it == s_evdev_key_names.end() ? nullptr : it->second;
}
std::optional<int> GetKeyCodeForName(const std::string_view key_name)
{
for (const auto& it : s_evdev_key_names)
{
if (key_name == it.second)
return it.first;
}
return std::nullopt;
}
} // namespace EvDevKeyNames

View file

@ -131,7 +131,7 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
return false;
}
bool ImGui_ImplSDL2_Init(SDL_Window* window)
void ImGui_ImplSDL2_Init(SDL_Window* window)
{
g_Window = window;
@ -176,7 +176,6 @@ bool ImGui_ImplSDL2_Init(SDL_Window* window)
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
return true;
}
void ImGui_ImplSDL2_Shutdown()

View file

@ -22,7 +22,7 @@
struct SDL_Window;
typedef union SDL_Event SDL_Event;
IMGUI_IMPL_API bool ImGui_ImplSDL2_Init(SDL_Window* window);
IMGUI_IMPL_API void ImGui_ImplSDL2_Init(SDL_Window* window);
IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);

View file

@ -1,48 +1,129 @@
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/system.h"
#include "frontend-common/sdl_initializer.h"
#include "sdl_host_interface.h"
#include <SDL.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
int main(int argc, char* argv[])
#ifdef USE_DRMKMS
#include "drm_host_interface.h"
#endif
#ifdef WITH_SDL2
#include "sdl_host_interface.h"
#endif
#ifdef _WIN32
#include "common/windows_headers.h"
#include "win32_host_interface.h"
#include <shellapi.h>
#endif
static std::unique_ptr<NoGUIHostInterface> CreateHostInterface(const char* platform)
{
FrontendCommon::EnsureSDLInitialized();
std::unique_ptr<NoGUIHostInterface> host_interface;
std::unique_ptr<SDLHostInterface> host_interface = SDLHostInterface::Create();
std::unique_ptr<SystemBootParameters> boot_params;
if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
{
SDL_Quit();
return EXIT_FAILURE;
}
#ifdef USE_DRMKMS
// TODO: We should detect if we have a display here...
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "drm") == 0))
host_interface = DRMHostInterface::Create();
#endif
#ifdef WITH_SDL2
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "sdl") == 0))
host_interface = SDLHostInterface::Create();
#endif
#ifdef _WIN32
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "win32") == 0))
host_interface = Win32HostInterface::Create();
#endif
return host_interface;
}
static int Run(std::unique_ptr<NoGUIHostInterface> host_interface, std::unique_ptr<SystemBootParameters> boot_params)
{
if (!host_interface->Initialize())
{
host_interface->Shutdown();
SDL_Quit();
return EXIT_FAILURE;
}
if (boot_params)
{
if (!host_interface->BootSystem(*boot_params) && host_interface->InBatchMode())
{
host_interface->Shutdown();
host_interface.reset();
SDL_Quit();
return EXIT_FAILURE;
}
host_interface->BootSystem(*boot_params);
boot_params.reset();
int result;
if (System::IsValid() || !host_interface->InBatchMode())
{
host_interface->Run();
result = EXIT_SUCCESS;
}
else
{
host_interface->ReportError("No file specified, and we're in batch mode. Exiting.");
result = EXIT_FAILURE;
}
host_interface->Run();
host_interface->Shutdown();
host_interface.reset();
SDL_Quit();
return EXIT_SUCCESS;
return result;
}
#ifdef _WIN32
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
std::unique_ptr<NoGUIHostInterface> host_interface = CreateHostInterface(nullptr);
std::unique_ptr<SystemBootParameters> boot_params;
{
std::vector<std::string> argc_strings;
argc_strings.reserve(1);
// CommandLineToArgvW() only adds the program path if the command line is empty?!
argc_strings.push_back(FileSystem::GetProgramPath());
if (std::wcslen(lpCmdLine) > 0)
{
int argc;
LPWSTR* argv_wide = CommandLineToArgvW(lpCmdLine, &argc);
if (argv_wide)
{
for (int i = 0; i < argc; i++)
argc_strings.push_back(StringUtil::WideStringToUTF8String(argv_wide[i]));
LocalFree(argv_wide);
}
}
std::vector<char*> argc_pointers;
argc_pointers.reserve(argc_strings.size());
for (std::string& arg : argc_strings)
argc_pointers.push_back(arg.data());
if (!host_interface->ParseCommandLineParameters(static_cast<int>(argc_pointers.size()), argc_pointers.data(),
&boot_params))
{
return EXIT_FAILURE;
}
}
return Run(std::move(host_interface), std::move(boot_params));
}
#else
int main(int argc, char* argv[])
{
std::unique_ptr<NoGUIHostInterface> host_interface = CreateHostInterface(nullptr);
std::unique_ptr<SystemBootParameters> boot_params;
if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
return EXIT_FAILURE;
return Run(std::move(host_interface), std::move(boot_params));
}
#endif

View file

@ -0,0 +1,410 @@
#include "nogui_host_interface.h"
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/controller.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/system.h"
#include "frontend-common/controller_interface.h"
#include "frontend-common/fullscreen_ui.h"
#include "frontend-common/icon.h"
#include "frontend-common/imgui_fullscreen.h"
#include "frontend-common/imgui_styles.h"
#include "frontend-common/ini_settings_interface.h"
#include "frontend-common/opengl_host_display.h"
#include "frontend-common/vulkan_host_display.h"
#include <cinttypes>
#include <cmath>
#include <imgui.h>
#include <imgui_stdlib.h>
Log_SetChannel(NoGUIHostInterface);
#ifdef WIN32
#include "frontend-common/d3d11_host_display.h"
#endif
NoGUIHostInterface::NoGUIHostInterface() = default;
NoGUIHostInterface::~NoGUIHostInterface() = default;
const char* NoGUIHostInterface::GetFrontendName() const
{
return "DuckStation NoGUI Frontend";
}
bool NoGUIHostInterface::Initialize()
{
// TODO: Make command line.
m_fullscreen_ui_enabled = true;
// we're always in batch mode for now
m_command_line_flags.batch_mode = !m_fullscreen_ui_enabled;
if (!CommonHostInterface::Initialize())
return false;
CreateImGuiContext();
if (!CreatePlatformWindow())
{
Log_ErrorPrintf("Failed to create platform window");
ImGui::DestroyContext();
return false;
}
if (!CreateDisplay())
{
Log_ErrorPrintf("Failed to create host display");
DestroyPlatformWindow();
ImGui::DestroyContext();
return false;
}
// process events to pick up controllers before updating input map
PollAndUpdate();
UpdateInputMap();
return true;
}
void NoGUIHostInterface::Shutdown()
{
DestroySystem();
CommonHostInterface::Shutdown();
if (m_display)
{
DestroyDisplay();
ImGui::DestroyContext();
}
DestroyPlatformWindow();
}
std::string NoGUIHostInterface::GetStringSettingValue(const char* section, const char* key,
const char* default_value /*= ""*/)
{
return m_settings_interface->GetStringValue(section, key, default_value);
}
bool NoGUIHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */)
{
return m_settings_interface->GetBoolValue(section, key, default_value);
}
int NoGUIHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */)
{
return m_settings_interface->GetIntValue(section, key, default_value);
}
float NoGUIHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */)
{
return m_settings_interface->GetFloatValue(section, key, default_value);
}
void NoGUIHostInterface::LoadSettings()
{
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::FixIncompatibleSettings(false);
}
void NoGUIHostInterface::UpdateInputMap()
{
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
}
void NoGUIHostInterface::ApplySettings(bool display_osd_messages)
{
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(display_osd_messages);
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
CheckForSettingsChanges(old_settings);
}
void NoGUIHostInterface::CreateImGuiContext()
{
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
}
void NoGUIHostInterface::OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale)
{
if (new_scale != ImGui::GetIO().DisplayFramebufferScale.x)
{
ImGui::GetIO().DisplayFramebufferScale = ImVec2(new_scale, new_scale);
ImGui::GetStyle() = ImGuiStyle();
ImGui::StyleColorsDarker();
ImGui::GetStyle().ScaleAllSizes(new_scale);
}
if (ImGuiFullscreen::UpdateLayoutScale())
{
if (ImGuiFullscreen::UpdateFonts())
{
if (!m_display->UpdateImGuiFontTexture())
Panic("Failed to update font texture");
}
}
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
}
bool NoGUIHostInterface::CreateDisplay()
{
std::optional<WindowInfo> wi = GetPlatformWindowInfo();
if (!wi)
{
ReportError("Failed to get platform window info");
return false;
}
// imgui init from window
ImGui::GetIO().DisplayFramebufferScale.x = wi->surface_scale;
ImGui::GetIO().DisplayFramebufferScale.y = wi->surface_scale;
ImGui::GetStyle() = ImGuiStyle();
ImGui::GetStyle().ScaleAllSizes(wi->surface_scale);
ImGui::StyleColorsDarker();
Assert(!m_display);
switch (g_settings.gpu_renderer)
{
case GPURenderer::HardwareVulkan:
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
break;
case GPURenderer::HardwareOpenGL:
#ifndef WIN32
default:
#endif
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
break;
#ifdef WIN32
case GPURenderer::HardwareD3D11:
default:
m_display = std::make_unique<FrontendCommon::D3D11HostDisplay>();
break;
#endif
}
if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation) ||
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation))
{
ReportError("Failed to create/initialize display render device");
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
if (!m_display->CreateImGuiContext() ||
(m_fullscreen_ui_enabled && !FullscreenUI::Initialize(this, m_settings_interface.get())) ||
!m_display->UpdateImGuiFontTexture())
{
ReportError("Failed to initialize imgui/fonts/fullscreen UI");
if (m_fullscreen_ui_enabled)
FullscreenUI::Shutdown();
m_display->DestroyImGuiContext();
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
return true;
}
void NoGUIHostInterface::DestroyDisplay()
{
if (m_fullscreen_ui_enabled)
FullscreenUI::Shutdown();
if (m_display)
{
m_display->DestroyImGuiContext();
m_display->DestroyRenderDevice();
}
m_display.reset();
}
bool NoGUIHostInterface::AcquireHostDisplay()
{
// Handle renderer switch if required.
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
bool needs_switch = false;
switch (g_settings.gpu_renderer)
{
#ifdef WIN32
case GPURenderer::HardwareD3D11:
needs_switch = (render_api != HostDisplay::RenderAPI::D3D11);
break;
#endif
case GPURenderer::HardwareVulkan:
needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan);
break;
case GPURenderer::HardwareOpenGL:
needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES);
break;
case GPURenderer::Software:
default:
needs_switch = false;
break;
}
if (needs_switch)
{
ImGui::EndFrame();
DestroyDisplay();
// We need to recreate the window, otherwise bad things happen...
DestroyPlatformWindow();
if (!CreatePlatformWindow())
Panic("Failed to recreate platform window on GPU renderer switch");
if (!CreateDisplay())
Panic("Failed to recreate display on GPU renderer switch");
ImGui::NewFrame();
}
if (!CreateHostDisplayResources())
return false;
return true;
}
void NoGUIHostInterface::ReleaseHostDisplay()
{
ReleaseHostDisplayResources();
// restore vsync, since we don't want to burn cycles at the menu
m_display->SetVSync(true);
}
void NoGUIHostInterface::OnSystemCreated()
{
CommonHostInterface::OnSystemCreated();
if (m_fullscreen_ui_enabled)
FullscreenUI::SystemCreated();
}
void NoGUIHostInterface::OnSystemPaused(bool paused)
{
CommonHostInterface::OnSystemPaused(paused);
if (m_fullscreen_ui_enabled)
FullscreenUI::SystemPaused(paused);
}
void NoGUIHostInterface::OnSystemDestroyed()
{
CommonHostInterface::OnSystemDestroyed();
ReportFormattedMessage("System shut down.");
if (m_fullscreen_ui_enabled)
FullscreenUI::SystemDestroyed();
}
void NoGUIHostInterface::OnRunningGameChanged()
{
CommonHostInterface::OnRunningGameChanged();
// TODO: Move to common
if (g_settings.apply_game_settings)
ApplySettings(true);
}
void NoGUIHostInterface::RequestExit()
{
m_quit_request = true;
}
void NoGUIHostInterface::PollAndUpdate()
{
CommonHostInterface::PollAndUpdate();
if (m_controller_interface)
m_controller_interface->PollEvents();
}
void NoGUIHostInterface::Run()
{
while (!m_quit_request)
{
RunCallbacks();
PollAndUpdate();
if (m_fullscreen_ui_enabled)
FullscreenUI::SetImGuiNavInputs();
ImGui::NewFrame();
if (System::IsRunning())
{
if (m_display_all_frames)
System::RunFrame();
else
System::RunFrames();
UpdateControllerRumble();
if (m_frame_step_request)
{
m_frame_step_request = false;
PauseSystem(true);
}
}
// rendering
{
DrawImGuiWindows();
ImGui::Render();
ImGui::EndFrame();
m_display->Render();
if (System::IsRunning())
{
System::UpdatePerformanceCounters();
if (m_throttler_enabled)
System::Throttle();
}
}
}
// Save state on exit so it can be resumed
if (!System::IsShutdown())
{
if (g_settings.save_state_on_exit)
SaveResumeSaveState();
DestroySystem();
}
}
void NoGUIHostInterface::RunLater(std::function<void()> callback)
{
std::unique_lock<std::mutex> lock(m_queued_callbacks_lock);
m_queued_callbacks.push_back(std::move(callback));
}
void NoGUIHostInterface::RunCallbacks()
{
std::unique_lock<std::mutex> lock(m_queued_callbacks_lock);
while (!m_queued_callbacks.empty())
{
auto callback = std::move(m_queued_callbacks.front());
m_queued_callbacks.pop_front();
lock.unlock();
callback();
lock.lock();
}
}

View file

@ -0,0 +1,73 @@
#pragma once
#include "common/window_info.h"
#include "core/host_display.h"
#include "core/host_interface.h"
#include "frontend-common/common_host_interface.h"
#include <array>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
class INISettingsInterface;
class NoGUIHostInterface : public CommonHostInterface
{
public:
NoGUIHostInterface();
~NoGUIHostInterface();
const char* GetFrontendName() const override;
virtual bool Initialize() override;
virtual void Shutdown() override;
virtual void Run();
std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override;
bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override;
int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override;
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
void RunLater(std::function<void()> callback) override;
void ApplySettings(bool display_osd_messages) override;
protected:
enum : u32
{
DEFAULT_WINDOW_WIDTH = 1280,
DEFAULT_WINDOW_HEIGHT = 720
};
virtual void LoadSettings() override;
bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override;
void UpdateInputMap() override;
void OnSystemCreated() override;
void OnSystemPaused(bool paused) override;
void OnSystemDestroyed() override;
void OnRunningGameChanged() override;
void RequestExit() override;
virtual void PollAndUpdate() override;
virtual bool CreatePlatformWindow() = 0;
virtual void DestroyPlatformWindow() = 0;
virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0;
void OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale);
bool CreateDisplay();
void DestroyDisplay();
void CreateImGuiContext();
void RunCallbacks();
std::unique_ptr<INISettingsInterface> m_settings_interface;
std::deque<std::function<void()>> m_queued_callbacks;
std::mutex m_queued_callbacks_lock;
bool m_quit_request = false;
};

View file

@ -1,41 +1,61 @@
#include "sdl_host_interface.h"
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/image.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/gpu.h"
#include "core/host_display.h"
#include "core/system.h"
#include "frontend-common/fullscreen_ui.h"
#include "frontend-common/controller_interface.h"
#include "frontend-common/icon.h"
#include "frontend-common/imgui_fullscreen.h"
#include "frontend-common/imgui_styles.h"
#include "frontend-common/ini_settings_interface.h"
#include "frontend-common/opengl_host_display.h"
#include "frontend-common/sdl_audio_stream.h"
#include "frontend-common/sdl_controller_interface.h"
#include "frontend-common/vulkan_host_display.h"
#include "frontend-common/sdl_initializer.h"
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_stdlib.h"
#include "scmversion/scmversion.h"
#include "sdl_key_names.h"
#include "sdl_util.h"
#include <SDL_syswm.h>
#include <cinttypes>
#include <cmath>
Log_SetChannel(SDLHostInterface);
#ifdef WIN32
#include "frontend-common/d3d11_host_display.h"
#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"));
}
#endif
SDLHostInterface::SDLHostInterface()
static float GetDPIScaleFactor(SDL_Window* window)
{
m_run_later_event_id = SDL_RegisterEvents(1);
#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
@ -43,16 +63,76 @@ const char* SDLHostInterface::GetFrontendName() const
return "DuckStation NoGUI Frontend";
}
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);
OnPlatformWindowResized(window_width, window_height, GetDPIScaleFactor(m_window));
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;
}
ALWAYS_INLINE static TinyString GetWindowTitle()
{
return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
}
bool SDLHostInterface::CreateSDLWindow()
bool SDLHostInterface::CreatePlatformWindow()
{
static constexpr u32 DEFAULT_WINDOW_WIDTH = 1280;
static constexpr u32 DEFAULT_WINDOW_HEIGHT = 720;
// Create window.
const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
@ -63,7 +143,7 @@ bool SDLHostInterface::CreateSDLWindow()
#ifndef __APPLE__
{
// scale by default monitor's DPI
float scale = SDLUtil::GetDPIScaleFactor(nullptr);
float scale = GetDPIScaleFactor(nullptr);
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));
}
@ -88,179 +168,77 @@ bool SDLHostInterface::CreateSDLWindow()
if (m_fullscreen)
SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
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::DestroySDLWindow()
void SDLHostInterface::DestroyPlatformWindow()
{
ImGui_ImplSDL2_Shutdown();
SDL_DestroyWindow(m_window);
m_window = nullptr;
}
bool SDLHostInterface::CreateDisplay()
std::optional<WindowInfo> SDLHostInterface::GetPlatformWindowInfo()
{
std::optional<WindowInfo> wi = SDLUtil::GetWindowInfoForSDLWindow(m_window);
if (!wi.has_value())
SDL_SysWMinfo syswm = {};
SDL_VERSION(&syswm.version);
if (!SDL_GetWindowWMInfo(m_window, &syswm))
{
ReportError("Failed to get window info from SDL window");
return false;
Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
return std::nullopt;
}
switch (g_settings.gpu_renderer)
int window_width, window_height;
SDL_GetWindowSize(m_window, &window_width, &window_height);
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;
switch (syswm.subsystem)
{
case GPURenderer::HardwareVulkan:
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
break;
case GPURenderer::HardwareOpenGL:
#ifndef WIN32
default:
#endif
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
break;
#ifdef WIN32
case GPURenderer::HardwareD3D11:
default:
m_display = std::make_unique<FrontendCommon::D3D11HostDisplay>();
break;
#endif
}
Assert(m_display);
if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation) ||
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation))
{
ReportError("Failed to create/initialize display render device");
m_display.reset();
return false;
}
if (!ImGui_ImplSDL2_Init(m_window) || !m_display->CreateImGuiContext())
{
ReportError("Failed to initialize ImGui SDL2 wrapper");
ImGui_ImplSDL2_Shutdown();
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
if (!FullscreenUI::Initialize(this, m_settings_interface.get()) || !m_display->UpdateImGuiFontTexture())
{
ReportError("Failed to initialize fonts/fullscreen UI");
FullscreenUI::Shutdown();
m_display->DestroyImGuiContext();
ImGui_ImplSDL2_Shutdown();
m_display->DestroyRenderDevice();
m_display.reset();
return false;
}
m_fullscreen_ui_enabled = true;
return true;
}
void SDLHostInterface::DestroyDisplay()
{
FullscreenUI::Shutdown();
m_display->DestroyImGuiContext();
m_display->DestroyRenderDevice();
m_display.reset();
}
void SDLHostInterface::CreateImGuiContext()
{
const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window);
ImGui::CreateContext();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale;
ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale;
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
ImGui::StyleColorsDarker();
}
void SDLHostInterface::UpdateFramebufferScale()
{
ImGuiIO& io = ImGui::GetIO();
const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window);
if (framebuffer_scale != io.DisplayFramebufferScale.x)
{
io.DisplayFramebufferScale = ImVec2(framebuffer_scale, framebuffer_scale);
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
}
if (ImGuiFullscreen::UpdateLayoutScale())
{
if (ImGuiFullscreen::UpdateFonts())
{
if (!m_display->UpdateImGuiFontTexture())
Panic("Failed to update font texture");
}
}
}
bool SDLHostInterface::AcquireHostDisplay()
{
// Handle renderer switch if required.
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
bool needs_switch = false;
switch (g_settings.gpu_renderer)
{
#ifdef WIN32
case GPURenderer::HardwareD3D11:
needs_switch = (render_api != HostDisplay::RenderAPI::D3D11);
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_WINDOWS:
wi.type = WindowInfo::Type::Win32;
wi.window_handle = syswm.info.win.window;
break;
#endif
case GPURenderer::HardwareVulkan:
needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan);
#ifdef SDL_VIDEO_DRIVER_COCOA
case SDL_SYSWM_COCOA:
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
break;
#endif
case GPURenderer::HardwareOpenGL:
needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES);
#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;
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
case GPURenderer::Software:
default:
needs_switch = false;
break;
Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast<u32>(syswm.subsystem));
return std::nullopt;
}
if (needs_switch)
{
ImGui::EndFrame();
DestroyDisplay();
// We need to recreate the window, otherwise bad things happen...
DestroySDLWindow();
if (!CreateSDLWindow())
Panic("Failed to recreate SDL window on GPU renderer switch");
if (!CreateDisplay())
Panic("Failed to recreate display on GPU renderer switch");
ImGui::NewFrame();
}
if (!CreateHostDisplayResources())
return false;
return true;
}
void SDLHostInterface::ReleaseHostDisplay()
{
ReleaseHostDisplayResources();
// restore vsync, since we don't want to burn cycles at the menu
m_display->SetVSync(true);
return wi;
}
std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
@ -272,197 +250,6 @@ std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode
return static_cast<HostKeyCode>(*code);
}
void SDLHostInterface::UpdateInputMap()
{
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
}
void SDLHostInterface::OnSystemCreated()
{
CommonHostInterface::OnSystemCreated();
FullscreenUI::SystemCreated();
}
void SDLHostInterface::OnSystemPaused(bool paused)
{
CommonHostInterface::OnSystemPaused(paused);
FullscreenUI::SystemPaused(paused);
}
void SDLHostInterface::OnSystemDestroyed()
{
CommonHostInterface::OnSystemDestroyed();
ReportFormattedMessage("System shut down.");
FullscreenUI::SystemDestroyed();
}
void SDLHostInterface::OnRunningGameChanged()
{
CommonHostInterface::OnRunningGameChanged();
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(true);
CommonHostInterface::FixIncompatibleSettings(true);
CheckForSettingsChanges(old_settings);
if (!System::GetRunningTitle().empty())
SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str());
else
SDL_SetWindowTitle(m_window, GetWindowTitle());
}
void SDLHostInterface::RequestExit()
{
m_quit_request = true;
}
void SDLHostInterface::RunLater(std::function<void()> callback)
{
SDL_Event ev = {};
ev.type = SDL_USEREVENT;
ev.user.code = m_run_later_event_id;
ev.user.data1 = new std::function<void()>(std::move(callback));
SDL_PushEvent(&ev);
}
void SDLHostInterface::ApplySettings(bool display_osd_messages)
{
Settings old_settings(std::move(g_settings));
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::ApplyGameSettings(display_osd_messages);
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
CheckForSettingsChanges(old_settings);
}
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);
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
m_fullscreen = enabled;
return true;
}
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
{
return std::make_unique<SDLHostInterface>();
}
bool SDLHostInterface::Initialize()
{
if (!CommonHostInterface::Initialize())
return false;
// Change to the user directory so that all default/relative paths in the config are after this.
if (!FileSystem::SetWorkingDirectory(m_user_directory.c_str()))
Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str());
if (!CreateSDLWindow())
{
Log_ErrorPrintf("Failed to create SDL window");
return false;
}
CreateImGuiContext();
if (!CreateDisplay())
{
Log_ErrorPrintf("Failed to create host display");
return false;
}
// process events to pick up controllers before updating input map
ProcessEvents();
UpdateInputMap();
return true;
}
void SDLHostInterface::Shutdown()
{
DestroySystem();
CommonHostInterface::Shutdown();
if (m_display)
{
DestroyDisplay();
ImGui::DestroyContext();
}
if (m_window)
DestroySDLWindow();
}
std::string SDLHostInterface::GetStringSettingValue(const char* section, const char* key,
const char* default_value /*= ""*/)
{
return m_settings_interface->GetStringValue(section, key, default_value);
}
bool SDLHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */)
{
return m_settings_interface->GetBoolValue(section, key, default_value);
}
int SDLHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */)
{
return m_settings_interface->GetIntValue(section, key, default_value);
}
float SDLHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */)
{
return m_settings_interface->GetFloatValue(section, key, default_value);
}
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);
UpdateFramebufferScale();
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
return true;
}
void SDLHostInterface::LoadSettings()
{
// Settings need to be loaded prior to creating the window for OpenGL bits.
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
CommonHostInterface::LoadSettings(*m_settings_interface.get());
CommonHostInterface::FixIncompatibleSettings(false);
}
void SDLHostInterface::ReportError(const char* message)
{
const bool was_fullscreen = IsFullscreen();
@ -514,16 +301,35 @@ bool SDLHostInterface::ConfirmMessage(const char* message)
return result;
}
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<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(&ev))
{
continue;
}
HandleSDLEvent(&ev);
}
ImGui_ImplSDL2_NewFrame();
CommonHostInterface::PollAndUpdate();
}
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
{
ImGui_ImplSDL2_ProcessEvent(event);
if (m_controller_interface &&
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(event))
{
return;
}
switch (event->type)
{
case SDL_WINDOWEVENT:
@ -531,14 +337,11 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
{
m_display->ResizeRenderWindow(event->window.data1, event->window.data2);
UpdateFramebufferScale();
if (!System::IsShutdown())
g_gpu->UpdateResolutionScale();
OnPlatformWindowResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window));
}
else if (event->window.event == SDL_WINDOWEVENT_MOVED)
{
UpdateFramebufferScale();
// TODO: Do we want to update DPI scale here?
}
}
break;
@ -576,86 +379,5 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
}
}
break;
case SDL_USEREVENT:
{
if (static_cast<u32>(event->user.code) == m_run_later_event_id)
{
std::function<void()>* callback = static_cast<std::function<void()>*>(event->user.data1);
Assert(callback);
(*callback)();
delete callback;
}
}
break;
}
}
void SDLHostInterface::PollAndUpdate()
{
ProcessEvents();
CommonHostInterface::PollAndUpdate();
}
void SDLHostInterface::ProcessEvents()
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
HandleSDLEvent(&ev);
else
break;
}
}
void SDLHostInterface::Run()
{
while (!m_quit_request)
{
PollAndUpdate();
if (System::IsRunning())
{
if (m_display_all_frames)
System::RunFrame();
else
System::RunFrames();
UpdateControllerRumble();
if (m_frame_step_request)
{
m_frame_step_request = false;
PauseSystem(true);
}
}
// rendering
{
ImGui_ImplSDL2_NewFrame();
FullscreenUI::SetImGuiNavInputs();
ImGui::NewFrame();
DrawImGuiWindows();
ImGui::Render();
ImGui::EndFrame();
m_display->Render();
if (System::IsRunning())
{
System::UpdatePerformanceCounters();
if (m_throttler_enabled)
System::Throttle();
}
}
}
// Save state on exit so it can be resumed
if (!System::IsShutdown())
{
if (g_settings.save_state_on_exit)
SaveResumeSaveState();
DestroySystem();
}
}

View file

@ -1,24 +1,8 @@
#pragma once
#include "common/gl/program.h"
#include "common/gl/texture.h"
#include "core/host_display.h"
#include "core/host_interface.h"
#include "frontend-common/common_host_interface.h"
#include "nogui_host_interface.h"
#include <SDL.h>
#include <array>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <string>
class AudioStream;
class INISettingsInterface;
struct GameListEntry;
class SDLHostInterface final : public CommonHostInterface
class SDLHostInterface final : public NoGUIHostInterface
{
public:
SDLHostInterface();
@ -35,53 +19,23 @@ public:
bool Initialize() override;
void Shutdown() override;
std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override;
bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override;
int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override;
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
bool IsFullscreen() const override;
bool SetFullscreen(bool enabled) override;
void RunLater(std::function<void()> callback) override;
void ApplySettings(bool display_osd_messages) override;
void Run();
protected:
void LoadSettings() override;
bool AcquireHostDisplay() override;
void ReleaseHostDisplay() override;
void OnSystemCreated() override;
void OnSystemPaused(bool paused) override;
void OnSystemDestroyed() override;
void OnRunningGameChanged() override;
void RequestExit() override;
void PollAndUpdate() override;
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
void UpdateInputMap() override;
bool CreatePlatformWindow() override;
void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override;
private:
bool CreateSDLWindow();
void DestroySDLWindow();
bool CreateDisplay();
void DestroyDisplay();
void CreateImGuiContext();
void UpdateFramebufferScale();
void HandleSDLEvent(const SDL_Event* event);
void ProcessEvents();
SDL_Window* m_window = nullptr;
std::unique_ptr<INISettingsInterface> m_settings_interface;
u32 m_run_later_event_id = 0;
bool m_fullscreen = false;
bool m_quit_request = false;
};

View file

@ -1,107 +0,0 @@
#include "sdl_util.h"
#include "common/log.h"
#include <SDL_syswm.h>
Log_SetChannel(SDLUtil);
#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"));
}
#endif
namespace SDLUtil {
std::optional<WindowInfo> GetWindowInfoForSDLWindow(SDL_Window* window)
{
SDL_SysWMinfo syswm = {};
SDL_VERSION(&syswm.version);
if (!SDL_GetWindowWMInfo(window, &syswm))
{
Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
return std::nullopt;
}
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
WindowInfo wi;
wi.surface_width = static_cast<u32>(window_width);
wi.surface_height = static_cast<u32>(window_height);
wi.surface_scale = GetDPIScaleFactor(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<void*>(static_cast<uintptr_t>(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<u32>(syswm.subsystem));
return std::nullopt;
}
return wi;
}
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;
}
} // namespace SDLUtil

View file

@ -1,11 +0,0 @@
#pragma once
#include "common/types.h"
#include "common/window_info.h"
#include <optional>
struct SDL_Window;
namespace SDLUtil {
std::optional<WindowInfo> GetWindowInfoForSDLWindow(SDL_Window* window);
float GetDPIScaleFactor(SDL_Window* window);
}

View file

@ -0,0 +1,150 @@
#include "win32_host_interface.h"
#include "common/log.h"
#include "common/string_util.h"
#include "resource.h"
#include <tchar.h>
Log_SetChannel(Win32HostInterface);
static constexpr _TCHAR WINDOW_CLASS_NAME[] = _T("DuckStationNoGUI");
Win32HostInterface::Win32HostInterface() = default;
Win32HostInterface::~Win32HostInterface() = default;
std::unique_ptr<NoGUIHostInterface> Win32HostInterface::Create()
{
return std::make_unique<Win32HostInterface>();
}
bool Win32HostInterface::Initialize()
{
if (!RegisterWindowClass())
return false;
if (!NoGUIHostInterface::Initialize())
return false;
return true;
}
void Win32HostInterface::Shutdown()
{
NoGUIHostInterface::Shutdown();
}
bool Win32HostInterface::RegisterWindowClass()
{
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(nullptr);
wc.hIcon = LoadIconA(NULL, (LPCSTR)IDI_ICON1);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = WINDOW_CLASS_NAME;
wc.hIconSm = LoadIconA(NULL, (LPCSTR)IDI_ICON1);
if (!RegisterClassExW(&wc))
{
MessageBoxA(nullptr, "Window registration failed.", "Error", MB_ICONERROR | MB_OK);
return false;
}
return true;
}
bool Win32HostInterface::CreatePlatformWindow()
{
m_hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, _T("DuckStation"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, nullptr, nullptr,
GetModuleHandleA(nullptr), this);
if (!m_hwnd)
{
MessageBoxA(nullptr, "CreateWindowEx failed.", "Error", MB_ICONERROR | MB_OK);
return false;
}
ShowWindow(m_hwnd, SW_SHOW);
UpdateWindow(m_hwnd);
ProcessWin32Events();
return true;
}
void Win32HostInterface::DestroyPlatformWindow()
{
if (m_hwnd)
{
DestroyWindow(m_hwnd);
m_hwnd = {};
}
}
std::optional<WindowInfo> Win32HostInterface::GetPlatformWindowInfo()
{
RECT rc = {};
GetClientRect(m_hwnd, &rc);
WindowInfo wi;
wi.window_handle = static_cast<void*>(m_hwnd);
wi.type = WindowInfo::Type::Win32;
wi.surface_width = static_cast<u32>(rc.right - rc.left);
wi.surface_height = static_cast<u32>(rc.bottom - rc.top);
// wi.surface_format = WindowInfo::SurfaceFormat::Auto;
return wi;
}
void Win32HostInterface::PollAndUpdate()
{
ProcessWin32Events();
NoGUIHostInterface::PollAndUpdate();
}
void Win32HostInterface::ProcessWin32Events()
{
MSG msg;
while (PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
LRESULT Win32HostInterface::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
Win32HostInterface* hi = static_cast<Win32HostInterface*>(g_host_interface);
switch (msg)
{
case WM_SIZE:
{
const u32 width = LOWORD(lParam);
const u32 height = HIWORD(lParam);
if (hi->m_display)
hi->m_display->ResizeRenderWindow(static_cast<s32>(width), static_cast<s32>(height));
}
break;
case WM_CLOSE:
hi->m_quit_request = true;
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
std::optional<Win32HostInterface::HostKeyCode> Win32HostInterface::GetHostKeyCode(const std::string_view key_code) const
{
std::optional<int> kc; // = EvDevKeyNames::GetKeyCodeForName(key_code);
if (!kc.has_value())
return std::nullopt;
return static_cast<HostKeyCode>(kc.value());
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "common/windows_headers.h"
#include "nogui_host_interface.h"
#include <memory>
#include <vector>
class Win32HostInterface final : public NoGUIHostInterface
{
public:
Win32HostInterface();
~Win32HostInterface();
bool Initialize();
void Shutdown();
static std::unique_ptr<NoGUIHostInterface> Create();
protected:
bool CreatePlatformWindow() override;
void DestroyPlatformWindow() override;
std::optional<WindowInfo> GetPlatformWindowInfo() override;
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
void PollAndUpdate() override;
private:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool RegisterWindowClass();
void ProcessWin32Events();
HWND m_hwnd{};
};