diff --git a/CMakeLists.txt b/CMakeLists.txt
index aedf1d1af..44468e094 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.8)
+cmake_minimum_required(VERSION 3.10)
project(duckstation C CXX)
message("CMake Version: ${CMAKE_VERSION}")
@@ -19,6 +19,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14.0" CACHE STRING "")
# Global options.
if(NOT ANDROID)
option(BUILD_SDL_FRONTEND "Build the SDL frontend" ON)
+ option(BUILD_NOGUI_FRONTEND "Build the NoGUI frontend" ON)
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
option(ENABLE_DISCORD_PRESENCE "Build with Discord Rich Presence support" ON)
option(USE_SDL2 "Link with SDL2 for controller support" ON)
@@ -46,6 +47,10 @@ if(ANDROID)
message(WARNING "Building for Android, disabling SDL frontend")
set(BUILD_SDL_FRONTEND OFF)
endif()
+ if(BUILD_NOGUI_FRONTEND)
+ message(WARNING "Building for Android, disabling NoGUI frontend")
+ set(BUILD_QT_FRONTEND OFF)
+ endif()
if(BUILD_QT_FRONTEND)
message(WARNING "Building for Android, disabling Qt frontend")
set(BUILD_QT_FRONTEND OFF)
@@ -137,18 +142,14 @@ if(MSVC)
# Enable LTO/LTCG on Release builds.
if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
- if (${CMAKE_VERSION} VERSION_LESS "3.9.0")
- message(WARNING "CMake version is less than 3.9.0, we can't enable LTCG/IPO. This will make the build slightly slower, consider updating your CMake version.")
+ cmake_policy(SET CMP0069 NEW)
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT IPO_IS_SUPPORTED)
+ if(IPO_IS_SUPPORTED)
+ message(STATUS "Enabling LTCG/IPO.")
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
else()
- cmake_policy(SET CMP0069 NEW)
- include(CheckIPOSupported)
- check_ipo_supported(RESULT IPO_IS_SUPPORTED)
- if(IPO_IS_SUPPORTED)
- message(STATUS "Enabling LTCG/IPO.")
- set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
- else()
- message(WARNING "LTCG/IPO is not supported, this will make the build slightly slower.")
- endif()
+ message(WARNING "LTCG/IPO is not supported, this will make the build slightly slower.")
endif()
endif()
endif()
diff --git a/duckstation.sln b/duckstation.sln
index 5545c22b5..9dc40a724 100644
--- a/duckstation.sln
+++ b/duckstation.sln
@@ -37,8 +37,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libFLAC", "dep\libFLAC\libF
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lzma", "dep\lzma\lzma.vcxproj", "{DD944834-7899-4C1C-A4C1-064B5009D239}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "duckstation-sdl", "src\duckstation-sdl\duckstation-sdl.vcxproj", "{DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}"
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "frontend-common", "src\frontend-common\frontend-common.vcxproj", "{6245DEC8-D2DA-47EE-A373-CBD6FCF3ECE6}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xxhash", "dep\xxhash\xxhash.vcxproj", "{09553C96-9F39-49BF-8AE6-7ACBD07C410C}"
@@ -61,6 +59,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vixl", "dep\vixl\vixl.vcxpr
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsamplerate", "dep\libsamplerate\libsamplerate.vcxproj", "{39F0ADFF-3A84-470D-9CF0-CA49E164F2F3}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "duckstation-nogui", "src\duckstation-nogui\duckstation-nogui.vcxproj", "{0A172B2E-DC67-49FC-A4C1-975F93C586C4}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "duckstation-sdl", "src\duckstation-sdl\duckstation-sdl.vcxproj", "{DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -461,30 +463,6 @@ Global
{DD944834-7899-4C1C-A4C1-064B5009D239}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64
{DD944834-7899-4C1C-A4C1-064B5009D239}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
{DD944834-7899-4C1C-A4C1-064B5009D239}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|ARM64.Build.0 = Debug|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x64.ActiveCfg = Debug|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x64.Build.0 = Debug|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x86.ActiveCfg = Debug|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x86.Build.0 = Debug|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|ARM64.ActiveCfg = DebugFast|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|ARM64.Build.0 = DebugFast|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x64.ActiveCfg = DebugFast|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x64.Build.0 = DebugFast|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x86.ActiveCfg = DebugFast|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x86.Build.0 = DebugFast|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|ARM64.ActiveCfg = Release|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|ARM64.Build.0 = Release|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x64.ActiveCfg = Release|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x64.Build.0 = Release|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x86.ActiveCfg = Release|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x86.Build.0 = Release|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|ARM64.Build.0 = ReleaseLTCG|ARM64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
- {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
{6245DEC8-D2DA-47EE-A373-CBD6FCF3ECE6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{6245DEC8-D2DA-47EE-A373-CBD6FCF3ECE6}.Debug|ARM64.Build.0 = Debug|ARM64
{6245DEC8-D2DA-47EE-A373-CBD6FCF3ECE6}.Debug|x64.ActiveCfg = Debug|x64
@@ -741,6 +719,54 @@ Global
{39F0ADFF-3A84-470D-9CF0-CA49E164F2F3}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64
{39F0ADFF-3A84-470D-9CF0-CA49E164F2F3}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
{39F0ADFF-3A84-470D-9CF0-CA49E164F2F3}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|ARM64.Build.0 = Debug|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|x64.ActiveCfg = Debug|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|x64.Build.0 = Debug|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|x86.ActiveCfg = Debug|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|x86.Build.0 = Debug|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|ARM64.ActiveCfg = DebugFast|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|ARM64.Build.0 = DebugFast|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x64.ActiveCfg = DebugFast|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x64.Build.0 = DebugFast|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x86.ActiveCfg = DebugFast|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x86.Build.0 = DebugFast|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|ARM64.ActiveCfg = Release|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|ARM64.Build.0 = Release|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|x64.ActiveCfg = Release|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|x64.Build.0 = Release|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|x86.ActiveCfg = Release|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Release|x86.Build.0 = Release|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|ARM64.Build.0 = ReleaseLTCG|ARM64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|ARM64.Build.0 = Debug|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x64.ActiveCfg = Debug|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x64.Build.0 = Debug|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x86.ActiveCfg = Debug|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Debug|x86.Build.0 = Debug|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|ARM64.ActiveCfg = DebugFast|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|ARM64.Build.0 = DebugFast|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x64.ActiveCfg = DebugFast|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x64.Build.0 = DebugFast|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x86.ActiveCfg = DebugFast|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.DebugFast|x86.Build.0 = DebugFast|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|ARM64.ActiveCfg = Release|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|ARM64.Build.0 = Release|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x64.ActiveCfg = Release|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x64.Build.0 = Release|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x86.ActiveCfg = Release|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.Release|x86.Build.0 = Release|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|ARM64.ActiveCfg = ReleaseLTCG|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|ARM64.Build.0 = ReleaseLTCG|ARM64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32
+ {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 36bc23203..df5b643b0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -9,8 +9,10 @@ if(WIN32)
add_subdirectory(updater)
endif()
-if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND)
- add_subdirectory(frontend-common)
+add_subdirectory(frontend-common)
+
+if(BUILD_NOGUI_FRONTEND)
+ add_subdirectory(duckstation-nogui)
endif()
if(BUILD_SDL_FRONTEND)
diff --git a/src/duckstation-nogui/CMakeLists.txt b/src/duckstation-nogui/CMakeLists.txt
new file mode 100644
index 000000000..4a063ba65
--- /dev/null
+++ b/src/duckstation-nogui/CMakeLists.txt
@@ -0,0 +1,25 @@
+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
+)
+
+target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS})
+target_link_libraries(duckstation-nogui PRIVATE core common imgui nativefiledialog glad frontend-common scmversion vulkan-loader ${SDL2_LIBRARIES})
+
+if(WIN32)
+ target_sources(duckstation-nogui PRIVATE
+ duckstation-nogui.manifest
+ )
+
+ # We want a Windows subsystem application not console.
+ set_target_properties(duckstation-nogui PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ DEBUG_POSTFIX "-debug")
+endif()
+
diff --git a/src/duckstation-nogui/duckstation-nogui.aps b/src/duckstation-nogui/duckstation-nogui.aps
new file mode 100644
index 000000000..147191914
Binary files /dev/null and b/src/duckstation-nogui/duckstation-nogui.aps differ
diff --git a/src/duckstation-nogui/duckstation-nogui.ico b/src/duckstation-nogui/duckstation-nogui.ico
new file mode 100644
index 000000000..f95b67991
Binary files /dev/null and b/src/duckstation-nogui/duckstation-nogui.ico differ
diff --git a/src/duckstation-nogui/duckstation-nogui.manifest b/src/duckstation-nogui/duckstation-nogui.manifest
new file mode 100644
index 000000000..6bbce6d9b
--- /dev/null
+++ b/src/duckstation-nogui/duckstation-nogui.manifest
@@ -0,0 +1,17 @@
+
+
+
+
+ PerMonitorV2
+ true
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/duckstation-nogui/duckstation-nogui.rc b/src/duckstation-nogui/duckstation-nogui.rc
new file mode 100644
index 000000000..913553f50
--- /dev/null
+++ b/src/duckstation-nogui/duckstation-nogui.rc
@@ -0,0 +1,110 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (Australia) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,1
+ PRODUCTVERSION 1,0,0,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "0c0904b0"
+ BEGIN
+ VALUE "CompanyName", "https://github.com/stenzek/duckstation"
+ VALUE "FileDescription", "DuckStation PS1 Emulator"
+ VALUE "FileVersion", "1.0.0.1"
+ VALUE "InternalName", "duckstation-nogui.exe"
+ VALUE "LegalCopyright", "Copyright (C) 2020 Stenzek and collaborators"
+ VALUE "OriginalFilename", "duckstation-nogui.exe"
+ VALUE "ProductName", "DuckStation NoGUI Frontend"
+ VALUE "ProductVersion", "1.0.0.1"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0xc09, 1200
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1 ICON "duckstation-nogui.ico"
+
+#endif // English (Australia) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj b/src/duckstation-nogui/duckstation-nogui.vcxproj
new file mode 100644
index 000000000..72f908258
--- /dev/null
+++ b/src/duckstation-nogui/duckstation-nogui.vcxproj
@@ -0,0 +1,586 @@
+
+
+
+
+ DebugFast
+ ARM64
+
+
+ DebugFast
+ Win32
+
+
+ DebugFast
+ x64
+
+
+ Debug
+ ARM64
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ ReleaseLTCG
+ ARM64
+
+
+ ReleaseLTCG
+ Win32
+
+
+ ReleaseLTCG
+ x64
+
+
+ Release
+ ARM64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+
+
+ {bb08260f-6fbc-46af-8924-090ee71360c6}
+
+
+ {ee054e08-3799-4a59-a422-18259c105ffd}
+
+
+ {868b98c8-65a1-494b-8346-250a73a48c0a}
+
+
+ {6245dec8-d2da-47ee-a373-cbd6fcf3ece6}
+
+
+ {075ced82-6a20-46df-94c7-9624ac9ddbeb}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0A172B2E-DC67-49FC-A4C1-975F93C586C4}
+ Win32Proj
+ duckstation-nogui
+ 10.0
+
+
+
+
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ true
+ v142
+ NotSet
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+ Application
+ false
+ v142
+ true
+ NotSet
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)bin\$(Platform)\
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ true
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ true
+ $(SolutionDir)bin\$(Platform)\
+
+
+ true
+ $(SolutionDir)bin\$(Platform)\
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ true
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ true
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)bin\$(Platform)\
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+
+
+ false
+ $(SolutionDir)bin\$(Platform)\
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ false
+ $(SolutionDir)bin\$(Platform)\
+
+
+ $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\
+ $(ProjectName)-$(Platform)-$(Configuration)
+ false
+ $(SolutionDir)bin\$(Platform)\
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ Default
+ true
+ false
+ stdcpp17
+ false
+ OnlyExplicitInline
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ Default
+ true
+ false
+ stdcpp17
+ false
+ OnlyExplicitInline
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Level4
+ Disabled
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ true
+ ProgramDatabase
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ Default
+ true
+ false
+ stdcpp17
+ false
+ OnlyExplicitInline
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ Default
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ true
+ stdcpp17
+ true
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ UseLinkTimeCodeGeneration
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ Default
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ false
+ stdcpp17
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ Default
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ true
+ stdcpp17
+ true
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ UseLinkTimeCodeGeneration
+
+
+
+
+ Level4
+
+
+ MaxSpeed
+ true
+ WITH_DISCORD_PRESENCE=1;WITH_SDL2=1;WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)
+ $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ true
+ true
+ stdcpp17
+ true
+ true
+ /Zo /utf-8 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies)
+ UseLinkTimeCodeGeneration
+
+
+
+
+
+
diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj.filters b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters
new file mode 100644
index 000000000..a54cb633f
--- /dev/null
+++ b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/duckstation-nogui/imgui_impl_sdl.cpp b/src/duckstation-nogui/imgui_impl_sdl.cpp
new file mode 100644
index 000000000..60a8e88b2
--- /dev/null
+++ b/src/duckstation-nogui/imgui_impl_sdl.cpp
@@ -0,0 +1,260 @@
+// dear imgui: Platform Binding for SDL2
+// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
+// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
+// (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.)
+
+// Implemented features:
+// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+// [X] Platform: Clipboard support.
+// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).
+// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
+// Missing features:
+// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME.
+// [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows).
+
+// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
+// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
+// https://github.com/ocornut/imgui
+
+// CHANGELOG
+// (minor and older changes stripped away, please see git history for details)
+// 2019-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
+// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
+// 2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).
+// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
+// 2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.
+// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
+// 2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.
+// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
+// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
+// 2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.
+// 2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.
+// 2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).
+// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
+// 2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.
+// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
+// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
+// 2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).
+// 2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.
+// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
+// 2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS.
+// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
+// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
+// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
+
+#include "imgui.h"
+#include "imgui_impl_sdl.h"
+
+// SDL
+// (the multi-viewports feature requires SDL features supported from SDL 2.0.4+. SDL 2.0.5+ is highly recommended)
+#include
+#include
+#if defined(__APPLE__)
+#include "TargetConditionals.h"
+#endif
+
+#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION_ATLEAST(2,0,4)
+#define SDL_HAS_WINDOW_ALPHA SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_ALWAYS_ON_TOP SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_USABLE_DISPLAY_BOUNDS SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_PER_MONITOR_DPI SDL_VERSION_ATLEAST(2,0,4)
+#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6)
+#define SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH SDL_VERSION_ATLEAST(2,0,5)
+#if !SDL_HAS_VULKAN
+static const Uint32 SDL_WINDOW_VULKAN = 0x10000000;
+#endif
+
+// Data
+static SDL_Window* g_Window = NULL;
+static Uint64 g_Time = 0;
+static bool g_MousePressed[3] = { false, false, false };
+static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
+static char* g_ClipboardTextData = NULL;
+
+static const char* ImGui_ImplSDL2_GetClipboardText(void*)
+{
+ if (g_ClipboardTextData)
+ SDL_free(g_ClipboardTextData);
+ g_ClipboardTextData = SDL_GetClipboardText();
+ return g_ClipboardTextData;
+}
+
+static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)
+{
+ SDL_SetClipboardText(text);
+}
+
+// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
+// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
+// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
+// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
+// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
+bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ switch (event->type)
+ {
+ case SDL_MOUSEWHEEL:
+ {
+ if (event->wheel.x > 0) io.MouseWheelH += 1;
+ if (event->wheel.x < 0) io.MouseWheelH -= 1;
+ if (event->wheel.y > 0) io.MouseWheel += 1;
+ if (event->wheel.y < 0) io.MouseWheel -= 1;
+ return true;
+ }
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true;
+ if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true;
+ if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true;
+ return true;
+ }
+ case SDL_TEXTINPUT:
+ {
+ io.AddInputCharactersUTF8(event->text.text);
+ return true;
+ }
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ int key = event->key.keysym.scancode;
+ IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
+ io.KeysDown[key] = (event->type == SDL_KEYDOWN);
+ io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
+ io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
+ io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
+ io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ImGui_ImplSDL2_Init(SDL_Window* window)
+{
+ g_Window = window;
+
+ // Setup back-end capabilities flags
+ ImGuiIO& io = ImGui::GetIO();
+ io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
+ io.BackendPlatformName = "imgui_impl_sdl";
+
+ // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
+ io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
+ io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
+ io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;
+ io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;
+ io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;
+ io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;
+ io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;
+ io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;
+ io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;
+ io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;
+ io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;
+ io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;
+ io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;
+ io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;
+ io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;
+ io.KeyMap[ImGuiKey_KeyPadEnter] = SDL_SCANCODE_RETURN2;
+ io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;
+ io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;
+ io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;
+ io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;
+ io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;
+ io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;
+
+ io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
+ io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
+ io.ClipboardUserData = NULL;
+
+ g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
+ g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
+ g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
+ g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
+ g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
+ 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()
+{
+ g_Window = NULL;
+
+ // Destroy last known clipboard data
+ if (g_ClipboardTextData)
+ SDL_free(g_ClipboardTextData);
+ g_ClipboardTextData = NULL;
+
+ // Destroy SDL mouse cursors
+ for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
+ SDL_FreeCursor(g_MouseCursors[cursor_n]);
+ memset(g_MouseCursors, 0, sizeof(g_MouseCursors));
+}
+
+// This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4.
+static void ImGui_ImplSDL2_UpdateMousePosAndButtons()
+{
+ ImGuiIO& io = ImGui::GetIO();
+
+ // [1]
+ // Only when requested by io.WantSetMousePos: set OS mouse pos from Dear ImGui mouse pos.
+ // (rarely used, mostly when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
+ if (io.WantSetMousePos)
+ {
+ SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y);
+ }
+ else
+ {
+ io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
+ }
+
+ // [2]
+ // Set Dear ImGui mouse pos from OS mouse pos + get buttons. (this is the common behavior)
+ int mouse_x_local, mouse_y_local;
+ Uint32 mouse_buttons = SDL_GetMouseState(&mouse_x_local, &mouse_y_local);
+ io.MouseDown[0] = g_MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
+ io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
+ io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
+ g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false;
+
+ // SDL 2.0.3 and before: single-viewport only
+ if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS)
+ io.MousePos = ImVec2((float)mouse_x_local, (float)mouse_y_local);
+}
+
+static void ImGui_ImplSDL2_UpdateMouseCursor()
+{
+ ImGuiIO& io = ImGui::GetIO();
+ if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
+ return;
+
+ ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
+ if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
+ {
+ // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
+ SDL_ShowCursor(SDL_FALSE);
+ }
+ else
+ {
+ // Show OS mouse cursor
+ SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]);
+ SDL_ShowCursor(SDL_TRUE);
+ }
+}
+
+void ImGui_ImplSDL2_NewFrame()
+{
+ ImGuiIO& io = ImGui::GetIO();
+
+ // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
+ static Uint64 frequency = SDL_GetPerformanceFrequency();
+ Uint64 current_time = SDL_GetPerformanceCounter();
+ io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f);
+ g_Time = current_time;
+
+ ImGui_ImplSDL2_UpdateMousePosAndButtons();
+ ImGui_ImplSDL2_UpdateMouseCursor();
+}
diff --git a/src/duckstation-nogui/imgui_impl_sdl.h b/src/duckstation-nogui/imgui_impl_sdl.h
new file mode 100644
index 000000000..0728620a4
--- /dev/null
+++ b/src/duckstation-nogui/imgui_impl_sdl.h
@@ -0,0 +1,28 @@
+// dear imgui: Platform Binding for SDL2
+// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)
+// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
+
+// Implemented features:
+// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
+// [X] Platform: Clipboard support.
+// [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).
+// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
+// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
+// Missing features:
+// [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME.
+// [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows).
+
+// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
+// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
+// https://github.com/ocornut/imgui
+
+#pragma once
+#include "imgui.h"
+
+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_Shutdown();
+IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame();
+IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);
diff --git a/src/duckstation-nogui/main.cpp b/src/duckstation-nogui/main.cpp
new file mode 100644
index 000000000..1f48cf920
--- /dev/null
+++ b/src/duckstation-nogui/main.cpp
@@ -0,0 +1,48 @@
+#include "common/assert.h"
+#include "common/log.h"
+#include "core/system.h"
+#include "frontend-common/sdl_initializer.h"
+#include "sdl_host_interface.h"
+#include
+#include
+#include
+
+int main(int argc, char* argv[])
+{
+ FrontendCommon::EnsureSDLInitialized();
+
+ std::unique_ptr host_interface = SDLHostInterface::Create();
+ std::unique_ptr boot_params;
+ if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
+ {
+ SDL_Quit();
+ return EXIT_FAILURE;
+ }
+
+ 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;
+ }
+
+ boot_params.reset();
+ }
+
+ host_interface->Run();
+ host_interface->Shutdown();
+ host_interface.reset();
+
+ SDL_Quit();
+ return EXIT_SUCCESS;
+}
diff --git a/src/duckstation-nogui/resource.h b/src/duckstation-nogui/resource.h
new file mode 100644
index 000000000..966189314
--- /dev/null
+++ b/src/duckstation-nogui/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by duckstation-sdl.rc
+//
+#define IDI_ICON1 102
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 103
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/duckstation-nogui/sdl_host_interface.cpp b/src/duckstation-nogui/sdl_host_interface.cpp
new file mode 100644
index 000000000..319129e4a
--- /dev/null
+++ b/src/duckstation-nogui/sdl_host_interface.cpp
@@ -0,0 +1,661 @@
+#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/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 "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
+#include
+Log_SetChannel(SDLHostInterface);
+
+#ifdef WIN32
+#include "frontend-common/d3d11_host_display.h"
+#endif
+
+SDLHostInterface::SDLHostInterface()
+{
+ m_run_later_event_id = SDL_RegisterEvents(1);
+}
+
+SDLHostInterface::~SDLHostInterface() = default;
+
+const char* SDLHostInterface::GetFrontendName() const
+{
+ return "DuckStation NoGUI Frontend";
+}
+
+ALWAYS_INLINE static TinyString GetWindowTitle()
+{
+ return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
+}
+
+bool SDLHostInterface::CreateSDLWindow()
+{
+ 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;
+
+ 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 = SDLUtil::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 (m_fullscreen)
+ SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+
+ // 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()
+{
+ SDL_DestroyWindow(m_window);
+ m_window = nullptr;
+}
+
+bool SDLHostInterface::CreateDisplay()
+{
+ std::optional wi = SDLUtil::GetWindowInfoForSDLWindow(m_window);
+ if (!wi.has_value())
+ {
+ ReportError("Failed to get window info from SDL window");
+ return false;
+ }
+
+ switch (g_settings.gpu_renderer)
+ {
+ case GPURenderer::HardwareVulkan:
+ m_display = std::make_unique();
+ break;
+
+ case GPURenderer::HardwareOpenGL:
+#ifndef WIN32
+ default:
+#endif
+ m_display = std::make_unique();
+ break;
+
+#ifdef WIN32
+ case GPURenderer::HardwareD3D11:
+ default:
+ m_display = std::make_unique();
+ 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);
+ 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...
+ 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);
+}
+
+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::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 callback)
+{
+ SDL_Event ev = {};
+ ev.type = SDL_USEREVENT;
+ ev.user.code = m_run_later_event_id;
+ ev.user.data1 = new std::function(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::Create()
+{
+ return std::make_unique();
+}
+
+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(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);
+
+ 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(GetSettingsFileName());
+ CommonHostInterface::LoadSettings(*m_settings_interface.get());
+ CommonHostInterface::FixIncompatibleSettings(false);
+}
+
+void SDLHostInterface::ReportError(const char* message)
+{
+ const bool was_fullscreen = IsFullscreen();
+ if (was_fullscreen)
+ SetFullscreen(false);
+
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation", message, m_window);
+
+ if (was_fullscreen)
+ SetFullscreen(true);
+}
+
+void SDLHostInterface::ReportMessage(const char* message)
+{
+ AddOSDMessage(message, 2.0f);
+}
+
+bool SDLHostInterface::ConfirmMessage(const char* message)
+{
+ const bool was_fullscreen = IsFullscreen();
+ if (was_fullscreen)
+ SetFullscreen(false);
+
+ SDL_MessageBoxData mbd = {};
+ mbd.flags = SDL_MESSAGEBOX_INFORMATION;
+ mbd.window = m_window;
+ mbd.title = "DuckStation";
+ mbd.message = message;
+ mbd.numbuttons = 2;
+
+ // Why the heck these are reversed I have no idea...
+ SDL_MessageBoxButtonData buttons[2] = {};
+ buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
+ buttons[1].buttonid = 0;
+ buttons[1].text = "Yes";
+ buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
+ buttons[0].buttonid = 1;
+ buttons[0].text = "No";
+ mbd.buttons = buttons;
+ mbd.numbuttons = countof(buttons);
+
+ int button_id = 0;
+ SDL_ShowMessageBox(&mbd, &button_id);
+ const bool result = (button_id == 0);
+
+ if (was_fullscreen)
+ SetFullscreen(true);
+
+ return result;
+}
+
+void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
+{
+ ImGui_ImplSDL2_ProcessEvent(event);
+
+ if (m_controller_interface &&
+ static_cast(m_controller_interface.get())->ProcessSDLEvent(event))
+ {
+ return;
+ }
+
+ switch (event->type)
+ {
+ case SDL_WINDOWEVENT:
+ {
+ if (event->window.event == SDL_WINDOWEVENT_RESIZED)
+ {
+ m_display->ResizeRenderWindow(event->window.data1, event->window.data2);
+ UpdateFramebufferScale();
+
+ if (!System::IsShutdown())
+ g_gpu->UpdateResolutionScale();
+ }
+ else if (event->window.event == SDL_WINDOWEVENT_MOVED)
+ {
+ UpdateFramebufferScale();
+ }
+ }
+ 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(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(ZeroExtend32(event->button.button));
+ const bool pressed = (event->type == SDL_MOUSEBUTTONDOWN);
+ HandleHostMouseEvent(button, pressed);
+ }
+ }
+ break;
+
+ case SDL_USEREVENT:
+ {
+ if (static_cast(event->user.code) == m_run_later_event_id)
+ {
+ std::function* callback = static_cast*>(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();
+ }
+}
diff --git a/src/duckstation-nogui/sdl_host_interface.h b/src/duckstation-nogui/sdl_host_interface.h
new file mode 100644
index 000000000..cb3d81727
--- /dev/null
+++ b/src/duckstation-nogui/sdl_host_interface.h
@@ -0,0 +1,87 @@
+#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
+#include
+#include
+#include