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 +#include +#include +#include + +class AudioStream; + +class INISettingsInterface; + +struct GameListEntry; + +class SDLHostInterface final : public CommonHostInterface +{ +public: + SDLHostInterface(); + ~SDLHostInterface(); + + static std::unique_ptr Create(); + + const char* GetFrontendName() const override; + + void ReportError(const char* message) override; + void ReportMessage(const char* message) override; + bool ConfirmMessage(const char* message) override; + + 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 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 GetHostKeyCode(const std::string_view key_code) const override; + void UpdateInputMap() 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 m_settings_interface; + u32 m_run_later_event_id = 0; + + bool m_fullscreen = false; + bool m_quit_request = false; +}; diff --git a/src/duckstation-nogui/sdl_key_names.h b/src/duckstation-nogui/sdl_key_names.h new file mode 100644 index 000000000..86e117c22 --- /dev/null +++ b/src/duckstation-nogui/sdl_key_names.h @@ -0,0 +1,353 @@ +#pragma once +#include "common/string.h" +#include "common/types.h" +#include +#include +#include +#include +#include +#include + +namespace SDLKeyNames { + +static const std::map s_sdl_key_names = {{SDLK_RETURN, "Return"}, + {SDLK_ESCAPE, "Escape"}, + {SDLK_BACKSPACE, "Backspace"}, + {SDLK_TAB, "Tab"}, + {SDLK_SPACE, "Space"}, + {SDLK_EXCLAIM, "Exclam"}, + {SDLK_QUOTEDBL, "QuoteDbl"}, + {SDLK_HASH, "Hash"}, + {SDLK_PERCENT, "Percent"}, + {SDLK_DOLLAR, "Dollar"}, + {SDLK_AMPERSAND, "Ampersand"}, + {SDLK_QUOTE, "Apostrophe"}, + {SDLK_LEFTPAREN, "ParenLeft"}, + {SDLK_RIGHTPAREN, "ParenRight"}, + {SDLK_ASTERISK, "Asterisk"}, + {SDLK_PLUS, "PLus"}, + {SDLK_COMMA, "Comma"}, + {SDLK_MINUS, "Minus"}, + {SDLK_PERIOD, "Period"}, + {SDLK_SLASH, "Slash"}, + {SDLK_0, "0"}, + {SDLK_1, "1"}, + {SDLK_2, "2"}, + {SDLK_3, "3"}, + {SDLK_4, "4"}, + {SDLK_5, "5"}, + {SDLK_6, "6"}, + {SDLK_7, "7"}, + {SDLK_8, "8"}, + {SDLK_9, "9"}, + {SDLK_COLON, "Colon"}, + {SDLK_SEMICOLON, "Semcolon"}, + {SDLK_LESS, "Less"}, + {SDLK_EQUALS, "Equal"}, + {SDLK_GREATER, "Greater"}, + {SDLK_QUESTION, "Question"}, + {SDLK_AT, "AT"}, + {SDLK_LEFTBRACKET, "BracketLeft"}, + {SDLK_BACKSLASH, "Backslash"}, + {SDLK_RIGHTBRACKET, "BracketRight"}, + {SDLK_CARET, "Caret"}, + {SDLK_UNDERSCORE, "Underscore"}, + {SDLK_BACKQUOTE, "QuoteLeft"}, + {SDLK_a, "A"}, + {SDLK_b, "B"}, + {SDLK_c, "C"}, + {SDLK_d, "D"}, + {SDLK_e, "E"}, + {SDLK_f, "F"}, + {SDLK_g, "G"}, + {SDLK_h, "H"}, + {SDLK_i, "I"}, + {SDLK_j, "J"}, + {SDLK_k, "K"}, + {SDLK_l, "L"}, + {SDLK_m, "M"}, + {SDLK_n, "N"}, + {SDLK_o, "O"}, + {SDLK_p, "P"}, + {SDLK_q, "Q"}, + {SDLK_r, "R"}, + {SDLK_s, "S"}, + {SDLK_t, "T"}, + {SDLK_u, "U"}, + {SDLK_v, "V"}, + {SDLK_w, "W"}, + {SDLK_x, "X"}, + {SDLK_y, "Y"}, + {SDLK_z, "Z"}, + {SDLK_CAPSLOCK, "CapsLock"}, + {SDLK_F1, "F1"}, + {SDLK_F2, "F2"}, + {SDLK_F3, "F3"}, + {SDLK_F4, "F4"}, + {SDLK_F5, "F5"}, + {SDLK_F6, "F6"}, + {SDLK_F7, "F7"}, + {SDLK_F8, "F8"}, + {SDLK_F9, "F9"}, + {SDLK_F10, "F10"}, + {SDLK_F11, "F11"}, + {SDLK_F12, "F12"}, + {SDLK_PRINTSCREEN, "Print"}, + {SDLK_SCROLLLOCK, "ScrollLock"}, + {SDLK_PAUSE, "Pause"}, + {SDLK_INSERT, "Insert"}, + {SDLK_HOME, "Home"}, + {SDLK_PAGEUP, "PageUp"}, + {SDLK_DELETE, "Delete"}, + {SDLK_END, "End"}, + {SDLK_PAGEDOWN, "PageDown"}, + {SDLK_RIGHT, "Right"}, + {SDLK_LEFT, "Left"}, + {SDLK_DOWN, "Down"}, + {SDLK_UP, "Up"}, + {SDLK_NUMLOCKCLEAR, "NumLock"}, + {SDLK_KP_DIVIDE, "Keypad+Divide"}, + {SDLK_KP_MULTIPLY, "Keypad+Multiply"}, + {SDLK_KP_MINUS, "Keypad+Minus"}, + {SDLK_KP_PLUS, "Keypad+Plus"}, + {SDLK_KP_ENTER, "Keypad+Return"}, + {SDLK_KP_1, "Keypad+1"}, + {SDLK_KP_2, "Keypad+2"}, + {SDLK_KP_3, "Keypad+3"}, + {SDLK_KP_4, "Keypad+4"}, + {SDLK_KP_5, "Keypad+5"}, + {SDLK_KP_6, "Keypad+6"}, + {SDLK_KP_7, "Keypad+7"}, + {SDLK_KP_8, "Keypad+8"}, + {SDLK_KP_9, "Keypad+9"}, + {SDLK_KP_0, "Keypad+0"}, + {SDLK_KP_PERIOD, "Keypad+Period"}, + {SDLK_APPLICATION, "Application"}, + {SDLK_POWER, "Power"}, + {SDLK_KP_EQUALS, "Keypad+Equal"}, + {SDLK_F13, "F13"}, + {SDLK_F14, "F14"}, + {SDLK_F15, "F15"}, + {SDLK_F16, "F16"}, + {SDLK_F17, "F17"}, + {SDLK_F18, "F18"}, + {SDLK_F19, "F19"}, + {SDLK_F20, "F20"}, + {SDLK_F21, "F21"}, + {SDLK_F22, "F22"}, + {SDLK_F23, "F23"}, + {SDLK_F24, "F24"}, + {SDLK_EXECUTE, "Execute"}, + {SDLK_HELP, "Help"}, + {SDLK_MENU, "Menu"}, + {SDLK_SELECT, "Select"}, + {SDLK_STOP, "Stop"}, + {SDLK_AGAIN, "Again"}, + {SDLK_UNDO, "Undo"}, + {SDLK_CUT, "Cut"}, + {SDLK_COPY, "Copy"}, + {SDLK_PASTE, "Paste"}, + {SDLK_FIND, "Find"}, + {SDLK_MUTE, "Mute"}, + {SDLK_VOLUMEUP, "VolumeUp"}, + {SDLK_VOLUMEDOWN, "VolumeDown"}, + {SDLK_KP_COMMA, "Keypad+Comma"}, + {SDLK_KP_EQUALSAS400, "Keypad+EqualAS400"}, + {SDLK_ALTERASE, "AltErase"}, + {SDLK_SYSREQ, "SysReq"}, + {SDLK_CANCEL, "Cancel"}, + {SDLK_CLEAR, "Clear"}, + {SDLK_PRIOR, "Prior"}, + {SDLK_RETURN2, "Return2"}, + {SDLK_SEPARATOR, "Separator"}, + {SDLK_OUT, "Out"}, + {SDLK_OPER, "Oper"}, + {SDLK_CLEARAGAIN, "ClearAgain"}, + {SDLK_CRSEL, "CrSel"}, + {SDLK_EXSEL, "ExSel"}, + {SDLK_KP_00, "Keypad+00"}, + {SDLK_KP_000, "Keypad+000"}, + {SDLK_THOUSANDSSEPARATOR, "ThousandsSeparator"}, + {SDLK_DECIMALSEPARATOR, "DecimalSeparator"}, + {SDLK_CURRENCYUNIT, "CurrencyUnit"}, + {SDLK_CURRENCYSUBUNIT, "CurrencySubunit"}, + {SDLK_KP_LEFTPAREN, "Keypad+ParenLeft"}, + {SDLK_KP_RIGHTPAREN, "Keypad+ParenRight"}, + {SDLK_KP_LEFTBRACE, "Keypad+LeftBrace"}, + {SDLK_KP_RIGHTBRACE, "Keypad+RightBrace"}, + {SDLK_KP_TAB, "Keypad+Tab"}, + {SDLK_KP_BACKSPACE, "Keypad+Backspace"}, + {SDLK_KP_A, "Keypad+A"}, + {SDLK_KP_B, "Keypad+B"}, + {SDLK_KP_C, "Keypad+C"}, + {SDLK_KP_D, "Keypad+D"}, + {SDLK_KP_E, "Keypad+E"}, + {SDLK_KP_F, "Keypad+F"}, + {SDLK_KP_XOR, "Keypad+XOR"}, + {SDLK_KP_POWER, "Keypad+Power"}, + {SDLK_KP_PERCENT, "Keypad+Percent"}, + {SDLK_KP_LESS, "Keypad+Less"}, + {SDLK_KP_GREATER, "Keypad+Greater"}, + {SDLK_KP_AMPERSAND, "Keypad+Ampersand"}, + {SDLK_KP_DBLAMPERSAND, "Keypad+AmpersandDbl"}, + {SDLK_KP_VERTICALBAR, "Keypad+Bar"}, + {SDLK_KP_DBLVERTICALBAR, "Keypad+BarDbl"}, + {SDLK_KP_COLON, "Keypad+Colon"}, + {SDLK_KP_HASH, "Keypad+Hash"}, + {SDLK_KP_SPACE, "Keypad+Space"}, + {SDLK_KP_AT, "Keypad+At"}, + {SDLK_KP_EXCLAM, "Keypad+Exclam"}, + {SDLK_KP_MEMSTORE, "Keypad+MemStore"}, + {SDLK_KP_MEMRECALL, "Keypad+MemRecall"}, + {SDLK_KP_MEMCLEAR, "Keypad+MemClear"}, + {SDLK_KP_MEMADD, "Keypad+MemAdd"}, + {SDLK_KP_MEMSUBTRACT, "Keypad+MemSubtract"}, + {SDLK_KP_MEMMULTIPLY, "Keypad+MemMultiply"}, + {SDLK_KP_MEMDIVIDE, "Keypad+MemDivide"}, + {SDLK_KP_PLUSMINUS, "Keypad+PlusMinus"}, + {SDLK_KP_CLEAR, "Keypad+Clear"}, + {SDLK_KP_CLEARENTRY, "Keypad+ClearEntry"}, + {SDLK_KP_BINARY, "Keypad+Binary"}, + {SDLK_KP_OCTAL, "Keypad+Octal"}, + {SDLK_KP_DECIMAL, "Keypad+Decimal"}, + {SDLK_KP_HEXADECIMAL, "Keypad+Hexadecimal"}, + {SDLK_LCTRL, "LeftControl"}, + {SDLK_LSHIFT, "LeftShift"}, + {SDLK_LALT, "LeftAlt"}, + {SDLK_LGUI, "Super_L"}, + {SDLK_RCTRL, "RightCtrl"}, + {SDLK_RSHIFT, "RightShift"}, + {SDLK_RALT, "RightAlt"}, + {SDLK_RGUI, "RightSuper"}, + {SDLK_MODE, "Mode"}, + {SDLK_AUDIONEXT, "MediaNext"}, + {SDLK_AUDIOPREV, "MediaPrevious"}, + {SDLK_AUDIOSTOP, "MediaStop"}, + {SDLK_AUDIOPLAY, "MediaPlay"}, + {SDLK_AUDIOMUTE, "VolumeMute"}, + {SDLK_MEDIASELECT, "MediaSelect"}, + {SDLK_WWW, "WWW"}, + {SDLK_MAIL, "Mail"}, + {SDLK_CALCULATOR, "Calculator"}, + {SDLK_COMPUTER, "Computer"}, + {SDLK_AC_SEARCH, "Search"}, + {SDLK_AC_HOME, "Home"}, + {SDLK_AC_BACK, "Back"}, + {SDLK_AC_FORWARD, "Forward"}, + {SDLK_AC_STOP, "Stop"}, + {SDLK_AC_REFRESH, "Refresh"}, + {SDLK_AC_BOOKMARKS, "Bookmarks"}, + {SDLK_BRIGHTNESSDOWN, "BrightnessDown"}, + {SDLK_BRIGHTNESSUP, "BrightnessUp"}, + {SDLK_DISPLAYSWITCH, "DisplaySwitch"}, + {SDLK_KBDILLUMTOGGLE, "IllumToggle"}, + {SDLK_KBDILLUMDOWN, "IllumDown"}, + {SDLK_KBDILLUMUP, "IllumUp"}, + {SDLK_EJECT, "Eject"}, + {SDLK_SLEEP, "Sleep"}, + {SDLK_APP1, "App1"}, + {SDLK_APP2, "App2"}, + {SDLK_AUDIOREWIND, "MediaRewind"}, + {SDLK_AUDIOFASTFORWARD, "MediaFastForward"}}; + +struct SDLKeyModifierEntry +{ + SDL_Keymod mod; + SDL_Keymod mod_mask; + SDL_Keycode key_left; + SDL_Keycode key_right; + const char* name; +}; + +static const std::array s_sdl_key_modifiers = { + {{KMOD_LSHIFT, static_cast(KMOD_LSHIFT | KMOD_RSHIFT), SDLK_LSHIFT, SDLK_RSHIFT, "Shift"}, + {KMOD_LCTRL, static_cast(KMOD_LCTRL | KMOD_LCTRL), SDLK_LCTRL, SDLK_RCTRL, "Control"}, + {KMOD_LALT, static_cast(KMOD_LALT | KMOD_RALT), SDLK_LALT, SDLK_RALT, "Alt"}, + {KMOD_LGUI, static_cast(KMOD_LGUI | KMOD_RGUI), SDLK_LGUI, SDLK_RGUI, "Meta"}}}; + +const char* GetKeyName(SDL_Keycode key) +{ + const auto it = s_sdl_key_names.find(key); + return it == s_sdl_key_names.end() ? nullptr : it->second; +} + +std::optional GetKeyCodeForName(const std::string_view key_name) +{ + for (const auto& it : s_sdl_key_names) + { + if (key_name == it.second) + return it.first; + } + + return std::nullopt; +} + +u32 KeyEventToInt(const SDL_Event* event) +{ + u32 code = static_cast(event->key.keysym.sym); + + const SDL_Keymod mods = static_cast(event->key.keysym.mod); + if (mods & (KMOD_LSHIFT | KMOD_RSHIFT)) + code |= static_cast(KMOD_LSHIFT) << 16; + if (mods & (KMOD_LCTRL | KMOD_RCTRL)) + code |= static_cast(KMOD_LCTRL) << 16; + if (mods & (KMOD_LALT | KMOD_RALT)) + code |= static_cast(KMOD_LALT) << 16; + if (mods & (KMOD_LGUI | KMOD_RGUI)) + code |= static_cast(KMOD_LGUI) << 16; + + return code; +} + +bool KeyEventToString(const SDL_Event* event, String& out_string) +{ + const SDL_Keycode key = event->key.keysym.sym; + const SDL_Keymod mods = static_cast(event->key.keysym.mod); + const char* key_name = GetKeyName(event->key.keysym.sym); + if (!key_name) + return false; + + out_string.Clear(); + + for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers) + { + if (mods & mod.mod_mask && key != mod.key_left && key != mod.key_right) + { + out_string.AppendString(mod.name); + out_string.AppendCharacter('+'); + } + } + + out_string.AppendString(key_name); + return true; +} + +std::optional ParseKeyString(const std::string_view key_str) +{ + u32 modifiers = 0; + std::string_view::size_type pos = 0; + for (;;) + { + std::string_view::size_type plus_pos = key_str.find('+', pos); + if (plus_pos == std::string_view::npos) + break; + + const std::string_view mod_part = key_str.substr(pos, plus_pos - pos); + for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers) + { + if (mod_part == mod.name) + { + modifiers |= static_cast(mod.mod); + break; + } + } + pos = plus_pos + 1; + } + + std::optional key_code = GetKeyCodeForName(key_str.substr(pos)); + if (!key_code) + return std::nullopt; + + return static_cast(key_code.value()) | (modifiers << 16); +} +} // namespace SDLKeyNames \ No newline at end of file diff --git a/src/duckstation-nogui/sdl_util.cpp b/src/duckstation-nogui/sdl_util.cpp new file mode 100644 index 000000000..318afab3c --- /dev/null +++ b/src/duckstation-nogui/sdl_util.cpp @@ -0,0 +1,107 @@ +#include "sdl_util.h" +#include "common/log.h" +#include +Log_SetChannel(SDLUtil); + +#ifdef __APPLE__ +#include +struct NSView; + +static NSView* GetContentViewFromWindow(NSWindow* window) +{ + // window.contentView + return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView")); +} +#endif + +namespace SDLUtil { + +std::optional 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(window_width); + wi.surface_height = static_cast(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(static_cast(syswm.info.x11.window)); + wi.display_connection = syswm.info.x11.display; + break; +#endif + +#ifdef SDL_VIDEO_DRIVER_WAYLAND + case SDL_SYSWM_WAYLAND: + wi.type = WindowInfo::Type::Wayland; + wi.window_handle = syswm.info.wl.surface; + wi.display_connection = syswm.info.wl.display; + break; +#endif + + default: + Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem)); + return std::nullopt; + } + + return wi; +} + +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 \ No newline at end of file diff --git a/src/duckstation-nogui/sdl_util.h b/src/duckstation-nogui/sdl_util.h new file mode 100644 index 000000000..685df5e25 --- /dev/null +++ b/src/duckstation-nogui/sdl_util.h @@ -0,0 +1,11 @@ +#pragma once +#include "common/types.h" +#include "common/window_info.h" +#include + +struct SDL_Window; + +namespace SDLUtil { +std::optional GetWindowInfoForSDLWindow(SDL_Window* window); +float GetDPIScaleFactor(SDL_Window* window); +} \ No newline at end of file