mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 05:45:38 +00:00
NoGUI: Migrate to new host abstraction
This commit is contained in:
parent
7277d29ff9
commit
6df7d9acda
|
@ -1,4 +1,4 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(duckstation C CXX)
|
project(duckstation C CXX)
|
||||||
|
|
||||||
message("CMake Version: ${CMAKE_VERSION}")
|
message("CMake Version: ${CMAKE_VERSION}")
|
||||||
|
|
29
CMakeModules/FindWaylandProtocols.cmake
Normal file
29
CMakeModules/FindWaylandProtocols.cmake
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# from https://github.com/glfw/glfw/blob/master/CMake/modules/FindWaylandProtocols.cmake
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
|
||||||
|
pkg_check_modules(WaylandProtocols QUIET wayland-protocols>=${WaylandProtocols_FIND_VERSION})
|
||||||
|
|
||||||
|
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=pkgdatadir wayland-protocols
|
||||||
|
OUTPUT_VARIABLE WaylandProtocols_PKGDATADIR
|
||||||
|
RESULT_VARIABLE _pkgconfig_failed)
|
||||||
|
if (_pkgconfig_failed)
|
||||||
|
message(FATAL_ERROR "Missing wayland-protocols pkgdatadir")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REGEX REPLACE "[\r\n]" "" WaylandProtocols_PKGDATADIR "${WaylandProtocols_PKGDATADIR}")
|
||||||
|
|
||||||
|
find_package_handle_standard_args(WaylandProtocols
|
||||||
|
FOUND_VAR
|
||||||
|
WaylandProtocols_FOUND
|
||||||
|
REQUIRED_VARS
|
||||||
|
WaylandProtocols_PKGDATADIR
|
||||||
|
VERSION_VAR
|
||||||
|
WaylandProtocols_VERSION
|
||||||
|
HANDLE_COMPONENTS
|
||||||
|
)
|
||||||
|
|
||||||
|
set(WAYLAND_PROTOCOLS_FOUND ${WaylandProtocols_FOUND})
|
||||||
|
set(WAYLAND_PROTOCOLS_PKGDATADIR ${WaylandProtocols_PKGDATADIR})
|
||||||
|
set(WAYLAND_PROTOCOLS_VERSION ${WaylandProtocols_VERSION})
|
||||||
|
|
38
CMakeModules/FindXKBCommon.cmake
Normal file
38
CMakeModules/FindXKBCommon.cmake
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# - Try to find XKBCommon
|
||||||
|
# Once done, this will define
|
||||||
|
#
|
||||||
|
# XKBCOMMON_FOUND - System has XKBCommon
|
||||||
|
# XKBCOMMON_INCLUDE_DIRS - The XKBCommon include directories
|
||||||
|
# XKBCOMMON_LIBRARIES - The libraries needed to use XKBCommon
|
||||||
|
# XKBCOMMON_DEFINITIONS - Compiler switches required for using XKBCommon
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PC_XKBCOMMON QUIET xkbcommon)
|
||||||
|
set(XKBCOMMON_DEFINITIONS ${PC_XKBCOMMON_CFLAGS_OTHER})
|
||||||
|
|
||||||
|
find_path(XKBCOMMON_INCLUDE_DIR
|
||||||
|
NAMES xkbcommon/xkbcommon.h
|
||||||
|
HINTS ${PC_XKBCOMMON_INCLUDE_DIR} ${PC_XKBCOMMON_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(XKBCOMMON_LIBRARY
|
||||||
|
NAMES xkbcommon
|
||||||
|
HINTS ${PC_XKBCOMMON_LIBRARY} ${PC_XKBCOMMON_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
set(XKBCOMMON_LIBRARIES ${XKBCOMMON_LIBRARY})
|
||||||
|
set(XKBCOMMON_LIBRARY_DIRS ${XKBCOMMON_LIBRARY_DIRS})
|
||||||
|
set(XKBCOMMON_INCLUDE_DIRS ${XKBCOMMON_INCLUDE_DIR})
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(XKBCommon DEFAULT_MSG
|
||||||
|
XKBCOMMON_LIBRARY
|
||||||
|
XKBCOMMON_INCLUDE_DIR
|
||||||
|
)
|
||||||
|
|
||||||
|
mark_as_advanced(XKBCOMMON_LIBRARY XKBCOMMON_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if (XKBCOMMON_INCLUDE_DIR AND XKBCOMMON_LIBRARY AND NOT TARGET XKBCommon::XKBCommon)
|
||||||
|
add_library(XKBCommon::XKBCommon UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(XKBCommon::XKBCommon PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${XKBCOMMON_INCLUDE_DIR}" IMPORTED_LOCATION "${XKBCOMMON_LIBRARY}")
|
||||||
|
endif()
|
|
@ -783,6 +783,7 @@ Global
|
||||||
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.Debug|x86.Build.0 = 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.ActiveCfg = DebugFast|ARM64
|
||||||
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x64.ActiveCfg = DebugFast|x64
|
{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.ActiveCfg = DebugFast|Win32
|
||||||
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x86.Build.0 = DebugFast|Win32
|
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugFast|x86.Build.0 = DebugFast|Win32
|
||||||
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugUWP|ARM64.ActiveCfg = DebugUWP|ARM64
|
{0A172B2E-DC67-49FC-A4C1-975F93C586C4}.DebugUWP|ARM64.ActiveCfg = DebugUWP|ARM64
|
||||||
|
|
|
@ -1,39 +1,18 @@
|
||||||
add_executable(duckstation-nogui
|
add_executable(duckstation-nogui
|
||||||
main.cpp
|
nogui_host.cpp
|
||||||
nogui_host_interface.cpp
|
nogui_host.h
|
||||||
nogui_host_interface.h
|
nogui_platform.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(duckstation-nogui PRIVATE core common imgui glad frontend-common scmversion)
|
target_link_libraries(duckstation-nogui PRIVATE core util common imgui glad frontend-common scmversion)
|
||||||
|
|
||||||
if(USE_SDL2)
|
|
||||||
target_sources(duckstation-nogui PRIVATE
|
|
||||||
sdl_host_interface.cpp
|
|
||||||
sdl_host_interface.h
|
|
||||||
sdl_key_names.h
|
|
||||||
)
|
|
||||||
target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(duckstation-nogui PRIVATE ${SDL2_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_EVDEV)
|
|
||||||
target_sources(duckstation-nogui PRIVATE
|
|
||||||
vty_host_interface.cpp
|
|
||||||
vty_host_interface.h
|
|
||||||
)
|
|
||||||
target_compile_definitions(duckstation-nogui PRIVATE "-DWITH_VTY=1")
|
|
||||||
target_compile_definitions(duckstation-nogui PRIVATE "-DUSE_LIBEVDEV=1")
|
|
||||||
target_include_directories(duckstation-nogui PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(duckstation-nogui PRIVATE ${LIBEVDEV_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(USE_DRMKMS)
|
|
||||||
target_compile_definitions(duckstation-nogui PRIVATE "-DWITH_DRMKMS=1")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
message(STATUS "Building Win32 NoGUI Platform.")
|
||||||
target_sources(duckstation-nogui PRIVATE
|
target_sources(duckstation-nogui PRIVATE
|
||||||
duckstation-nogui.manifest
|
duckstation-nogui.manifest
|
||||||
|
resource.h
|
||||||
|
win32_nogui_platform.cpp
|
||||||
|
win32_nogui_platform.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# We want a Windows subsystem application not console.
|
# We want a Windows subsystem application not console.
|
||||||
|
@ -42,3 +21,57 @@ if(WIN32)
|
||||||
DEBUG_POSTFIX "-debug")
|
DEBUG_POSTFIX "-debug")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(USE_X11)
|
||||||
|
message(STATUS "Building X11 NoGUI Platform.")
|
||||||
|
target_compile_definitions(duckstation-nogui PRIVATE "NOGUI_PLATFORM_X11=1")
|
||||||
|
target_sources(duckstation-nogui PRIVATE
|
||||||
|
x11_nogui_platform.cpp
|
||||||
|
x11_nogui_platform.h
|
||||||
|
)
|
||||||
|
target_include_directories(duckstation-nogui PRIVATE "${X11_INCLUDE_DIR}" "${X11_Xrandr_INCLUDE_PATH}")
|
||||||
|
target_link_libraries(duckstation-nogui PRIVATE "${X11_LIBRARIES}" "${X11_Xrandr_LIB}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_WAYLAND)
|
||||||
|
message(STATUS "Building Wayland NoGUI Platform.")
|
||||||
|
find_package(Wayland REQUIRED Client)
|
||||||
|
find_package(WaylandScanner REQUIRED)
|
||||||
|
find_package(WaylandProtocols 1.15 REQUIRED)
|
||||||
|
find_package(XKBCommon REQUIRED)
|
||||||
|
|
||||||
|
target_compile_definitions(duckstation-nogui PRIVATE "NOGUI_PLATFORM_WAYLAND=1")
|
||||||
|
target_sources(duckstation-nogui PRIVATE
|
||||||
|
wayland_nogui_platform.cpp
|
||||||
|
wayland_nogui_platform.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate the xdg-shell and xdg-decoration protocols at build-time.
|
||||||
|
# Because these are C, not C++, we have to put them in their own library, otherwise
|
||||||
|
# cmake tries to generate a C PCH as well as the C++ one...
|
||||||
|
ecm_add_wayland_client_protocol(WAYLAND_PLATFORM_SRCS
|
||||||
|
PROTOCOL "${WAYLAND_PROTOCOLS_PKGDATADIR}/stable/xdg-shell/xdg-shell.xml"
|
||||||
|
BASENAME xdg-shell)
|
||||||
|
ecm_add_wayland_client_protocol(WAYLAND_PLATFORM_SRCS
|
||||||
|
PROTOCOL "${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
|
||||||
|
BASENAME xdg-decoration)
|
||||||
|
add_library(duckstation-nogui-wayland-protocols STATIC ${WAYLAND_PLATFORM_SRCS})
|
||||||
|
target_include_directories(duckstation-nogui-wayland-protocols PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
|
||||||
|
target_link_libraries(duckstation-nogui PRIVATE
|
||||||
|
duckstation-nogui-wayland-protocols
|
||||||
|
Wayland::Client
|
||||||
|
XKBCommon::XKBCommon
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_DRMKMS AND USE_EVDEV)
|
||||||
|
message(STATUS "Building VTY/DRM/KMS/EVDev NoGUI Platform.")
|
||||||
|
target_compile_definitions(duckstation-nogui PRIVATE "NOGUI_PLATFORM_VTY=1" "WITH_DRMKMS=1")
|
||||||
|
target_sources(duckstation-nogui PRIVATE
|
||||||
|
vty_key_names.h
|
||||||
|
vty_nogui_platform.cpp
|
||||||
|
vty_nogui_platform.h
|
||||||
|
)
|
||||||
|
target_include_directories(duckstation-nogui PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(duckstation-nogui PRIVATE ${LIBEVDEV_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
|
@ -2,26 +2,29 @@
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
|
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="vty_host_interface.cpp">
|
<ClCompile Include="nogui_host.cpp" />
|
||||||
|
<ClCompile Include="vty_nogui_platform.cpp">
|
||||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="nogui_host_interface.cpp" />
|
<ClCompile Include="wayland_nogui_platform.cpp">
|
||||||
<ClCompile Include="sdl_host_interface.cpp" />
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
<ClCompile Include="main.cpp" />
|
</ClCompile>
|
||||||
<ClCompile Include="win32_host_interface.cpp" />
|
<ClCompile Include="win32_nogui_platform.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="vty_host_interface.h">
|
<ClInclude Include="nogui_host.h" />
|
||||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
<ClInclude Include="nogui_platform.h" />
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="evdev_key_names.h">
|
|
||||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="nogui_host_interface.h" />
|
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="sdl_host_interface.h" />
|
<ClInclude Include="vty_key_names.h">
|
||||||
<ClInclude Include="sdl_key_names.h" />
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
<ClInclude Include="win32_host_interface.h" />
|
</ClInclude>
|
||||||
|
<ClInclude Include="vty_nogui_platform.h">
|
||||||
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="wayland_nogui_platform.h">
|
||||||
|
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="win32_nogui_platform.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="duckstation-nogui.manifest" />
|
<Manifest Include="duckstation-nogui.manifest" />
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="nogui_host.cpp" />
|
||||||
<ClCompile Include="sdl_host_interface.cpp" />
|
<ClCompile Include="win32_nogui_platform.cpp" />
|
||||||
<ClCompile Include="nogui_host_interface.cpp" />
|
<ClCompile Include="vty_nogui_platform.cpp" />
|
||||||
<ClCompile Include="win32_host_interface.cpp" />
|
<ClCompile Include="wayland_nogui_platform.cpp" />
|
||||||
<ClCompile Include="vty_host_interface.cpp" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="sdl_host_interface.h" />
|
|
||||||
<ClInclude Include="sdl_key_names.h" />
|
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="nogui_host_interface.h" />
|
<ClInclude Include="nogui_host.h" />
|
||||||
<ClInclude Include="win32_host_interface.h" />
|
<ClInclude Include="win32_nogui_platform.h" />
|
||||||
<ClInclude Include="evdev_key_names.h" />
|
<ClInclude Include="vty_nogui_platform.h" />
|
||||||
<ClInclude Include="vty_host_interface.h" />
|
<ClInclude Include="vty_key_names.h" />
|
||||||
|
<ClInclude Include="nogui_platform.h" />
|
||||||
|
<ClInclude Include="wayland_nogui_platform.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="duckstation-nogui.manifest" />
|
<Manifest Include="duckstation-nogui.manifest" />
|
||||||
|
|
|
@ -1,143 +0,0 @@
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/file_system.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifdef WITH_VTY
|
|
||||||
#include "vty_host_interface.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef WITH_SDL2
|
|
||||||
#include "sdl_host_interface.h"
|
|
||||||
|
|
||||||
static bool IsSDLHostInterfaceAvailable()
|
|
||||||
{
|
|
||||||
#if defined(__linux__)
|
|
||||||
// Only available if we have a X11 or Wayland display.
|
|
||||||
if (std::getenv("DISPLAY") || std::getenv("WAYLAND_DISPLAY"))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
// Always available on Windows/Apple.
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include "common/windows_headers.h"
|
|
||||||
#include "win32_host_interface.h"
|
|
||||||
#include <shellapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::unique_ptr<NoGUIHostInterface> CreateHostInterface()
|
|
||||||
{
|
|
||||||
const char* platform = std::getenv("DUCKSTATION_NOGUI_PLATFORM");
|
|
||||||
std::unique_ptr<NoGUIHostInterface> host_interface;
|
|
||||||
|
|
||||||
#ifdef WITH_SDL2
|
|
||||||
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "sdl") == 0) && IsSDLHostInterfaceAvailable())
|
|
||||||
host_interface = SDLHostInterface::Create();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef WITH_VTY
|
|
||||||
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "vty") == 0))
|
|
||||||
host_interface = VTYHostInterface::Create();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "win32") == 0))
|
|
||||||
host_interface = Win32HostInterface::Create();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return host_interface;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int Run(std::unique_ptr<NoGUIHostInterface> host_interface, std::unique_ptr<SystemBootParameters> boot_params)
|
|
||||||
{
|
|
||||||
if (!host_interface->Initialize())
|
|
||||||
{
|
|
||||||
host_interface->Shutdown();
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boot_params)
|
|
||||||
host_interface->BootSystem(std::move(boot_params));
|
|
||||||
|
|
||||||
int result;
|
|
||||||
if (System::IsValid() || !host_interface->InBatchMode())
|
|
||||||
{
|
|
||||||
host_interface->Run();
|
|
||||||
result = EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
host_interface->ReportError("No file specified, and we're in batch mode. Exiting.");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
host_interface->Shutdown();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
|
|
||||||
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
|
|
||||||
{
|
|
||||||
std::unique_ptr<NoGUIHostInterface> host_interface = CreateHostInterface();
|
|
||||||
std::unique_ptr<SystemBootParameters> boot_params;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::vector<std::string> argc_strings;
|
|
||||||
argc_strings.reserve(1);
|
|
||||||
|
|
||||||
// CommandLineToArgvW() only adds the program path if the command line is empty?!
|
|
||||||
argc_strings.push_back(FileSystem::GetProgramPath());
|
|
||||||
|
|
||||||
if (std::wcslen(lpCmdLine) > 0)
|
|
||||||
{
|
|
||||||
int argc;
|
|
||||||
LPWSTR* argv_wide = CommandLineToArgvW(lpCmdLine, &argc);
|
|
||||||
if (argv_wide)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < argc; i++)
|
|
||||||
argc_strings.push_back(StringUtil::WideStringToUTF8String(argv_wide[i]));
|
|
||||||
|
|
||||||
LocalFree(argv_wide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<char*> argc_pointers;
|
|
||||||
argc_pointers.reserve(argc_strings.size());
|
|
||||||
for (std::string& arg : argc_strings)
|
|
||||||
argc_pointers.push_back(arg.data());
|
|
||||||
|
|
||||||
if (!host_interface->ParseCommandLineParameters(static_cast<int>(argc_pointers.size()), argc_pointers.data(),
|
|
||||||
&boot_params))
|
|
||||||
{
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Run(std::move(host_interface), std::move(boot_params));
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
std::unique_ptr<NoGUIHostInterface> host_interface = CreateHostInterface();
|
|
||||||
std::unique_ptr<SystemBootParameters> boot_params;
|
|
||||||
if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params))
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
|
|
||||||
return Run(std::move(host_interface), std::move(boot_params));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
1363
src/duckstation-nogui/nogui_host.cpp
Normal file
1363
src/duckstation-nogui/nogui_host.cpp
Normal file
File diff suppressed because it is too large
Load diff
34
src/duckstation-nogui/nogui_host.h
Normal file
34
src/duckstation-nogui/nogui_host.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace NoGUIHost {
|
||||||
|
/// Sets batch mode (exit after game shutdown).
|
||||||
|
bool InBatchMode();
|
||||||
|
void SetBatchMode(bool enabled);
|
||||||
|
|
||||||
|
/// Starts the virtual machine.
|
||||||
|
void StartSystem(SystemBootParameters params);
|
||||||
|
|
||||||
|
/// Returns the application name and version, optionally including debug/devel config indicator.
|
||||||
|
std::string GetAppNameAndVersion();
|
||||||
|
|
||||||
|
/// Returns the debug/devel config indicator.
|
||||||
|
std::string GetAppConfigSuffix();
|
||||||
|
|
||||||
|
/// Thread-safe settings access.
|
||||||
|
void SaveSettings();
|
||||||
|
|
||||||
|
/// Called on the UI thread in response to various events.
|
||||||
|
void ProcessPlatformWindowResize(s32 width, s32 height, float scale);
|
||||||
|
void ProcessPlatformMouseMoveEvent(float x, float y);
|
||||||
|
void ProcessPlatformMouseButtonEvent(s32 button, bool pressed);
|
||||||
|
void ProcessPlatformMouseWheelEvent(float x, float y);
|
||||||
|
void ProcessPlatformKeyEvent(s32 key, bool pressed);
|
||||||
|
void PlatformWindowFocusGained();
|
||||||
|
void PlatformWindowFocusLost();
|
||||||
|
bool GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height);
|
||||||
|
void SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height);
|
||||||
|
} // namespace NoGUIHost
|
|
@ -1,352 +0,0 @@
|
||||||
#include "nogui_host_interface.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/byte_stream.h"
|
|
||||||
#include "common/file_system.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "core/controller.h"
|
|
||||||
#include "core/gpu.h"
|
|
||||||
#include "core/host_display.h"
|
|
||||||
#include "core/imgui_styles.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include "frontend-common/controller_interface.h"
|
|
||||||
#include "frontend-common/fullscreen_ui.h"
|
|
||||||
#include "frontend-common/icon.h"
|
|
||||||
#include "frontend-common/ini_settings_interface.h"
|
|
||||||
#include "frontend-common/opengl_host_display.h"
|
|
||||||
#include "frontend-common/vulkan_host_display.h"
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "imgui_internal.h"
|
|
||||||
#include "imgui_stdlib.h"
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cmath>
|
|
||||||
Log_SetChannel(NoGUIHostInterface);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include "frontend-common/d3d11_host_display.h"
|
|
||||||
#include "frontend-common/d3d12_host_display.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NoGUIHostInterface::NoGUIHostInterface() = default;
|
|
||||||
|
|
||||||
NoGUIHostInterface::~NoGUIHostInterface() = default;
|
|
||||||
|
|
||||||
const char* NoGUIHostInterface::GetFrontendName() const
|
|
||||||
{
|
|
||||||
return "DuckStation NoGUI Frontend";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NoGUIHostInterface::Initialize()
|
|
||||||
{
|
|
||||||
SetUserDirectory();
|
|
||||||
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
|
|
||||||
|
|
||||||
// TODO: Make command line.
|
|
||||||
m_flags.force_fullscreen_ui = true;
|
|
||||||
|
|
||||||
if (!CommonHostInterface::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const bool start_fullscreen = m_flags.start_fullscreen || g_settings.start_fullscreen;
|
|
||||||
if (!CreatePlatformWindow())
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to create platform window");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreateDisplay(start_fullscreen))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to create host display");
|
|
||||||
DestroyPlatformWindow();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_fullscreen_ui_enabled)
|
|
||||||
{
|
|
||||||
FullscreenUI::SetDebugMenuAllowed(true);
|
|
||||||
FullscreenUI::QueueGameListRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// process events to pick up controllers before updating input map
|
|
||||||
PollAndUpdate();
|
|
||||||
UpdateInputMap();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::Shutdown()
|
|
||||||
{
|
|
||||||
DestroyDisplay();
|
|
||||||
DestroyPlatformWindow();
|
|
||||||
|
|
||||||
CommonHostInterface::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::SetDefaultSettings(SettingsInterface& si)
|
|
||||||
{
|
|
||||||
CommonHostInterface::SetDefaultSettings(si);
|
|
||||||
|
|
||||||
// TODO: Maybe we should bind this to F1 in the future.
|
|
||||||
si.SetStringValue("Hotkeys", "OpenQuickMenu", "Keyboard/Escape");
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::OnDisplayInvalidated() {}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::OnSystemPerformanceCountersUpdated() {}
|
|
||||||
|
|
||||||
bool NoGUIHostInterface::CreateDisplay(bool fullscreen)
|
|
||||||
{
|
|
||||||
std::optional<WindowInfo> wi = GetPlatformWindowInfo();
|
|
||||||
if (!wi)
|
|
||||||
{
|
|
||||||
ReportError("Failed to get platform window info");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert(!m_display);
|
|
||||||
switch (g_settings.gpu_renderer)
|
|
||||||
{
|
|
||||||
case GPURenderer::HardwareVulkan:
|
|
||||||
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::HardwareOpenGL:
|
|
||||||
#ifndef _WIN32
|
|
||||||
default:
|
|
||||||
#endif
|
|
||||||
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
case GPURenderer::HardwareD3D12:
|
|
||||||
m_display = std::make_unique<FrontendCommon::D3D12HostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::HardwareD3D11:
|
|
||||||
default:
|
|
||||||
m_display = std::make_unique<FrontendCommon::D3D11HostDisplay>();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device,
|
|
||||||
g_settings.gpu_threaded_presentation) ||
|
|
||||||
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
|
|
||||||
g_settings.gpu_threaded_presentation) ||
|
|
||||||
!CreateHostDisplayResources())
|
|
||||||
{
|
|
||||||
m_display->DestroyRenderDevice();
|
|
||||||
m_display.reset();
|
|
||||||
ReportError("Failed to create/initialize display render device");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullscreen)
|
|
||||||
SetFullscreen(true);
|
|
||||||
|
|
||||||
if (!CreateHostDisplayResources())
|
|
||||||
Log_WarningPrint("Failed to create host display resources");
|
|
||||||
|
|
||||||
Log_InfoPrintf("Host display initialized at %ux%u resolution", m_display->GetWindowWidth(),
|
|
||||||
m_display->GetWindowHeight());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::DestroyDisplay()
|
|
||||||
{
|
|
||||||
ReleaseHostDisplayResources();
|
|
||||||
|
|
||||||
if (m_display)
|
|
||||||
m_display->DestroyRenderDevice();
|
|
||||||
|
|
||||||
m_display.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NoGUIHostInterface::AcquireHostDisplay()
|
|
||||||
{
|
|
||||||
// Handle renderer switch if required.
|
|
||||||
const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI();
|
|
||||||
bool needs_switch = false;
|
|
||||||
switch (g_settings.gpu_renderer)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
case GPURenderer::HardwareD3D11:
|
|
||||||
needs_switch = (render_api != HostDisplay::RenderAPI::D3D11);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case GPURenderer::HardwareVulkan:
|
|
||||||
needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::HardwareOpenGL:
|
|
||||||
needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::Software:
|
|
||||||
default:
|
|
||||||
needs_switch = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needs_switch)
|
|
||||||
{
|
|
||||||
const bool was_fullscreen = IsFullscreen();
|
|
||||||
|
|
||||||
DestroyDisplay();
|
|
||||||
|
|
||||||
// We need to recreate the window, otherwise bad things happen...
|
|
||||||
DestroyPlatformWindow();
|
|
||||||
if (!CreatePlatformWindow())
|
|
||||||
Panic("Failed to recreate platform window on GPU renderer switch");
|
|
||||||
|
|
||||||
if (!CreateDisplay(was_fullscreen))
|
|
||||||
Panic("Failed to recreate display on GPU renderer switch");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::ReleaseHostDisplay()
|
|
||||||
{
|
|
||||||
// restore vsync, since we don't want to burn cycles at the menu
|
|
||||||
m_display->SetVSync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::RequestExit()
|
|
||||||
{
|
|
||||||
m_quit_request = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::Run()
|
|
||||||
{
|
|
||||||
while (!m_quit_request)
|
|
||||||
{
|
|
||||||
RunCallbacks();
|
|
||||||
PollAndUpdate();
|
|
||||||
|
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
if (System::IsRunning())
|
|
||||||
{
|
|
||||||
if (m_display_all_frames)
|
|
||||||
System::RunFrame();
|
|
||||||
else
|
|
||||||
System::RunFrames();
|
|
||||||
|
|
||||||
UpdateControllerMetaState();
|
|
||||||
if (m_frame_step_request)
|
|
||||||
{
|
|
||||||
m_frame_step_request = false;
|
|
||||||
PauseSystem(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rendering
|
|
||||||
{
|
|
||||||
DrawImGuiWindows();
|
|
||||||
ImGui::Render();
|
|
||||||
ImGui::EndFrame();
|
|
||||||
|
|
||||||
m_display->Render();
|
|
||||||
|
|
||||||
if (System::IsRunning())
|
|
||||||
{
|
|
||||||
System::UpdatePerformanceCounters();
|
|
||||||
|
|
||||||
if (m_throttler_enabled)
|
|
||||||
System::Throttle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save state on exit so it can be resumed
|
|
||||||
if (!System::IsShutdown())
|
|
||||||
PowerOffSystem(ShouldSaveResumeState());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::ReportMessage(const char* message)
|
|
||||||
{
|
|
||||||
Log_InfoPrint(message);
|
|
||||||
AddOSDMessage(message, 10.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::ReportError(const char* message)
|
|
||||||
{
|
|
||||||
Log_ErrorPrint(message);
|
|
||||||
|
|
||||||
if (!m_display)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const bool was_in_frame = GImGui->FrameCount != GImGui->FrameCountEnded;
|
|
||||||
if (was_in_frame)
|
|
||||||
ImGui::EndFrame();
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
RunCallbacks();
|
|
||||||
PollAndUpdate();
|
|
||||||
if (m_fullscreen_ui_enabled)
|
|
||||||
FullscreenUI::SetImGuiNavInputs();
|
|
||||||
|
|
||||||
ImGui::NewFrame();
|
|
||||||
done = FullscreenUI::DrawErrorWindow(message);
|
|
||||||
ImGui::EndFrame();
|
|
||||||
m_display->Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (was_in_frame)
|
|
||||||
ImGui::NewFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NoGUIHostInterface::ConfirmMessage(const char* message)
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Confirm: %s", message);
|
|
||||||
|
|
||||||
if (!m_display)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const bool was_in_frame = GImGui->FrameCount != GImGui->FrameCountEnded;
|
|
||||||
if (was_in_frame)
|
|
||||||
ImGui::EndFrame();
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
bool result = true;
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
RunCallbacks();
|
|
||||||
PollAndUpdate();
|
|
||||||
if (m_fullscreen_ui_enabled)
|
|
||||||
FullscreenUI::SetImGuiNavInputs();
|
|
||||||
|
|
||||||
ImGui::NewFrame();
|
|
||||||
done = FullscreenUI::DrawConfirmWindow(message, &result);
|
|
||||||
ImGui::EndFrame();
|
|
||||||
m_display->Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (was_in_frame)
|
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::RunLater(std::function<void()> callback)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(m_queued_callbacks_lock);
|
|
||||||
m_queued_callbacks.push_back(std::move(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoGUIHostInterface::RunCallbacks()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(m_queued_callbacks_lock);
|
|
||||||
|
|
||||||
while (!m_queued_callbacks.empty())
|
|
||||||
{
|
|
||||||
auto callback = std::move(m_queued_callbacks.front());
|
|
||||||
m_queued_callbacks.pop_front();
|
|
||||||
lock.unlock();
|
|
||||||
callback();
|
|
||||||
lock.lock();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/window_info.h"
|
|
||||||
#include "core/host_display.h"
|
|
||||||
#include "core/host_interface.h"
|
|
||||||
#include "frontend-common/common_host.h"
|
|
||||||
#include <array>
|
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class INISettingsInterface;
|
|
||||||
|
|
||||||
class NoGUIHostInterface : public CommonHostInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NoGUIHostInterface();
|
|
||||||
~NoGUIHostInterface();
|
|
||||||
|
|
||||||
const char* GetFrontendName() const override;
|
|
||||||
|
|
||||||
virtual bool Initialize() override;
|
|
||||||
virtual void Shutdown() override;
|
|
||||||
virtual void Run();
|
|
||||||
|
|
||||||
void ReportMessage(const char* message) override;
|
|
||||||
void ReportError(const char* message) override;
|
|
||||||
bool ConfirmMessage(const char* message) override;
|
|
||||||
|
|
||||||
void RunLater(std::function<void()> callback) override;
|
|
||||||
|
|
||||||
virtual void OnDisplayInvalidated() override;
|
|
||||||
virtual void OnSystemPerformanceCountersUpdated() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
enum : u32
|
|
||||||
{
|
|
||||||
DEFAULT_WINDOW_WIDTH = 1280,
|
|
||||||
DEFAULT_WINDOW_HEIGHT = 720
|
|
||||||
};
|
|
||||||
|
|
||||||
bool AcquireHostDisplay() override;
|
|
||||||
void ReleaseHostDisplay() override;
|
|
||||||
|
|
||||||
void RequestExit() override;
|
|
||||||
|
|
||||||
virtual void SetDefaultSettings(SettingsInterface& si) override;
|
|
||||||
|
|
||||||
virtual bool CreatePlatformWindow() = 0;
|
|
||||||
virtual void DestroyPlatformWindow() = 0;
|
|
||||||
virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0;
|
|
||||||
|
|
||||||
bool CreateDisplay(bool fullscreen);
|
|
||||||
void DestroyDisplay();
|
|
||||||
void RunCallbacks();
|
|
||||||
|
|
||||||
std::deque<std::function<void()>> m_queued_callbacks;
|
|
||||||
std::mutex m_queued_callbacks_lock;
|
|
||||||
|
|
||||||
bool m_quit_request = false;
|
|
||||||
};
|
|
59
src/duckstation-nogui/nogui_platform.h
Normal file
59
src/duckstation-nogui/nogui_platform.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/host_display.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
class SettingsInterface;
|
||||||
|
|
||||||
|
class NoGUIPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~NoGUIPlatform() = default;
|
||||||
|
|
||||||
|
virtual void ReportError(const std::string_view& title, const std::string_view& message) = 0;
|
||||||
|
|
||||||
|
virtual void SetDefaultConfig(SettingsInterface& si) = 0;
|
||||||
|
|
||||||
|
virtual bool CreatePlatformWindow(std::string title) = 0;
|
||||||
|
virtual void DestroyPlatformWindow() = 0;
|
||||||
|
|
||||||
|
virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0;
|
||||||
|
virtual void SetPlatformWindowTitle(std::string title) = 0;
|
||||||
|
virtual void* GetPlatformWindowHandle() = 0;
|
||||||
|
|
||||||
|
virtual std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) = 0;
|
||||||
|
virtual std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) = 0;
|
||||||
|
|
||||||
|
virtual void RunMessageLoop() = 0;
|
||||||
|
virtual void ExecuteInMessageLoop(std::function<void()> func) = 0;
|
||||||
|
virtual void QuitMessageLoop() = 0;
|
||||||
|
|
||||||
|
virtual void SetFullscreen(bool enabled) = 0;
|
||||||
|
|
||||||
|
virtual bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) = 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static std::unique_ptr<NoGUIPlatform> CreateWin32Platform();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef NOGUI_PLATFORM_WAYLAND
|
||||||
|
static std::unique_ptr<NoGUIPlatform> CreateWaylandPlatform();
|
||||||
|
#endif
|
||||||
|
#ifdef NOGUI_PLATFORM_X11
|
||||||
|
static std::unique_ptr<NoGUIPlatform> CreateX11Platform();
|
||||||
|
#endif
|
||||||
|
#ifdef NOGUI_PLATFORM_VTY
|
||||||
|
static std::unique_ptr<NoGUIPlatform> CreateVTYPlatform();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static constexpr s32 DEFAULT_WINDOW_WIDTH = 1280;
|
||||||
|
static constexpr s32 DEFAULT_WINDOW_HEIGHT = 720;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern std::unique_ptr<NoGUIPlatform> g_nogui_window;
|
|
@ -1,438 +0,0 @@
|
||||||
#include "sdl_host_interface.h"
|
|
||||||
#include "core/system.h"
|
|
||||||
#include "frontend-common/controller_interface.h"
|
|
||||||
#include "frontend-common/fullscreen_ui.h"
|
|
||||||
#include "frontend-common/icon.h"
|
|
||||||
#include "frontend-common/ini_settings_interface.h"
|
|
||||||
#include "frontend-common/sdl_controller_interface.h"
|
|
||||||
#include "frontend-common/sdl_initializer.h"
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "imgui_impl_sdl.h"
|
|
||||||
#include "scmversion/scmversion.h"
|
|
||||||
#include "sdl_key_names.h"
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cmath>
|
|
||||||
Log_SetChannel(SDLHostInterface);
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#include <objc/message.h>
|
|
||||||
struct NSView;
|
|
||||||
|
|
||||||
static NSView* GetContentViewFromWindow(NSWindow* window)
|
|
||||||
{
|
|
||||||
// window.contentView
|
|
||||||
return reinterpret_cast<NSView* (*)(id, SEL)>(objc_msgSend)(reinterpret_cast<id>(window), sel_getUid("contentView"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static float GetDPIScaleFactor(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef __APPLE__
|
|
||||||
static constexpr float DEFAULT_DPI = 72.0f;
|
|
||||||
#else
|
|
||||||
static constexpr float DEFAULT_DPI = 96.0f;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!window)
|
|
||||||
{
|
|
||||||
SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1,
|
|
||||||
SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
|
|
||||||
if (!dummy_window)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
const float scale = GetDPIScaleFactor(dummy_window);
|
|
||||||
|
|
||||||
SDL_DestroyWindow(dummy_window);
|
|
||||||
|
|
||||||
return scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
int display_index = SDL_GetWindowDisplayIndex(window);
|
|
||||||
float display_dpi = DEFAULT_DPI;
|
|
||||||
if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
return display_dpi / DEFAULT_DPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLHostInterface::SDLHostInterface() = default;
|
|
||||||
|
|
||||||
SDLHostInterface::~SDLHostInterface() = default;
|
|
||||||
|
|
||||||
const char* SDLHostInterface::GetFrontendName() const
|
|
||||||
{
|
|
||||||
return "DuckStation NoGUI Frontend";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<SDLHostInterface> SDLHostInterface::Create()
|
|
||||||
{
|
|
||||||
return std::make_unique<SDLHostInterface>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLHostInterface::Initialize()
|
|
||||||
{
|
|
||||||
FrontendCommon::EnsureSDLInitialized();
|
|
||||||
|
|
||||||
if (!NoGUIHostInterface::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::Shutdown()
|
|
||||||
{
|
|
||||||
NoGUIHostInterface::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLHostInterface::IsFullscreen() const
|
|
||||||
{
|
|
||||||
return m_fullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLHostInterface::SetFullscreen(bool enabled)
|
|
||||||
{
|
|
||||||
if (m_fullscreen == enabled)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const std::string fullscreen_mode(GetStringSettingValue("GPU", "FullscreenMode", ""));
|
|
||||||
const bool is_exclusive_fullscreen = (enabled && !fullscreen_mode.empty() && m_display->SupportsFullscreen());
|
|
||||||
const bool was_exclusive_fullscreen = m_display->IsFullscreen();
|
|
||||||
|
|
||||||
if (was_exclusive_fullscreen)
|
|
||||||
m_display->SetFullscreen(false, 0, 0, 0.0f);
|
|
||||||
|
|
||||||
SDL_SetWindowFullscreen(m_window, (enabled && !is_exclusive_fullscreen) ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
|
||||||
|
|
||||||
if (is_exclusive_fullscreen)
|
|
||||||
{
|
|
||||||
u32 width, height;
|
|
||||||
float refresh_rate;
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
if (ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate))
|
|
||||||
{
|
|
||||||
result = m_display->SetFullscreen(true, width, height, refresh_rate);
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
AddOSDMessage(TranslateStdString("OSDMessage", "Acquired exclusive fullscreen."), 10.0f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddOSDMessage(TranslateStdString("OSDMessage", "Failed to acquire exclusive fullscreen."), 10.0f);
|
|
||||||
enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_fullscreen = enabled;
|
|
||||||
|
|
||||||
const bool hide_cursor = (enabled && GetBoolSettingValue("Main", "HideCursorInFullscreen", true));
|
|
||||||
SDL_ShowCursor(hide_cursor ? SDL_DISABLE : SDL_ENABLE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
|
||||||
{
|
|
||||||
if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// use imgui scale as the dpr
|
|
||||||
const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x;
|
|
||||||
const s32 scaled_width =
|
|
||||||
std::max<s32>(static_cast<s32>(std::ceil(static_cast<float>(new_window_width) * dpi_scale)), 1);
|
|
||||||
const s32 scaled_height = std::max<s32>(
|
|
||||||
static_cast<s32>(std::ceil(static_cast<float>(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(),
|
|
||||||
1);
|
|
||||||
|
|
||||||
SDL_SetWindowSize(m_window, scaled_width, scaled_height);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ALWAYS_INLINE static TinyString GetWindowTitle()
|
|
||||||
{
|
|
||||||
return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLHostInterface::CreatePlatformWindow()
|
|
||||||
{
|
|
||||||
// Create window.
|
|
||||||
const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
|
||||||
|
|
||||||
int window_x, window_y, window_width, window_height;
|
|
||||||
GetSavedWindowGeometry(&window_x, &window_y, &window_width, &window_height);
|
|
||||||
m_window = SDL_CreateWindow(GetWindowTitle(), window_x, window_y, window_width, window_height, window_flags);
|
|
||||||
if (!m_window)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Set window icon.
|
|
||||||
SDL_Surface* icon_surface =
|
|
||||||
SDL_CreateRGBSurfaceFrom(const_cast<unsigned int*>(WINDOW_ICON_DATA), WINDOW_ICON_WIDTH, WINDOW_ICON_HEIGHT, 32,
|
|
||||||
WINDOW_ICON_WIDTH * sizeof(u32), UINT32_C(0x000000FF), UINT32_C(0x0000FF00),
|
|
||||||
UINT32_C(0x00FF0000), UINT32_C(0xFF000000));
|
|
||||||
if (icon_surface)
|
|
||||||
{
|
|
||||||
SDL_SetWindowIcon(m_window, icon_surface);
|
|
||||||
SDL_FreeSurface(icon_surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui_ImplSDL2_Init(m_window);
|
|
||||||
|
|
||||||
// Process events so that we have everything sorted out before creating a child window for the GL context (X11).
|
|
||||||
SDL_PumpEvents();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::DestroyPlatformWindow()
|
|
||||||
{
|
|
||||||
SaveWindowGeometry();
|
|
||||||
ImGui_ImplSDL2_Shutdown();
|
|
||||||
SDL_DestroyWindow(m_window);
|
|
||||||
m_window = nullptr;
|
|
||||||
m_fullscreen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> SDLHostInterface::GetPlatformWindowInfo()
|
|
||||||
{
|
|
||||||
SDL_SysWMinfo syswm = {};
|
|
||||||
SDL_VERSION(&syswm.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(m_window, &syswm))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("SDL_GetWindowWMInfo failed");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
int window_width, window_height;
|
|
||||||
SDL_GetWindowSize(m_window, &window_width, &window_height);
|
|
||||||
|
|
||||||
WindowInfo wi;
|
|
||||||
wi.surface_width = static_cast<u32>(window_width);
|
|
||||||
wi.surface_height = static_cast<u32>(window_height);
|
|
||||||
wi.surface_scale = GetDPIScaleFactor(m_window);
|
|
||||||
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
|
|
||||||
|
|
||||||
switch (syswm.subsystem)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
|
||||||
case SDL_SYSWM_WINDOWS:
|
|
||||||
wi.type = WindowInfo::Type::Win32;
|
|
||||||
wi.window_handle = syswm.info.win.window;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_COCOA
|
|
||||||
case SDL_SYSWM_COCOA:
|
|
||||||
wi.type = WindowInfo::Type::MacOS;
|
|
||||||
wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_X11
|
|
||||||
case SDL_SYSWM_X11:
|
|
||||||
wi.type = WindowInfo::Type::X11;
|
|
||||||
wi.window_handle = reinterpret_cast<void*>(static_cast<uintptr_t>(syswm.info.x11.window));
|
|
||||||
wi.display_connection = syswm.info.x11.display;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WAYLAND
|
|
||||||
case SDL_SYSWM_WAYLAND:
|
|
||||||
wi.type = WindowInfo::Type::Wayland;
|
|
||||||
wi.window_handle = syswm.info.wl.surface;
|
|
||||||
wi.display_connection = syswm.info.wl.display;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
default:
|
|
||||||
Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast<u32>(syswm.subsystem));
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wi;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<CommonHostInterface::HostKeyCode> SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
|
||||||
{
|
|
||||||
const std::optional<u32> code = SDLKeyNames::ParseKeyString(key_code);
|
|
||||||
if (!code)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return static_cast<HostKeyCode>(*code);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::SetMouseMode(bool relative, bool hide_cursor) {}
|
|
||||||
|
|
||||||
void SDLHostInterface::PollAndUpdate()
|
|
||||||
{
|
|
||||||
// Process SDL events before the controller interface can steal them.
|
|
||||||
const bool is_sdl_controller_interface =
|
|
||||||
(m_controller_interface && m_controller_interface->GetBackend() == ControllerInterface::Backend::SDL);
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
SDL_Event ev;
|
|
||||||
if (!SDL_PollEvent(&ev))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (is_sdl_controller_interface &&
|
|
||||||
static_cast<SDLControllerInterface*>(m_controller_interface.get())->ProcessSDLEvent(&ev))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleSDLEvent(&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui_ImplSDL2_NewFrame();
|
|
||||||
NoGUIHostInterface::PollAndUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::HandleSDLEvent(const SDL_Event* event)
|
|
||||||
{
|
|
||||||
ImGui_ImplSDL2_ProcessEvent(event);
|
|
||||||
|
|
||||||
switch (event->type)
|
|
||||||
{
|
|
||||||
case SDL_WINDOWEVENT:
|
|
||||||
{
|
|
||||||
switch (event->window.event)
|
|
||||||
{
|
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
||||||
{
|
|
||||||
s32 window_width, window_height;
|
|
||||||
SDL_GetWindowSize(m_window, &window_width, &window_height);
|
|
||||||
m_display->ResizeRenderWindow(window_width, window_height);
|
|
||||||
HostDisplayResized();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
|
||||||
{
|
|
||||||
if (g_settings.pause_on_focus_loss && System::IsRunning() && !m_was_paused_by_focus_loss)
|
|
||||||
{
|
|
||||||
PauseSystem(true);
|
|
||||||
m_was_paused_by_focus_loss = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
|
||||||
{
|
|
||||||
if (m_was_paused_by_focus_loss)
|
|
||||||
{
|
|
||||||
if (System::IsPaused())
|
|
||||||
PauseSystem(false);
|
|
||||||
m_was_paused_by_focus_loss = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_QUIT:
|
|
||||||
m_quit_request = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
case SDL_KEYUP:
|
|
||||||
{
|
|
||||||
const bool pressed = (event->type == SDL_KEYDOWN);
|
|
||||||
|
|
||||||
// Binding mode
|
|
||||||
if (m_fullscreen_ui_enabled && FullscreenUI::IsBindingInput())
|
|
||||||
{
|
|
||||||
if (event->key.repeat > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TinyString key_string;
|
|
||||||
if (SDLKeyNames::KeyEventToString(event, key_string))
|
|
||||||
{
|
|
||||||
if (FullscreenUI::HandleKeyboardBinding(key_string, pressed))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ImGui::GetIO().WantCaptureKeyboard && event->key.repeat == 0)
|
|
||||||
{
|
|
||||||
const u32 code = SDLKeyNames::KeyEventToInt(event);
|
|
||||||
HandleHostKeyEvent(code & SDLKeyNames::KEY_MASK, code & SDLKeyNames::MODIFIER_MASK, pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_MOUSEMOTION:
|
|
||||||
{
|
|
||||||
m_display->SetMousePosition(event->motion.x, event->motion.y);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
|
||||||
{
|
|
||||||
// map left -> 0, right -> 1, middle -> 2 to match with qt
|
|
||||||
static constexpr std::array<s32, 5> mouse_mapping = {{1, 3, 2, 4, 5}};
|
|
||||||
if (!ImGui::GetIO().WantCaptureMouse && event->button.button > 0 && event->button.button <= mouse_mapping.size())
|
|
||||||
{
|
|
||||||
const s32 button = mouse_mapping[event->button.button - 1];
|
|
||||||
const bool pressed = (event->type == SDL_MOUSEBUTTONDOWN);
|
|
||||||
HandleHostMouseEvent(button, pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::GetSavedWindowGeometry(int* x, int* y, int* width, int* height)
|
|
||||||
{
|
|
||||||
auto lock = GetSettingsLock();
|
|
||||||
*x = m_settings_interface->GetIntValue("SDLHostInterface", "WindowX", SDL_WINDOWPOS_UNDEFINED);
|
|
||||||
*y = m_settings_interface->GetIntValue("SDLHostInterface", "WindowY", SDL_WINDOWPOS_UNDEFINED);
|
|
||||||
|
|
||||||
*width = m_settings_interface->GetIntValue("SDLHostInterface", "WindowWidth", -1);
|
|
||||||
*height = m_settings_interface->GetIntValue("SDLHostInterface", "WindowHeight", -1);
|
|
||||||
|
|
||||||
if (*width < 0 || *height < 0)
|
|
||||||
{
|
|
||||||
*width = DEFAULT_WINDOW_WIDTH;
|
|
||||||
*height = DEFAULT_WINDOW_HEIGHT;
|
|
||||||
|
|
||||||
// macOS does DPI scaling differently..
|
|
||||||
#ifndef __APPLE__
|
|
||||||
{
|
|
||||||
// scale by default monitor's DPI
|
|
||||||
float scale = GetDPIScaleFactor(nullptr);
|
|
||||||
*width = static_cast<int>(std::round(static_cast<float>(*width) * scale));
|
|
||||||
*height = static_cast<int>(std::round(static_cast<float>(*height) * scale));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLHostInterface::SaveWindowGeometry()
|
|
||||||
{
|
|
||||||
if (m_fullscreen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int x = 0;
|
|
||||||
int y = 0;
|
|
||||||
SDL_GetWindowPosition(m_window, &x, &y);
|
|
||||||
|
|
||||||
int width = DEFAULT_WINDOW_WIDTH;
|
|
||||||
int height = DEFAULT_WINDOW_HEIGHT;
|
|
||||||
SDL_GetWindowSize(m_window, &width, &height);
|
|
||||||
|
|
||||||
int old_x, old_y, old_width, old_height;
|
|
||||||
GetSavedWindowGeometry(&old_x, &old_y, &old_width, &old_height);
|
|
||||||
if (x == old_x && y == old_y && width == old_width && height == old_height)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto lock = GetSettingsLock();
|
|
||||||
m_settings_interface->SetIntValue("SDLHostInterface", "WindowX", x);
|
|
||||||
m_settings_interface->SetIntValue("SDLHostInterface", "WindowY", y);
|
|
||||||
m_settings_interface->SetIntValue("SDLHostInterface", "WindowWidth", width);
|
|
||||||
m_settings_interface->SetIntValue("SDLHostInterface", "WindowHeight", height);
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "nogui_host_interface.h"
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
class SDLHostInterface final : public NoGUIHostInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SDLHostInterface();
|
|
||||||
~SDLHostInterface();
|
|
||||||
|
|
||||||
static std::unique_ptr<SDLHostInterface> Create();
|
|
||||||
|
|
||||||
const char* GetFrontendName() const override;
|
|
||||||
|
|
||||||
bool Initialize() override;
|
|
||||||
void Shutdown() override;
|
|
||||||
|
|
||||||
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
|
||||||
|
|
||||||
bool IsFullscreen() const override;
|
|
||||||
bool SetFullscreen(bool enabled) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void SetMouseMode(bool relative, bool hide_cursor) override;
|
|
||||||
|
|
||||||
void PollAndUpdate() override;
|
|
||||||
|
|
||||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
|
||||||
|
|
||||||
bool CreatePlatformWindow() override;
|
|
||||||
void DestroyPlatformWindow() override;
|
|
||||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void HandleSDLEvent(const SDL_Event* event);
|
|
||||||
|
|
||||||
void GetSavedWindowGeometry(int* x, int* y, int* width, int* height);
|
|
||||||
void SaveWindowGeometry();
|
|
||||||
|
|
||||||
SDL_Window* m_window = nullptr;
|
|
||||||
bool m_fullscreen = false;
|
|
||||||
bool m_was_paused_by_focus_loss = false;
|
|
||||||
};
|
|
|
@ -1,368 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/string.h"
|
|
||||||
#include "common/types.h"
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
#include <map>
|
|
||||||
#include <optional>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace SDLKeyNames {
|
|
||||||
|
|
||||||
enum : u32
|
|
||||||
{
|
|
||||||
MODIFIER_SHIFT = 16,
|
|
||||||
KEY_MASK = ((1 << MODIFIER_SHIFT) - 1),
|
|
||||||
MODIFIER_MASK = ~KEY_MASK,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const std::map<int, const char*> 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<SDLKeyModifierEntry, 4> s_sdl_key_modifiers = {
|
|
||||||
{{KMOD_LSHIFT, static_cast<SDL_Keymod>(KMOD_LSHIFT | KMOD_RSHIFT), SDLK_LSHIFT, SDLK_RSHIFT, "Shift"},
|
|
||||||
{KMOD_LCTRL, static_cast<SDL_Keymod>(KMOD_LCTRL | KMOD_RCTRL), SDLK_LCTRL, SDLK_RCTRL, "Control"},
|
|
||||||
{KMOD_LALT, static_cast<SDL_Keymod>(KMOD_LALT | KMOD_RALT), SDLK_LALT, SDLK_RALT, "Alt"},
|
|
||||||
{KMOD_LGUI, static_cast<SDL_Keymod>(KMOD_LGUI | KMOD_RGUI), SDLK_LGUI, SDLK_RGUI, "Meta"}}};
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::optional<SDL_Keycode> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u32 KeyEventToInt(const SDL_Event* event)
|
|
||||||
{
|
|
||||||
u32 code = static_cast<u32>(event->key.keysym.sym);
|
|
||||||
|
|
||||||
const SDL_Keymod mods = static_cast<SDL_Keymod>(event->key.keysym.mod);
|
|
||||||
if (mods & (KMOD_LSHIFT | KMOD_RSHIFT))
|
|
||||||
code |= static_cast<u32>(KMOD_LSHIFT) << MODIFIER_SHIFT;
|
|
||||||
if (mods & (KMOD_LCTRL | KMOD_RCTRL))
|
|
||||||
code |= static_cast<u32>(KMOD_LCTRL) << MODIFIER_SHIFT;
|
|
||||||
if (mods & (KMOD_LALT | KMOD_RALT))
|
|
||||||
code |= static_cast<u32>(KMOD_LALT) << MODIFIER_SHIFT;
|
|
||||||
if (mods & (KMOD_LGUI | KMOD_RGUI))
|
|
||||||
code |= static_cast<u32>(KMOD_LGUI) << MODIFIER_SHIFT;
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool KeyEventToString(const SDL_Event* event, String& out_string)
|
|
||||||
{
|
|
||||||
const SDL_Keycode key = event->key.keysym.sym;
|
|
||||||
const SDL_Keymod mods = static_cast<SDL_Keymod>(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::optional<u32> 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);
|
|
||||||
|
|
||||||
// Keypad in SDL is not a mod and should always be the last + in the string
|
|
||||||
bool known_mod = false;
|
|
||||||
for (const SDLKeyModifierEntry& mod : s_sdl_key_modifiers)
|
|
||||||
{
|
|
||||||
if (mod_part == mod.name)
|
|
||||||
{
|
|
||||||
modifiers |= static_cast<int>(mod.mod);
|
|
||||||
known_mod = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!known_mod)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = plus_pos + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<SDL_Keycode> key_code = GetKeyCodeForName(key_str.substr(pos));
|
|
||||||
if (!key_code)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return static_cast<u32>(key_code.value()) | (modifiers << MODIFIER_SHIFT);
|
|
||||||
}
|
|
||||||
} // namespace SDLKeyNames
|
|
|
@ -1,229 +0,0 @@
|
||||||
#include "vty_host_interface.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "evdev_key_names.h"
|
|
||||||
#include "frontend-common/ini_settings_interface.h"
|
|
||||||
#include "imgui.h"
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <linux/input-event-codes.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
Log_SetChannel(VTYHostInterface);
|
|
||||||
|
|
||||||
#ifdef WITH_DRMKMS
|
|
||||||
#include "common/drm_display.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
VTYHostInterface::VTYHostInterface() = default;
|
|
||||||
|
|
||||||
VTYHostInterface::~VTYHostInterface()
|
|
||||||
{
|
|
||||||
CloseEVDevFDs();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<NoGUIHostInterface> VTYHostInterface::Create()
|
|
||||||
{
|
|
||||||
return std::make_unique<VTYHostInterface>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VTYHostInterface::Initialize()
|
|
||||||
{
|
|
||||||
if (!NoGUIHostInterface::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
OpenEVDevFDs();
|
|
||||||
|
|
||||||
signal(SIGTERM, SIGTERMHandler);
|
|
||||||
signal(SIGINT, SIGTERMHandler);
|
|
||||||
signal(SIGQUIT, SIGTERMHandler);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::Shutdown()
|
|
||||||
{
|
|
||||||
CloseEVDevFDs();
|
|
||||||
NoGUIHostInterface::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VTYHostInterface::IsFullscreen() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VTYHostInterface::SetFullscreen(bool enabled)
|
|
||||||
{
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::FixIncompatibleSettings(bool display_osd_messages)
|
|
||||||
{
|
|
||||||
NoGUIHostInterface::FixIncompatibleSettings(display_osd_messages);
|
|
||||||
|
|
||||||
// Some things we definitely don't want.
|
|
||||||
g_settings.confim_power_off = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VTYHostInterface::CreatePlatformWindow()
|
|
||||||
{
|
|
||||||
SetImGuiKeyMap();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::DestroyPlatformWindow()
|
|
||||||
{
|
|
||||||
// nothing to destroy, it's all in the context
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> VTYHostInterface::GetPlatformWindowInfo()
|
|
||||||
{
|
|
||||||
WindowInfo wi;
|
|
||||||
wi.type = WindowInfo::Type::Display;
|
|
||||||
wi.surface_width = 0;
|
|
||||||
wi.surface_height = 0;
|
|
||||||
wi.surface_refresh_rate = 0.0f;
|
|
||||||
wi.surface_format = WindowInfo::SurfaceFormat::Auto;
|
|
||||||
|
|
||||||
const std::string fullscreen_mode = m_settings_interface->GetStringValue("GPU", "FullscreenMode", "");
|
|
||||||
if (!fullscreen_mode.empty())
|
|
||||||
{
|
|
||||||
if (!ParseFullscreenMode(fullscreen_mode, &wi.surface_width, &wi.surface_height, &wi.surface_refresh_rate))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to parse fullscreen mode '%s'", fullscreen_mode.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef WITH_DRMKMS
|
|
||||||
// set to current mode
|
|
||||||
if (wi.surface_width == 0)
|
|
||||||
{
|
|
||||||
if (!DRMDisplay::GetCurrentMode(&wi.surface_width, &wi.surface_height, &wi.surface_refresh_rate))
|
|
||||||
Log_ErrorPrintf("Failed to get current mode, will use default.");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return wi;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::PollAndUpdate()
|
|
||||||
{
|
|
||||||
PollEvDevKeyboards();
|
|
||||||
|
|
||||||
NoGUIHostInterface::PollAndUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::SetMouseMode(bool relative, bool hide_cursor) {}
|
|
||||||
|
|
||||||
void VTYHostInterface::OpenEVDevFDs()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 1000; i++)
|
|
||||||
{
|
|
||||||
TinyString path;
|
|
||||||
path.Format("/dev/input/event%d", i);
|
|
||||||
|
|
||||||
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
|
||||||
if (fd < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
struct libevdev* obj;
|
|
||||||
if (libevdev_new_from_fd(fd, &obj) != 0)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray());
|
|
||||||
close(fd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log_DevPrintf("Input path: %s", path.GetCharArray());
|
|
||||||
Log_DevPrintf("Input device name: \"%s\"", libevdev_get_name(obj));
|
|
||||||
Log_DevPrintf("Input device ID: bus %#x vendor %#x product %#x", libevdev_get_id_bustype(obj),
|
|
||||||
libevdev_get_id_vendor(obj), libevdev_get_id_product(obj));
|
|
||||||
if (!libevdev_has_event_code(obj, EV_KEY, KEY_SPACE))
|
|
||||||
{
|
|
||||||
Log_DevPrintf("This device does not look like a keyboard");
|
|
||||||
libevdev_free(obj);
|
|
||||||
close(fd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int grab_res = libevdev_grab(obj, LIBEVDEV_GRAB);
|
|
||||||
if (grab_res != 0)
|
|
||||||
Log_WarningPrintf("Failed to grab '%s' (%s): %d", libevdev_get_name(obj), path.GetCharArray(), grab_res);
|
|
||||||
|
|
||||||
m_evdev_keyboards.push_back({obj, fd});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::CloseEVDevFDs()
|
|
||||||
{
|
|
||||||
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
|
||||||
{
|
|
||||||
libevdev_grab(kb.obj, LIBEVDEV_UNGRAB);
|
|
||||||
libevdev_free(kb.obj);
|
|
||||||
close(kb.fd);
|
|
||||||
}
|
|
||||||
m_evdev_keyboards.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::PollEvDevKeyboards()
|
|
||||||
{
|
|
||||||
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
|
||||||
{
|
|
||||||
struct input_event ev;
|
|
||||||
while (libevdev_next_event(kb.obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0)
|
|
||||||
{
|
|
||||||
// auto-repeat
|
|
||||||
if (ev.value == 2)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const bool pressed = (ev.value == 1);
|
|
||||||
const HostKeyCode code = static_cast<HostKeyCode>(ev.code);
|
|
||||||
if (static_cast<unsigned>(code) < countof(ImGuiIO::KeysDown))
|
|
||||||
ImGui::GetIO().KeysDown[code] = pressed;
|
|
||||||
|
|
||||||
HandleHostKeyEvent(code, 0, pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::SetImGuiKeyMap()
|
|
||||||
{
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
|
||||||
|
|
||||||
io.KeyMap[ImGuiKey_Tab] = KEY_TAB;
|
|
||||||
io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT;
|
|
||||||
io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT;
|
|
||||||
io.KeyMap[ImGuiKey_UpArrow] = KEY_UP;
|
|
||||||
io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN;
|
|
||||||
io.KeyMap[ImGuiKey_PageUp] = KEY_PAGEUP;
|
|
||||||
io.KeyMap[ImGuiKey_PageDown] = KEY_PAGEDOWN;
|
|
||||||
io.KeyMap[ImGuiKey_Home] = KEY_HOME;
|
|
||||||
io.KeyMap[ImGuiKey_End] = KEY_END;
|
|
||||||
io.KeyMap[ImGuiKey_Insert] = KEY_INSERT;
|
|
||||||
io.KeyMap[ImGuiKey_Delete] = KEY_DELETE;
|
|
||||||
io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSPACE;
|
|
||||||
io.KeyMap[ImGuiKey_Space] = KEY_SPACE;
|
|
||||||
io.KeyMap[ImGuiKey_Enter] = KEY_ENTER;
|
|
||||||
io.KeyMap[ImGuiKey_Escape] = KEY_ESC;
|
|
||||||
io.KeyMap[ImGuiKey_KeyPadEnter] = KEY_KPENTER;
|
|
||||||
io.KeyMap[ImGuiKey_A] = KEY_A;
|
|
||||||
io.KeyMap[ImGuiKey_C] = KEY_C;
|
|
||||||
io.KeyMap[ImGuiKey_V] = KEY_V;
|
|
||||||
io.KeyMap[ImGuiKey_X] = KEY_X;
|
|
||||||
io.KeyMap[ImGuiKey_Y] = KEY_Y;
|
|
||||||
io.KeyMap[ImGuiKey_Z] = KEY_Z;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<VTYHostInterface::HostKeyCode> VTYHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
|
||||||
{
|
|
||||||
std::optional<int> kc = EvDevKeyNames::GetKeyCodeForName(key_code);
|
|
||||||
if (!kc.has_value())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return static_cast<HostKeyCode>(kc.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
void VTYHostInterface::SIGTERMHandler(int sig)
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Recieved SIGTERM");
|
|
||||||
static_cast<VTYHostInterface*>(g_host_interface)->m_quit_request = true;
|
|
||||||
signal(sig, SIG_DFL);
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "nogui_host_interface.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <libevdev/libevdev.h>
|
|
||||||
|
|
||||||
class VTYHostInterface final : public NoGUIHostInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
VTYHostInterface();
|
|
||||||
~VTYHostInterface();
|
|
||||||
|
|
||||||
bool Initialize();
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
bool IsFullscreen() const override;
|
|
||||||
bool SetFullscreen(bool enabled) override;
|
|
||||||
|
|
||||||
static std::unique_ptr<NoGUIHostInterface> Create();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void FixIncompatibleSettings(bool display_osd_messages) override;
|
|
||||||
|
|
||||||
bool CreatePlatformWindow() override;
|
|
||||||
void DestroyPlatformWindow() override;
|
|
||||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
|
||||||
|
|
||||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
|
||||||
|
|
||||||
void PollAndUpdate() override;
|
|
||||||
|
|
||||||
void SetMouseMode(bool relative, bool hide_cursor) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void SIGTERMHandler(int sig);
|
|
||||||
|
|
||||||
void OpenEVDevFDs();
|
|
||||||
void CloseEVDevFDs();
|
|
||||||
void PollEvDevKeyboards();
|
|
||||||
void SetImGuiKeyMap();
|
|
||||||
|
|
||||||
struct EvDevKeyboard
|
|
||||||
{
|
|
||||||
struct libevdev* obj;
|
|
||||||
int fd;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<EvDevKeyboard> m_evdev_keyboards;
|
|
||||||
};
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
namespace EvDevKeyNames {
|
namespace VTYKeyNames {
|
||||||
|
|
||||||
static const std::map<int, const char*> s_evdev_key_names = {{KEY_ESC, "Escape"},
|
static const std::map<int, const char*> s_evdev_key_names = {{KEY_ESC, "Escape"},
|
||||||
{KEY_1, "1"},
|
{KEY_1, "1"},
|
||||||
|
@ -275,4 +275,4 @@ static inline std::optional<int> GetKeyCodeForName(const std::string_view key_na
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace EvDevKeyNames
|
} // namespace VTYKeyNames
|
233
src/duckstation-nogui/vty_nogui_platform.cpp
Normal file
233
src/duckstation-nogui/vty_nogui_platform.cpp
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
#include "vty_nogui_platform.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/threading.h"
|
||||||
|
#include "core/host.h"
|
||||||
|
#include "core/host_settings.h"
|
||||||
|
#include "nogui_host.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include "vty_key_names.h"
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
Log_SetChannel(VTYNoGUIPlatform);
|
||||||
|
|
||||||
|
#ifdef WITH_DRMKMS
|
||||||
|
#include "common/drm_display.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VTYNoGUIPlatform::VTYNoGUIPlatform()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
VTYNoGUIPlatform::~VTYNoGUIPlatform()
|
||||||
|
{
|
||||||
|
CloseEVDevFDs();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateVTYPlatform()
|
||||||
|
{
|
||||||
|
std::unique_ptr<VTYNoGUIPlatform> platform(std::make_unique<VTYNoGUIPlatform>());
|
||||||
|
if (!platform->Initialize())
|
||||||
|
platform.reset();
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VTYNoGUIPlatform::Initialize()
|
||||||
|
{
|
||||||
|
OpenEVDevFDs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||||
|
{
|
||||||
|
const std::string title_copy(title);
|
||||||
|
const std::string message_copy(message);
|
||||||
|
Log_ErrorPrintf("%s: %s", title_copy.c_str(), message_copy.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VTYNoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::DestroyPlatformWindow()
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<WindowInfo> VTYNoGUIPlatform::GetPlatformWindowInfo()
|
||||||
|
{
|
||||||
|
WindowInfo wi;
|
||||||
|
wi.type = WindowInfo::Type::Display;
|
||||||
|
wi.surface_width = 0;
|
||||||
|
wi.surface_height = 0;
|
||||||
|
wi.surface_refresh_rate = 0.0f;
|
||||||
|
wi.surface_format = WindowInfo::SurfaceFormat::Auto;
|
||||||
|
|
||||||
|
const std::string fullscreen_mode = Host::GetStringSettingValue("GPU", "FullscreenMode", "");
|
||||||
|
if (!fullscreen_mode.empty())
|
||||||
|
{
|
||||||
|
if (!HostDisplay::ParseFullscreenMode(fullscreen_mode, &wi.surface_width, &wi.surface_height,
|
||||||
|
&wi.surface_refresh_rate))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to parse fullscreen mode '%s'", fullscreen_mode.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_DRMKMS
|
||||||
|
// set to current mode
|
||||||
|
if (wi.surface_width == 0)
|
||||||
|
{
|
||||||
|
if (!DRMDisplay::GetCurrentMode(&wi.surface_width, &wi.surface_height, &wi.surface_refresh_rate))
|
||||||
|
Log_ErrorPrintf("Failed to get current mode, will use default.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This isn't great, but it's an approximation at least..
|
||||||
|
if (wi.surface_width > 0)
|
||||||
|
wi.surface_scale = std::max(0.1f, static_cast<float>(wi.surface_width) / 1280.0f);
|
||||||
|
|
||||||
|
return wi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::SetFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
// already fullscreen :-)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VTYNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Window Title: %s", title.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void* VTYNoGUIPlatform::GetPlatformWindowHandle()
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::RunMessageLoop()
|
||||||
|
{
|
||||||
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
PollEvDevKeyboards();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
while (!m_callback_queue.empty())
|
||||||
|
{
|
||||||
|
std::function<void()> func = std::move(m_callback_queue.front());
|
||||||
|
m_callback_queue.pop_front();
|
||||||
|
lock.unlock();
|
||||||
|
func();
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this suck less.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
m_callback_queue.push_back(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::QuitMessageLoop()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::OpenEVDevFDs()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
TinyString path;
|
||||||
|
path.Format("/dev/input/event%d", i);
|
||||||
|
|
||||||
|
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
struct libevdev* obj;
|
||||||
|
if (libevdev_new_from_fd(fd, &obj) != 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray());
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_DevPrintf("Input path: %s", path.GetCharArray());
|
||||||
|
Log_DevPrintf("Input device name: \"%s\"", libevdev_get_name(obj));
|
||||||
|
Log_DevPrintf("Input device ID: bus %#x vendor %#x product %#x", libevdev_get_id_bustype(obj),
|
||||||
|
libevdev_get_id_vendor(obj), libevdev_get_id_product(obj));
|
||||||
|
if (!libevdev_has_event_code(obj, EV_KEY, KEY_SPACE))
|
||||||
|
{
|
||||||
|
Log_DevPrintf("This device does not look like a keyboard");
|
||||||
|
libevdev_free(obj);
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int grab_res = libevdev_grab(obj, LIBEVDEV_GRAB);
|
||||||
|
if (grab_res != 0)
|
||||||
|
Log_WarningPrintf("Failed to grab '%s' (%s): %d", libevdev_get_name(obj), path.GetCharArray(), grab_res);
|
||||||
|
|
||||||
|
m_evdev_keyboards.push_back({obj, fd});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::CloseEVDevFDs()
|
||||||
|
{
|
||||||
|
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
||||||
|
{
|
||||||
|
libevdev_grab(kb.obj, LIBEVDEV_UNGRAB);
|
||||||
|
libevdev_free(kb.obj);
|
||||||
|
close(kb.fd);
|
||||||
|
}
|
||||||
|
m_evdev_keyboards.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VTYNoGUIPlatform::PollEvDevKeyboards()
|
||||||
|
{
|
||||||
|
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
||||||
|
{
|
||||||
|
struct input_event ev;
|
||||||
|
while (libevdev_next_event(kb.obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0)
|
||||||
|
{
|
||||||
|
// auto-repeat
|
||||||
|
// TODO: forward char to imgui
|
||||||
|
if (ev.value == 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const bool pressed = (ev.value == 1);
|
||||||
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(ev.code), pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> VTYNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||||
|
{
|
||||||
|
std::optional<int> converted(VTYKeyNames::GetKeyCodeForName(str));
|
||||||
|
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> VTYNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||||
|
{
|
||||||
|
const char* keyname = VTYKeyNames::GetKeyName(static_cast<int>(code));
|
||||||
|
return keyname ? std::optional<std::string>(std::string(keyname)) : std::nullopt;
|
||||||
|
}
|
57
src/duckstation-nogui/vty_nogui_platform.h
Normal file
57
src/duckstation-nogui/vty_nogui_platform.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <libevdev/libevdev.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class VTYNoGUIPlatform : public NoGUIPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VTYNoGUIPlatform();
|
||||||
|
~VTYNoGUIPlatform();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void ReportError(const std::string_view& title, const std::string_view& message) override;
|
||||||
|
|
||||||
|
void SetDefaultConfig(SettingsInterface& si) override;
|
||||||
|
|
||||||
|
bool CreatePlatformWindow(std::string title) override;
|
||||||
|
void DestroyPlatformWindow() override;
|
||||||
|
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||||
|
void SetPlatformWindowTitle(std::string title) override;
|
||||||
|
void* GetPlatformWindowHandle() override;
|
||||||
|
|
||||||
|
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) override;
|
||||||
|
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) override;
|
||||||
|
|
||||||
|
void RunMessageLoop() override;
|
||||||
|
void ExecuteInMessageLoop(std::function<void()> func) override;
|
||||||
|
void QuitMessageLoop() override;
|
||||||
|
|
||||||
|
void SetFullscreen(bool enabled) override;
|
||||||
|
|
||||||
|
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OpenEVDevFDs();
|
||||||
|
void CloseEVDevFDs();
|
||||||
|
void PollEvDevKeyboards();
|
||||||
|
void SetImGuiKeyMap();
|
||||||
|
|
||||||
|
struct EvDevKeyboard
|
||||||
|
{
|
||||||
|
struct libevdev* obj;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<EvDevKeyboard> m_evdev_keyboards;
|
||||||
|
|
||||||
|
std::deque<std::function<void()>> m_callback_queue;
|
||||||
|
std::mutex m_callback_queue_mutex;
|
||||||
|
|
||||||
|
std::atomic_bool m_message_loop_running{false};
|
||||||
|
};
|
465
src/duckstation-nogui/wayland_nogui_platform.cpp
Normal file
465
src/duckstation-nogui/wayland_nogui_platform.cpp
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
#include "wayland_nogui_platform.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/threading.h"
|
||||||
|
#include "core/host.h"
|
||||||
|
#include "core/host_settings.h"
|
||||||
|
#include "nogui_host.h"
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
Log_SetChannel(WaylandNoGUIPlatform);
|
||||||
|
|
||||||
|
WaylandNoGUIPlatform::WaylandNoGUIPlatform()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandNoGUIPlatform::~WaylandNoGUIPlatform()
|
||||||
|
{
|
||||||
|
if (m_xkb_state)
|
||||||
|
xkb_state_unref(m_xkb_state);
|
||||||
|
if (m_xkb_keymap)
|
||||||
|
xkb_keymap_unref(m_xkb_keymap);
|
||||||
|
if (m_wl_keyboard)
|
||||||
|
wl_keyboard_destroy(m_wl_keyboard);
|
||||||
|
if (m_wl_pointer)
|
||||||
|
wl_pointer_destroy(m_wl_pointer);
|
||||||
|
if (m_wl_seat)
|
||||||
|
wl_seat_destroy(m_wl_seat);
|
||||||
|
if (m_xkb_context)
|
||||||
|
xkb_context_unref(m_xkb_context);
|
||||||
|
if (m_registry)
|
||||||
|
wl_registry_destroy(m_registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaylandNoGUIPlatform::Initialize()
|
||||||
|
{
|
||||||
|
m_xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||||
|
if (!m_xkb_context)
|
||||||
|
{
|
||||||
|
Panic("Failed to create XKB context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_display = wl_display_connect(nullptr);
|
||||||
|
if (!m_display)
|
||||||
|
{
|
||||||
|
Panic("Failed to connect to Wayland display.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const wl_registry_listener registry_listener = {GlobalRegistryHandler, GlobalRegistryRemover};
|
||||||
|
m_registry = wl_display_get_registry(m_display);
|
||||||
|
wl_registry_add_listener(m_registry, ®istry_listener, this);
|
||||||
|
|
||||||
|
// Call back to registry listener to get compositor/shell.
|
||||||
|
wl_display_dispatch_pending(m_display);
|
||||||
|
wl_display_roundtrip(m_display);
|
||||||
|
|
||||||
|
// We need a shell/compositor, or at least one we understand.
|
||||||
|
if (!m_compositor || !m_xdg_wm_base)
|
||||||
|
{
|
||||||
|
Panic("Missing Wayland shell/compositor\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const xdg_wm_base_listener xdg_wm_base_listener = {XDGWMBasePing};
|
||||||
|
xdg_wm_base_add_listener(m_xdg_wm_base, &xdg_wm_base_listener, this);
|
||||||
|
wl_display_dispatch_pending(m_display);
|
||||||
|
wl_display_roundtrip(m_display);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||||
|
{
|
||||||
|
const std::string title_copy(title);
|
||||||
|
const std::string message_copy(message);
|
||||||
|
Log_ErrorPrintf("%s: %s", title_copy.c_str(), message_copy.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::SetDefaultConfig(SettingsInterface& si) {}
|
||||||
|
|
||||||
|
bool WaylandNoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||||
|
{
|
||||||
|
s32 window_x, window_y, window_width, window_height;
|
||||||
|
bool has_window_pos = NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height);
|
||||||
|
if (!has_window_pos)
|
||||||
|
{
|
||||||
|
window_x = 0;
|
||||||
|
window_y = 0;
|
||||||
|
window_width = DEFAULT_WINDOW_WIDTH;
|
||||||
|
window_height = DEFAULT_WINDOW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the compositor and shell surface.
|
||||||
|
if (!(m_surface = wl_compositor_create_surface(m_compositor)) ||
|
||||||
|
!(m_xdg_surface = xdg_wm_base_get_xdg_surface(m_xdg_wm_base, m_surface)) ||
|
||||||
|
!(m_xdg_toplevel = xdg_surface_get_toplevel(m_xdg_surface)))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to create compositor/shell surfaces");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const xdg_surface_listener shell_surface_listener = {XDGSurfaceConfigure};
|
||||||
|
xdg_surface_add_listener(m_xdg_surface, &shell_surface_listener, this);
|
||||||
|
|
||||||
|
static const xdg_toplevel_listener toplevel_listener = {TopLevelConfigure, TopLevelClose};
|
||||||
|
xdg_toplevel_add_listener(m_xdg_toplevel, &toplevel_listener, this);
|
||||||
|
|
||||||
|
// Create region in the surface to draw into.
|
||||||
|
m_region = wl_compositor_create_region(m_compositor);
|
||||||
|
wl_region_add(m_region, 0, 0, window_width, window_height);
|
||||||
|
wl_surface_set_opaque_region(m_surface, m_region);
|
||||||
|
wl_surface_commit(m_surface);
|
||||||
|
|
||||||
|
// This doesn't seem to have any effect on kwin...
|
||||||
|
if (has_window_pos)
|
||||||
|
{
|
||||||
|
xdg_surface_set_window_geometry(m_xdg_surface, window_x, window_y, window_width, window_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_decoration_manager)
|
||||||
|
{
|
||||||
|
m_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(m_decoration_manager, m_xdg_toplevel);
|
||||||
|
if (m_toplevel_decoration)
|
||||||
|
zxdg_toplevel_decoration_v1_set_mode(m_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_window_info.surface_width = static_cast<u32>(window_width);
|
||||||
|
m_window_info.surface_height = static_cast<u32>(window_height);
|
||||||
|
m_window_info.surface_scale = 1.0f;
|
||||||
|
m_window_info.type = WindowInfo::Type::Wayland;
|
||||||
|
m_window_info.window_handle = m_surface;
|
||||||
|
m_window_info.display_connection = m_display;
|
||||||
|
|
||||||
|
wl_display_dispatch_pending(m_display);
|
||||||
|
wl_display_roundtrip(m_display);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::DestroyPlatformWindow()
|
||||||
|
{
|
||||||
|
m_window_info = {};
|
||||||
|
|
||||||
|
if (m_toplevel_decoration)
|
||||||
|
{
|
||||||
|
zxdg_toplevel_decoration_v1_destroy(m_toplevel_decoration);
|
||||||
|
m_toplevel_decoration = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_xdg_toplevel)
|
||||||
|
{
|
||||||
|
xdg_toplevel_destroy(m_xdg_toplevel);
|
||||||
|
m_xdg_toplevel = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_xdg_surface)
|
||||||
|
{
|
||||||
|
xdg_surface_destroy(m_xdg_surface);
|
||||||
|
m_xdg_surface = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_surface)
|
||||||
|
{
|
||||||
|
wl_surface_destroy(m_surface);
|
||||||
|
m_surface = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_display_dispatch_pending(m_display);
|
||||||
|
wl_display_roundtrip(m_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<WindowInfo> WaylandNoGUIPlatform::GetPlatformWindowInfo()
|
||||||
|
{
|
||||||
|
if (m_window_info.type == WindowInfo::Type::Wayland)
|
||||||
|
return m_window_info;
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||||
|
{
|
||||||
|
if (m_xdg_toplevel)
|
||||||
|
xdg_toplevel_set_title(m_xdg_toplevel, title.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void* WaylandNoGUIPlatform::GetPlatformWindowHandle()
|
||||||
|
{
|
||||||
|
return m_surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> WaylandNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_key_map_mutex);
|
||||||
|
for (const auto& it : m_key_map)
|
||||||
|
{
|
||||||
|
if (StringUtil::Strncasecmp(it.second.c_str(), str.data(), str.length()) == 0)
|
||||||
|
return it.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> WaylandNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_key_map_mutex);
|
||||||
|
const auto it = m_key_map.find(static_cast<s32>(code));
|
||||||
|
return (it != m_key_map.end()) ? std::optional<std::string>(it->second) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::GlobalRegistryHandler(void* data, wl_registry* registry, uint32_t id, const char* interface,
|
||||||
|
uint32_t version)
|
||||||
|
{
|
||||||
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
||||||
|
if (std::strcmp(interface, wl_compositor_interface.name) == 0)
|
||||||
|
{
|
||||||
|
platform->m_compositor =
|
||||||
|
static_cast<wl_compositor*>(wl_registry_bind(platform->m_registry, id, &wl_compositor_interface, 1));
|
||||||
|
}
|
||||||
|
else if (std::strcmp(interface, xdg_wm_base_interface.name) == 0)
|
||||||
|
{
|
||||||
|
platform->m_xdg_wm_base =
|
||||||
|
static_cast<xdg_wm_base*>(wl_registry_bind(platform->m_registry, id, &xdg_wm_base_interface, 1));
|
||||||
|
}
|
||||||
|
else if (std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0)
|
||||||
|
{
|
||||||
|
platform->m_decoration_manager = static_cast<zxdg_decoration_manager_v1*>(
|
||||||
|
wl_registry_bind(platform->m_registry, id, &zxdg_decoration_manager_v1_interface, 1));
|
||||||
|
}
|
||||||
|
else if (std::strcmp(interface, wl_seat_interface.name) == 0)
|
||||||
|
{
|
||||||
|
static const wl_seat_listener seat_listener = {&WaylandNoGUIPlatform::SeatCapabilities};
|
||||||
|
platform->m_wl_seat = static_cast<wl_seat*>(wl_registry_bind(registry, id, &wl_seat_interface, 1));
|
||||||
|
wl_seat_add_listener(platform->m_wl_seat, &seat_listener, platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::GlobalRegistryRemover(void* data, wl_registry* registry, uint32_t id) {}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::XDGWMBasePing(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
|
||||||
|
{
|
||||||
|
xdg_wm_base_pong(xdg_wm_base, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::XDGSurfaceConfigure(void* data, struct xdg_surface* xdg_surface, uint32_t serial)
|
||||||
|
{
|
||||||
|
xdg_surface_ack_configure(xdg_surface, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::TopLevelConfigure(void* data, struct xdg_toplevel* xdg_toplevel, int32_t width,
|
||||||
|
int32_t height, struct wl_array* states)
|
||||||
|
{
|
||||||
|
// If this is zero, it's asking us to set the size.
|
||||||
|
if (width == 0 || height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
||||||
|
platform->m_window_info.surface_width = width;
|
||||||
|
platform->m_window_info.surface_height = height;
|
||||||
|
|
||||||
|
NoGUIHost::ProcessPlatformWindowResize(width, height, platform->m_window_info.surface_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::TopLevelClose(void* data, struct xdg_toplevel* xdg_toplevel)
|
||||||
|
{
|
||||||
|
Host::RunOnCPUThread([]() { Host::RequestExit(g_settings.save_state_on_exit); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::SeatCapabilities(void* data, wl_seat* seat, uint32_t capabilities)
|
||||||
|
{
|
||||||
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
||||||
|
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD)
|
||||||
|
{
|
||||||
|
static const wl_keyboard_listener keyboard_listener = {
|
||||||
|
&WaylandNoGUIPlatform::KeyboardKeymap, &WaylandNoGUIPlatform::KeyboardEnter, &WaylandNoGUIPlatform::KeyboardLeave,
|
||||||
|
&WaylandNoGUIPlatform::KeyboardKey, &WaylandNoGUIPlatform::KeyboardModifiers};
|
||||||
|
platform->m_wl_keyboard = wl_seat_get_keyboard(seat);
|
||||||
|
wl_keyboard_add_listener(platform->m_wl_keyboard, &keyboard_listener, platform);
|
||||||
|
}
|
||||||
|
if (capabilities & WL_SEAT_CAPABILITY_POINTER)
|
||||||
|
{
|
||||||
|
static const wl_pointer_listener pointer_listener = {
|
||||||
|
&WaylandNoGUIPlatform::PointerEnter, &WaylandNoGUIPlatform::PointerLeave, &WaylandNoGUIPlatform::PointerMotion,
|
||||||
|
&WaylandNoGUIPlatform::PointerButton, &WaylandNoGUIPlatform::PointerAxis};
|
||||||
|
platform->m_wl_pointer = wl_seat_get_pointer(seat);
|
||||||
|
wl_pointer_add_listener(platform->m_wl_pointer, &pointer_listener, platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::KeyboardKeymap(void* data, wl_keyboard* keyboard, uint32_t format, int32_t fd, uint32_t size)
|
||||||
|
{
|
||||||
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
||||||
|
char* keymap_string = static_cast<char*>(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
|
||||||
|
if (platform->m_xkb_keymap)
|
||||||
|
xkb_keymap_unref(platform->m_xkb_keymap);
|
||||||
|
platform->m_xkb_keymap = xkb_keymap_new_from_string(platform->m_xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||||
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
munmap(keymap_string, size);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (platform->m_xkb_state)
|
||||||
|
xkb_state_unref(platform->m_xkb_state);
|
||||||
|
platform->m_xkb_state = xkb_state_new(platform->m_xkb_keymap);
|
||||||
|
|
||||||
|
platform->InitializeKeyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::InitializeKeyMap()
|
||||||
|
{
|
||||||
|
m_key_map.clear();
|
||||||
|
Log_VerbosePrintf("Init keymap");
|
||||||
|
|
||||||
|
const xkb_keycode_t min_keycode = xkb_keymap_min_keycode(m_xkb_keymap);
|
||||||
|
const xkb_keycode_t max_keycode = xkb_keymap_max_keycode(m_xkb_keymap);
|
||||||
|
DebugAssert(max_keycode >= min_keycode);
|
||||||
|
|
||||||
|
for (xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; keycode++)
|
||||||
|
{
|
||||||
|
const xkb_layout_index_t num_layouts = xkb_keymap_num_layouts_for_key(m_xkb_keymap, keycode);
|
||||||
|
if (num_layouts == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Take the first layout which we find a valid keysym for.
|
||||||
|
bool found_keysym = false;
|
||||||
|
for (xkb_layout_index_t layout = 0; layout < num_layouts && !found_keysym; layout++)
|
||||||
|
{
|
||||||
|
const xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(m_xkb_keymap, keycode, layout);
|
||||||
|
if (num_levels == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Take the first level which we find a valid keysym for.
|
||||||
|
for (xkb_level_index_t level = 0; level < num_levels; level++)
|
||||||
|
{
|
||||||
|
const xkb_keysym_t* keysyms;
|
||||||
|
const int num_syms = xkb_keymap_key_get_syms_by_level(m_xkb_keymap, keycode, layout, level, &keysyms);
|
||||||
|
if (num_syms == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Just take the first. Should only be one in most cases anyway.
|
||||||
|
const xkb_keysym_t keysym = xkb_keysym_to_upper(keysyms[0]);
|
||||||
|
|
||||||
|
char keysym_name_buf[64];
|
||||||
|
if (xkb_keysym_get_name(keysym, keysym_name_buf, sizeof(keysym_name_buf)) <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_key_map.emplace(static_cast<s32>(keycode), keysym_name_buf);
|
||||||
|
found_keysym = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::KeyboardEnter(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface,
|
||||||
|
wl_array* keys)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::KeyboardLeave(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) {}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::KeyboardKey(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key,
|
||||||
|
uint32_t state)
|
||||||
|
{
|
||||||
|
const xkb_keycode_t keycode = static_cast<xkb_keycode_t>(key + 8);
|
||||||
|
const bool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED);
|
||||||
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(keycode), pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::KeyboardModifiers(void* data, wl_keyboard* keyboard, uint32_t serial,
|
||||||
|
uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked,
|
||||||
|
uint32_t group)
|
||||||
|
{
|
||||||
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
||||||
|
xkb_state_update_mask(platform->m_xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::PointerEnter(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface,
|
||||||
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::PointerLeave(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface) {}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::PointerMotion(void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y)
|
||||||
|
{
|
||||||
|
const float pos_x = static_cast<float>(wl_fixed_to_double(x));
|
||||||
|
const float pos_y = static_cast<float>(wl_fixed_to_double(y));
|
||||||
|
NoGUIHost::ProcessPlatformMouseMoveEvent(static_cast<int>(pos_x), static_cast<int>(pos_y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::PointerButton(void* data, wl_pointer* pointer, uint32_t serial, uint32_t time,
|
||||||
|
uint32_t button, uint32_t state)
|
||||||
|
{
|
||||||
|
if (button < BTN_MOUSE || (button - BTN_MOUSE) >= 32)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const s32 button_index = (button - BTN_MOUSE);
|
||||||
|
const bool button_pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED);
|
||||||
|
NoGUIHost::ProcessPlatformMouseButtonEvent(button_index, button_pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::PointerAxis(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value)
|
||||||
|
{
|
||||||
|
const float x = (axis == 1) ? std::clamp(static_cast<float>(wl_fixed_to_double(value)), -1.0f, 1.0f) : 0.0f;
|
||||||
|
const float y = (axis == 0) ? std::clamp(static_cast<float>(-wl_fixed_to_double(value)), -1.0f, 1.0f) : 0.0f;
|
||||||
|
NoGUIHost::ProcessPlatformMouseWheelEvent(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::RunMessageLoop()
|
||||||
|
{
|
||||||
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
wl_display_dispatch_pending(m_display);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
while (!m_callback_queue.empty())
|
||||||
|
{
|
||||||
|
std::function<void()> func = std::move(m_callback_queue.front());
|
||||||
|
m_callback_queue.pop_front();
|
||||||
|
lock.unlock();
|
||||||
|
func();
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this suck less.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
m_callback_queue.push_back(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::QuitMessageLoop()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandNoGUIPlatform::SetFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
// how the heck can we do this?
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaylandNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateWaylandPlatform()
|
||||||
|
{
|
||||||
|
std::unique_ptr<WaylandNoGUIPlatform> ret = std::unique_ptr<WaylandNoGUIPlatform>(new WaylandNoGUIPlatform());
|
||||||
|
if (!ret->Initialize())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
98
src/duckstation-nogui/wayland_nogui_platform.h
Normal file
98
src/duckstation-nogui/wayland_nogui_platform.h
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
|
||||||
|
#include "wayland-xdg-decoration-client-protocol.h"
|
||||||
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
|
#include <wayland-client-protocol.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
class WaylandNoGUIPlatform : public NoGUIPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaylandNoGUIPlatform();
|
||||||
|
~WaylandNoGUIPlatform();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void ReportError(const std::string_view& title, const std::string_view& message) override;
|
||||||
|
|
||||||
|
void SetDefaultConfig(SettingsInterface& si) override;
|
||||||
|
|
||||||
|
bool CreatePlatformWindow(std::string title) override;
|
||||||
|
void DestroyPlatformWindow() override;
|
||||||
|
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||||
|
void SetPlatformWindowTitle(std::string title) override;
|
||||||
|
void* GetPlatformWindowHandle() override;
|
||||||
|
|
||||||
|
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) override;
|
||||||
|
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) override;
|
||||||
|
|
||||||
|
void RunMessageLoop() override;
|
||||||
|
void ExecuteInMessageLoop(std::function<void()> func) override;
|
||||||
|
void QuitMessageLoop() override;
|
||||||
|
|
||||||
|
void SetFullscreen(bool enabled) override;
|
||||||
|
|
||||||
|
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitializeKeyMap();
|
||||||
|
|
||||||
|
static void GlobalRegistryHandler(void* data, wl_registry* registry, uint32_t id, const char* interface,
|
||||||
|
uint32_t version);
|
||||||
|
static void GlobalRegistryRemover(void* data, wl_registry* registry, uint32_t id);
|
||||||
|
static void XDGWMBasePing(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial);
|
||||||
|
static void XDGSurfaceConfigure(void* data, struct xdg_surface* xdg_surface, uint32_t serial);
|
||||||
|
static void TopLevelConfigure(void* data, struct xdg_toplevel* xdg_toplevel, int32_t width, int32_t height,
|
||||||
|
struct wl_array* states);
|
||||||
|
static void PointerEnter(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x,
|
||||||
|
wl_fixed_t surface_y);
|
||||||
|
static void PointerLeave(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface);
|
||||||
|
static void PointerMotion(void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y);
|
||||||
|
static void PointerButton(void* data, wl_pointer* pointer, uint32_t serial, uint32_t time, uint32_t button,
|
||||||
|
uint32_t state);
|
||||||
|
static void PointerAxis(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
|
||||||
|
static void KeyboardKeymap(void* data, wl_keyboard* keyboard, uint32_t format, int32_t fd, uint32_t size);
|
||||||
|
static void KeyboardEnter(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys);
|
||||||
|
static void KeyboardLeave(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface);
|
||||||
|
static void KeyboardKey(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key,
|
||||||
|
uint32_t state);
|
||||||
|
static void KeyboardModifiers(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t mods_depressed,
|
||||||
|
uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
|
||||||
|
static void SeatCapabilities(void* data, wl_seat* seat, uint32_t capabilities);
|
||||||
|
static void TopLevelClose(void* data, struct xdg_toplevel* xdg_toplevel);
|
||||||
|
|
||||||
|
std::atomic_bool m_message_loop_running{false};
|
||||||
|
// std::atomic_bool m_fullscreen{false};
|
||||||
|
|
||||||
|
WindowInfo m_window_info = {};
|
||||||
|
|
||||||
|
wl_display* m_display = nullptr;
|
||||||
|
wl_registry* m_registry = nullptr;
|
||||||
|
wl_compositor* m_compositor = nullptr;
|
||||||
|
xdg_wm_base* m_xdg_wm_base = nullptr;
|
||||||
|
wl_surface* m_surface = nullptr;
|
||||||
|
wl_region* m_region = nullptr;
|
||||||
|
xdg_surface* m_xdg_surface = nullptr;
|
||||||
|
xdg_toplevel* m_xdg_toplevel = nullptr;
|
||||||
|
zxdg_decoration_manager_v1* m_decoration_manager = nullptr;
|
||||||
|
zxdg_toplevel_decoration_v1* m_toplevel_decoration = nullptr;
|
||||||
|
wl_seat* m_wl_seat = nullptr;
|
||||||
|
wl_keyboard* m_wl_keyboard = nullptr;
|
||||||
|
wl_pointer* m_wl_pointer = nullptr;
|
||||||
|
xkb_context* m_xkb_context = nullptr;
|
||||||
|
xkb_keymap* m_xkb_keymap = nullptr;
|
||||||
|
xkb_state* m_xkb_state = nullptr;
|
||||||
|
|
||||||
|
std::unordered_map<s32, std::string> m_key_map;
|
||||||
|
std::mutex m_key_map_mutex;
|
||||||
|
|
||||||
|
std::deque<std::function<void()>> m_callback_queue;
|
||||||
|
std::mutex m_callback_queue_mutex;
|
||||||
|
};
|
|
@ -1,152 +0,0 @@
|
||||||
#include "win32_host_interface.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "resource.h"
|
|
||||||
#include <tchar.h>
|
|
||||||
Log_SetChannel(Win32HostInterface);
|
|
||||||
|
|
||||||
static constexpr LPCWSTR WINDOW_CLASS_NAME = L"DuckStationNoGUI";
|
|
||||||
|
|
||||||
Win32HostInterface::Win32HostInterface() = default;
|
|
||||||
|
|
||||||
Win32HostInterface::~Win32HostInterface() = default;
|
|
||||||
|
|
||||||
std::unique_ptr<NoGUIHostInterface> Win32HostInterface::Create()
|
|
||||||
{
|
|
||||||
return std::make_unique<Win32HostInterface>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Win32HostInterface::Initialize()
|
|
||||||
{
|
|
||||||
if (!RegisterWindowClass())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!NoGUIHostInterface::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32HostInterface::Shutdown()
|
|
||||||
{
|
|
||||||
NoGUIHostInterface::Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Win32HostInterface::RegisterWindowClass()
|
|
||||||
{
|
|
||||||
WNDCLASSEXW wc = {};
|
|
||||||
wc.cbSize = sizeof(WNDCLASSEXW);
|
|
||||||
wc.style = 0;
|
|
||||||
wc.lpfnWndProc = WndProc;
|
|
||||||
wc.cbClsExtra = 0;
|
|
||||||
wc.cbWndExtra = 0;
|
|
||||||
wc.hInstance = GetModuleHandle(nullptr);
|
|
||||||
wc.hIcon = LoadIconA(NULL, (LPCSTR)IDI_ICON1);
|
|
||||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
||||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
||||||
wc.lpszMenuName = NULL;
|
|
||||||
wc.lpszClassName = WINDOW_CLASS_NAME;
|
|
||||||
wc.hIconSm = LoadIconA(NULL, (LPCSTR)IDI_ICON1);
|
|
||||||
|
|
||||||
if (!RegisterClassExW(&wc))
|
|
||||||
{
|
|
||||||
MessageBoxA(nullptr, "Window registration failed.", "Error", MB_ICONERROR | MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32HostInterface::SetMouseMode(bool relative, bool hide_cursor) {}
|
|
||||||
|
|
||||||
bool Win32HostInterface::CreatePlatformWindow()
|
|
||||||
{
|
|
||||||
m_hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, L"DuckStation", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
|
|
||||||
CW_USEDEFAULT, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, nullptr, nullptr,
|
|
||||||
GetModuleHandleA(nullptr), this);
|
|
||||||
if (!m_hwnd)
|
|
||||||
{
|
|
||||||
MessageBoxA(nullptr, "CreateWindowEx failed.", "Error", MB_ICONERROR | MB_OK);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowWindow(m_hwnd, SW_SHOW);
|
|
||||||
UpdateWindow(m_hwnd);
|
|
||||||
ProcessWin32Events();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32HostInterface::DestroyPlatformWindow()
|
|
||||||
{
|
|
||||||
if (m_hwnd)
|
|
||||||
{
|
|
||||||
DestroyWindow(m_hwnd);
|
|
||||||
m_hwnd = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> Win32HostInterface::GetPlatformWindowInfo()
|
|
||||||
{
|
|
||||||
RECT rc = {};
|
|
||||||
GetClientRect(m_hwnd, &rc);
|
|
||||||
|
|
||||||
WindowInfo wi;
|
|
||||||
wi.window_handle = static_cast<void*>(m_hwnd);
|
|
||||||
wi.type = WindowInfo::Type::Win32;
|
|
||||||
wi.surface_width = static_cast<u32>(rc.right - rc.left);
|
|
||||||
wi.surface_height = static_cast<u32>(rc.bottom - rc.top);
|
|
||||||
// wi.surface_format = WindowInfo::SurfaceFormat::Auto;
|
|
||||||
return wi;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32HostInterface::PollAndUpdate()
|
|
||||||
{
|
|
||||||
ProcessWin32Events();
|
|
||||||
|
|
||||||
NoGUIHostInterface::PollAndUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Win32HostInterface::ProcessWin32Events()
|
|
||||||
{
|
|
||||||
MSG msg;
|
|
||||||
while (PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE))
|
|
||||||
{
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT Win32HostInterface::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
||||||
{
|
|
||||||
Win32HostInterface* hi = static_cast<Win32HostInterface*>(g_host_interface);
|
|
||||||
switch (msg)
|
|
||||||
{
|
|
||||||
case WM_SIZE:
|
|
||||||
{
|
|
||||||
const u32 width = LOWORD(lParam);
|
|
||||||
const u32 height = HIWORD(lParam);
|
|
||||||
if (hi->m_display)
|
|
||||||
hi->m_display->ResizeRenderWindow(static_cast<s32>(width), static_cast<s32>(height));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WM_CLOSE:
|
|
||||||
hi->m_quit_request = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Win32HostInterface::HostKeyCode> Win32HostInterface::GetHostKeyCode(const std::string_view key_code) const
|
|
||||||
{
|
|
||||||
std::optional<int> kc; // = EvDevKeyNames::GetKeyCodeForName(key_code);
|
|
||||||
if (!kc.has_value())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
return static_cast<HostKeyCode>(kc.value());
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/windows_headers.h"
|
|
||||||
#include "nogui_host_interface.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class Win32HostInterface final : public NoGUIHostInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Win32HostInterface();
|
|
||||||
~Win32HostInterface();
|
|
||||||
|
|
||||||
bool Initialize();
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
static std::unique_ptr<NoGUIHostInterface> Create();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void SetMouseMode(bool relative, bool hide_cursor) override;
|
|
||||||
|
|
||||||
bool CreatePlatformWindow() override;
|
|
||||||
void DestroyPlatformWindow() override;
|
|
||||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
|
||||||
|
|
||||||
std::optional<HostKeyCode> GetHostKeyCode(const std::string_view key_code) const override;
|
|
||||||
|
|
||||||
void PollAndUpdate() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
||||||
|
|
||||||
bool RegisterWindowClass();
|
|
||||||
void ProcessWin32Events();
|
|
||||||
|
|
||||||
HWND m_hwnd{};
|
|
||||||
};
|
|
164
src/duckstation-nogui/win32_key_names.h
Normal file
164
src/duckstation-nogui/win32_key_names.h
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "common/windows_headers.h"
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace Win32KeyNames {
|
||||||
|
static const std::map<int, const char*> s_win32_key_names = {
|
||||||
|
{VK_RETURN, "Return"},
|
||||||
|
{VK_ESCAPE, "Escape"},
|
||||||
|
{VK_BACK, "Backspace"},
|
||||||
|
{VK_TAB, "Tab"},
|
||||||
|
{VK_SPACE, "Space"},
|
||||||
|
{0xDE, "Apostrophe"},
|
||||||
|
{0xBC, "Comma"},
|
||||||
|
{0xBD, "Minus"},
|
||||||
|
{0xBE, "Period"},
|
||||||
|
{0xBF, "Slash"},
|
||||||
|
{'0', "0"},
|
||||||
|
{'1', "1"},
|
||||||
|
{'2', "2"},
|
||||||
|
{'3', "3"},
|
||||||
|
{'4', "4"},
|
||||||
|
{'5', "5"},
|
||||||
|
{'6', "6"},
|
||||||
|
{'7', "7"},
|
||||||
|
{'8', "8"},
|
||||||
|
{'9', "9"},
|
||||||
|
{0xBA, "Semcolon"},
|
||||||
|
{0xBB, "Equal"},
|
||||||
|
{0xDB, "BracketLeft"},
|
||||||
|
{0xDC, "Backslash"},
|
||||||
|
{0xDD, "BracketRight"},
|
||||||
|
{0xC0, "QuoteLeft"},
|
||||||
|
{'A', "A"},
|
||||||
|
{'B', "B"},
|
||||||
|
{'C', "C"},
|
||||||
|
{'D', "D"},
|
||||||
|
{'E', "E"},
|
||||||
|
{'F', "F"},
|
||||||
|
{'G', "G"},
|
||||||
|
{'H', "H"},
|
||||||
|
{'I', "I"},
|
||||||
|
{'J', "J"},
|
||||||
|
{'K', "K"},
|
||||||
|
{'L', "L"},
|
||||||
|
{'M', "M"},
|
||||||
|
{'N', "N"},
|
||||||
|
{'O', "O"},
|
||||||
|
{'P', "P"},
|
||||||
|
{'Q', "Q"},
|
||||||
|
{'R', "R"},
|
||||||
|
{'S', "S"},
|
||||||
|
{'T', "T"},
|
||||||
|
{'U', "U"},
|
||||||
|
{'V', "V"},
|
||||||
|
{'W', "W"},
|
||||||
|
{'X', "X"},
|
||||||
|
{'Y', "Y"},
|
||||||
|
{'Z', "Z"},
|
||||||
|
{VK_CAPITAL, "CapsLock"},
|
||||||
|
{VK_F1, "F1"},
|
||||||
|
{VK_F2, "F2"},
|
||||||
|
{VK_F3, "F3"},
|
||||||
|
{VK_F4, "F4"},
|
||||||
|
{VK_F5, "F5"},
|
||||||
|
{VK_F6, "F6"},
|
||||||
|
{VK_F7, "F7"},
|
||||||
|
{VK_F8, "F8"},
|
||||||
|
{VK_F9, "F9"},
|
||||||
|
{VK_F10, "F10"},
|
||||||
|
{VK_F11, "F11"},
|
||||||
|
{VK_F12, "F12"},
|
||||||
|
{VK_PRINT, "Print"},
|
||||||
|
{VK_SCROLL, "ScrollLock"},
|
||||||
|
{VK_PAUSE, "Pause"},
|
||||||
|
{VK_INSERT, "Insert"},
|
||||||
|
{VK_HOME, "Home"},
|
||||||
|
{VK_PRIOR, "PageUp"},
|
||||||
|
{VK_DELETE, "Delete"},
|
||||||
|
{VK_END, "End"},
|
||||||
|
{VK_NEXT, "PageDown"},
|
||||||
|
{VK_RIGHT, "Right"},
|
||||||
|
{VK_LEFT, "Left"},
|
||||||
|
{VK_DOWN, "Down"},
|
||||||
|
{VK_UP, "Up"},
|
||||||
|
{VK_NUMLOCK, "NumLock"},
|
||||||
|
{VK_DIVIDE, "KeypadDivide"},
|
||||||
|
{VK_MULTIPLY, "KeypadMultiply"},
|
||||||
|
{VK_SUBTRACT, "KeypadMinus"},
|
||||||
|
{VK_ADD, "KeypadPlus"},
|
||||||
|
//{VK_KP_ENTER, "KeypadReturn"},
|
||||||
|
{VK_NUMPAD1, "Keypad1"},
|
||||||
|
{VK_NUMPAD2, "Keypad2"},
|
||||||
|
{VK_NUMPAD3, "Keypad3"},
|
||||||
|
{VK_NUMPAD4, "Keypad4"},
|
||||||
|
{VK_NUMPAD5, "Keypad5"},
|
||||||
|
{VK_NUMPAD6, "Keypad6"},
|
||||||
|
{VK_NUMPAD7, "Keypad7"},
|
||||||
|
{VK_NUMPAD8, "Keypad8"},
|
||||||
|
{VK_NUMPAD9, "Keypad9"},
|
||||||
|
{VK_NUMPAD0, "Keypad0"},
|
||||||
|
{VK_SEPARATOR, "KeypadPeriod"},
|
||||||
|
{VK_F13, "F13"},
|
||||||
|
{VK_F14, "F14"},
|
||||||
|
{VK_F15, "F15"},
|
||||||
|
{VK_F16, "F16"},
|
||||||
|
{VK_F17, "F17"},
|
||||||
|
{VK_F18, "F18"},
|
||||||
|
{VK_F19, "F19"},
|
||||||
|
{VK_F20, "F20"},
|
||||||
|
{VK_F21, "F21"},
|
||||||
|
{VK_F22, "F22"},
|
||||||
|
{VK_F23, "F23"},
|
||||||
|
{VK_F24, "F24"},
|
||||||
|
{VK_EXECUTE, "Execute"},
|
||||||
|
{VK_HELP, "Help"},
|
||||||
|
{VK_MENU, "Menu"},
|
||||||
|
{VK_SELECT, "Select"},
|
||||||
|
{VK_MEDIA_STOP, "Stop"},
|
||||||
|
{VK_VOLUME_UP, "VolumeUp"},
|
||||||
|
{VK_VOLUME_DOWN, "VolumeDown"},
|
||||||
|
{VK_CANCEL, "Cancel"},
|
||||||
|
{VK_CLEAR, "Clear"},
|
||||||
|
{VK_PRIOR, "Prior"},
|
||||||
|
{VK_SEPARATOR, "Separator"},
|
||||||
|
{VK_CRSEL, "CrSel"},
|
||||||
|
{VK_EXSEL, "ExSel"},
|
||||||
|
{VK_LCONTROL, "LeftControl"},
|
||||||
|
{VK_LSHIFT, "LeftShift"},
|
||||||
|
{VK_LMENU, "LeftAlt"},
|
||||||
|
{VK_LWIN, "Super_L"},
|
||||||
|
{VK_RCONTROL, "RightCtrl"},
|
||||||
|
{VK_RSHIFT, "RightShift"},
|
||||||
|
{VK_RMENU, "RightAlt"},
|
||||||
|
{VK_RWIN, "RightSuper"},
|
||||||
|
{VK_MEDIA_NEXT_TRACK, "MediaNext"},
|
||||||
|
{VK_MEDIA_PREV_TRACK, "MediaPrevious"},
|
||||||
|
{VK_MEDIA_STOP, "MediaStop"},
|
||||||
|
{VK_MEDIA_PLAY_PAUSE, "MediaPlay"},
|
||||||
|
{VK_VOLUME_MUTE, "VolumeMute"},
|
||||||
|
{VK_SLEEP, "Sleep"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* GetKeyName(DWORD key)
|
||||||
|
{
|
||||||
|
const auto it = s_win32_key_names.find(key);
|
||||||
|
return it == s_win32_key_names.end() ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<DWORD> GetKeyCodeForName(const std::string_view& key_name)
|
||||||
|
{
|
||||||
|
for (const auto& it : s_win32_key_names)
|
||||||
|
{
|
||||||
|
if (key_name == it.second)
|
||||||
|
return it.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} // namespace Win32KeyNames
|
365
src/duckstation-nogui/win32_nogui_platform.cpp
Normal file
365
src/duckstation-nogui/win32_nogui_platform.cpp
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
#include "win32_nogui_platform.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/threading.h"
|
||||||
|
#include "core/host.h"
|
||||||
|
#include "core/host_settings.h"
|
||||||
|
#include "nogui_host.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include "win32_key_names.h"
|
||||||
|
#include <tchar.h>
|
||||||
|
Log_SetChannel(Win32HostInterface);
|
||||||
|
|
||||||
|
static constexpr LPCWSTR WINDOW_CLASS_NAME = L"DuckStationNoGUI";
|
||||||
|
static constexpr DWORD WINDOWED_STYLE = WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX;
|
||||||
|
static constexpr DWORD WINDOWED_EXSTYLE = WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE;
|
||||||
|
static constexpr DWORD FULLSCREEN_STYLE = WS_POPUP | WS_MINIMIZEBOX;
|
||||||
|
|
||||||
|
static float GetWindowScale(HWND hwnd)
|
||||||
|
{
|
||||||
|
static UINT(WINAPI * get_dpi_for_window)(HWND hwnd);
|
||||||
|
if (!get_dpi_for_window)
|
||||||
|
{
|
||||||
|
HMODULE mod = GetModuleHandleW(L"user32.dll");
|
||||||
|
if (mod)
|
||||||
|
get_dpi_for_window = reinterpret_cast<decltype(get_dpi_for_window)>(GetProcAddress(mod, "GetDpiForWindow"));
|
||||||
|
}
|
||||||
|
if (!get_dpi_for_window)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
// less than 100% scaling seems unlikely.
|
||||||
|
const UINT dpi = hwnd ? get_dpi_for_window(hwnd) : 96;
|
||||||
|
return (dpi > 0) ? std::max(1.0f, static_cast<float>(dpi) / 96.0f) : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32NoGUIPlatform::Win32NoGUIPlatform()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32NoGUIPlatform::~Win32NoGUIPlatform()
|
||||||
|
{
|
||||||
|
UnregisterClassW(WINDOW_CLASS_NAME, GetModuleHandle(nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32NoGUIPlatform::Initialize()
|
||||||
|
{
|
||||||
|
WNDCLASSEXW wc = {};
|
||||||
|
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||||
|
wc.style = 0;
|
||||||
|
wc.lpfnWndProc = WndProc;
|
||||||
|
wc.cbClsExtra = 0;
|
||||||
|
wc.cbWndExtra = 0;
|
||||||
|
wc.hInstance = GetModuleHandle(nullptr);
|
||||||
|
wc.hIcon = LoadIconA(wc.hInstance, (LPCSTR)IDI_ICON1);
|
||||||
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||||
|
wc.lpszMenuName = NULL;
|
||||||
|
wc.lpszClassName = WINDOW_CLASS_NAME;
|
||||||
|
wc.hIconSm = LoadIconA(wc.hInstance, (LPCSTR)IDI_ICON1);
|
||||||
|
|
||||||
|
if (!RegisterClassExW(&wc))
|
||||||
|
{
|
||||||
|
MessageBoxW(nullptr, L"Window registration failed.", L"Error", MB_ICONERROR | MB_OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_window_thread_id = GetCurrentThreadId();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||||
|
{
|
||||||
|
const std::wstring title_copy(StringUtil::UTF8StringToWideString(title));
|
||||||
|
const std::wstring message_copy(StringUtil::UTF8StringToWideString(message));
|
||||||
|
|
||||||
|
MessageBoxW(m_hwnd, message_copy.c_str(), title_copy.c_str(), MB_ICONERROR | MB_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||||
|
{
|
||||||
|
s32 window_x, window_y, window_width, window_height;
|
||||||
|
if (!NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height))
|
||||||
|
{
|
||||||
|
window_x = CW_USEDEFAULT;
|
||||||
|
window_y = CW_USEDEFAULT;
|
||||||
|
window_width = DEFAULT_WINDOW_WIDTH;
|
||||||
|
window_height = DEFAULT_WINDOW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, StringUtil::UTF8StringToWideString(title).c_str(),
|
||||||
|
WINDOWED_STYLE, window_x, window_y, window_width, window_height, nullptr, nullptr,
|
||||||
|
GetModuleHandleW(nullptr), this);
|
||||||
|
if (!hwnd)
|
||||||
|
{
|
||||||
|
MessageBoxW(nullptr, L"CreateWindowEx failed.", L"Error", MB_ICONERROR | MB_OK);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliberately not stored to m_hwnd yet, because otherwise the msg handlers will run
|
||||||
|
ShowWindow(hwnd, SW_SHOW);
|
||||||
|
UpdateWindow(hwnd);
|
||||||
|
m_hwnd = hwnd;
|
||||||
|
m_window_scale = GetWindowScale(m_hwnd);
|
||||||
|
m_last_mouse_buttons = 0;
|
||||||
|
|
||||||
|
if (m_fullscreen.load(std::memory_order_acquire))
|
||||||
|
SetFullscreen(true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::DestroyPlatformWindow()
|
||||||
|
{
|
||||||
|
if (!m_hwnd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RECT rc;
|
||||||
|
if (!m_fullscreen.load(std::memory_order_acquire) && GetWindowRect(m_hwnd, &rc))
|
||||||
|
{
|
||||||
|
NoGUIHost::SavePlatformWindowGeometry(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyWindow(m_hwnd);
|
||||||
|
m_hwnd = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<WindowInfo> Win32NoGUIPlatform::GetPlatformWindowInfo()
|
||||||
|
{
|
||||||
|
if (!m_hwnd)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
RECT rc = {};
|
||||||
|
GetWindowRect(m_hwnd, &rc);
|
||||||
|
|
||||||
|
WindowInfo wi;
|
||||||
|
wi.surface_width = static_cast<u32>(rc.right - rc.left);
|
||||||
|
wi.surface_height = static_cast<u32>(rc.bottom - rc.top);
|
||||||
|
wi.surface_scale = m_window_scale;
|
||||||
|
wi.type = WindowInfo::Type::Win32;
|
||||||
|
wi.window_handle = m_hwnd;
|
||||||
|
return wi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||||
|
{
|
||||||
|
if (!m_hwnd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetWindowTextW(m_hwnd, StringUtil::UTF8StringToWideString(title).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Win32NoGUIPlatform::GetPlatformWindowHandle()
|
||||||
|
{
|
||||||
|
return m_hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> Win32NoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||||
|
{
|
||||||
|
std::optional<DWORD> converted(Win32KeyNames::GetKeyCodeForName(str));
|
||||||
|
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> Win32NoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||||
|
{
|
||||||
|
const char* converted = Win32KeyNames::GetKeyName(code);
|
||||||
|
return converted ? std::optional<std::string>(converted) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::RunMessageLoop()
|
||||||
|
{
|
||||||
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
MSG msg;
|
||||||
|
if (GetMessageW(&msg, NULL, 0, 0))
|
||||||
|
{
|
||||||
|
// handle self messages (when we don't have a window yet)
|
||||||
|
if (msg.hwnd == NULL && msg.message >= WM_FIRST && msg.message <= WM_LAST)
|
||||||
|
{
|
||||||
|
WndProc(NULL, msg.message, msg.wParam, msg.lParam);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||||
|
{
|
||||||
|
std::function<void()>* pfunc = new std::function<void()>(std::move(func));
|
||||||
|
if (m_hwnd)
|
||||||
|
PostMessageW(m_hwnd, WM_FUNC, 0, reinterpret_cast<LPARAM>(pfunc));
|
||||||
|
else
|
||||||
|
PostThreadMessageW(m_window_thread_id, WM_FUNC, 0, reinterpret_cast<LPARAM>(pfunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::QuitMessageLoop()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(false, std::memory_order_release);
|
||||||
|
PostThreadMessageW(m_window_thread_id, WM_WAKEUP, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Win32NoGUIPlatform::SetFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
if (!m_hwnd || m_fullscreen.load(std::memory_order_acquire) == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LONG style = GetWindowLong(m_hwnd, GWL_STYLE);
|
||||||
|
LONG exstyle = GetWindowLong(m_hwnd, GWL_EXSTYLE);
|
||||||
|
RECT rc;
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if (!monitor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MONITORINFO mi = {sizeof(MONITORINFO)};
|
||||||
|
if (!GetMonitorInfo(monitor, &mi) || !GetWindowRect(m_hwnd, &m_windowed_rect))
|
||||||
|
return;
|
||||||
|
|
||||||
|
style = (style & ~WINDOWED_STYLE) | FULLSCREEN_STYLE;
|
||||||
|
exstyle = (style & ~WINDOWED_EXSTYLE);
|
||||||
|
rc = mi.rcMonitor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
style = (style & ~FULLSCREEN_STYLE) | WINDOWED_STYLE;
|
||||||
|
exstyle = exstyle | WINDOWED_EXSTYLE;
|
||||||
|
rc = m_windowed_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowLongPtrW(m_hwnd, GWL_STYLE, style);
|
||||||
|
SetWindowLongPtrW(m_hwnd, GWL_EXSTYLE, exstyle);
|
||||||
|
SetWindowPos(m_hwnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW);
|
||||||
|
|
||||||
|
m_fullscreen.store(enabled, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Win32NoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||||
|
{
|
||||||
|
RECT rc;
|
||||||
|
if (!m_hwnd || m_fullscreen.load(std::memory_order_acquire) || !GetWindowRect(m_hwnd, &rc))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetWindowPos(m_hwnd, NULL, rc.left, rc.top, new_window_width, new_window_height, SWP_SHOWWINDOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK Win32NoGUIPlatform::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
Win32NoGUIPlatform* platform = static_cast<Win32NoGUIPlatform*>(g_nogui_window.get());
|
||||||
|
if (hwnd != platform->m_hwnd && msg != WM_FUNC)
|
||||||
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||||
|
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WM_SIZE:
|
||||||
|
{
|
||||||
|
const u32 width = LOWORD(lParam);
|
||||||
|
const u32 height = HIWORD(lParam);
|
||||||
|
NoGUIHost::ProcessPlatformWindowResize(width, height, platform->m_window_scale);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
case WM_KEYUP:
|
||||||
|
{
|
||||||
|
const bool pressed = (msg == WM_KEYDOWN);
|
||||||
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(wParam), pressed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_MOUSEMOVE:
|
||||||
|
{
|
||||||
|
const float x = static_cast<float>(static_cast<s16>(LOWORD(lParam)));
|
||||||
|
const float y = static_cast<float>(static_cast<s16>(HIWORD(lParam)));
|
||||||
|
NoGUIHost::ProcessPlatformMouseMoveEvent(x, y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_MBUTTONDOWN:
|
||||||
|
case WM_MBUTTONUP:
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
case WM_XBUTTONDOWN:
|
||||||
|
case WM_XBUTTONUP:
|
||||||
|
{
|
||||||
|
const DWORD buttons = static_cast<DWORD>(wParam);
|
||||||
|
const DWORD changed = platform->m_last_mouse_buttons ^ buttons;
|
||||||
|
platform->m_last_mouse_buttons = buttons;
|
||||||
|
|
||||||
|
static constexpr DWORD masks[] = {MK_LBUTTON, MK_RBUTTON, MK_MBUTTON, MK_XBUTTON1, MK_XBUTTON2};
|
||||||
|
for (u32 i = 0; i < std::size(masks); i++)
|
||||||
|
{
|
||||||
|
if (changed & masks[i])
|
||||||
|
NoGUIHost::ProcessPlatformMouseButtonEvent(i, (buttons & masks[i]) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_MOUSEWHEEL:
|
||||||
|
case WM_MOUSEHWHEEL:
|
||||||
|
{
|
||||||
|
const float d =
|
||||||
|
std::clamp(static_cast<float>(static_cast<s16>(HIWORD(wParam))) / static_cast<float>(WHEEL_DELTA), -1.0f, 1.0f);
|
||||||
|
NoGUIHost::ProcessPlatformMouseWheelEvent((msg == WM_MOUSEHWHEEL) ? d : 0.0f, (msg == WM_MOUSEWHEEL) ? d : 0.0f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_ACTIVATEAPP:
|
||||||
|
{
|
||||||
|
if (wParam)
|
||||||
|
NoGUIHost::PlatformWindowFocusGained();
|
||||||
|
else
|
||||||
|
NoGUIHost::PlatformWindowFocusLost();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_CLOSE:
|
||||||
|
case WM_QUIT:
|
||||||
|
{
|
||||||
|
Host::RunOnCPUThread([]() { Host::RequestExit(g_settings.save_state_on_exit); });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_FUNC:
|
||||||
|
{
|
||||||
|
std::function<void()>* pfunc = reinterpret_cast<std::function<void()>*>(lParam);
|
||||||
|
if (pfunc)
|
||||||
|
{
|
||||||
|
(*pfunc)();
|
||||||
|
delete pfunc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_WAKEUP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateWin32Platform()
|
||||||
|
{
|
||||||
|
std::unique_ptr<Win32NoGUIPlatform> ret(new Win32NoGUIPlatform());
|
||||||
|
if (!ret->Initialize())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
58
src/duckstation-nogui/win32_nogui_platform.h
Normal file
58
src/duckstation-nogui/win32_nogui_platform.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "common/windows_headers.h"
|
||||||
|
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
|
||||||
|
class Win32NoGUIPlatform : public NoGUIPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Win32NoGUIPlatform();
|
||||||
|
~Win32NoGUIPlatform();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void ReportError(const std::string_view& title, const std::string_view& message) override;
|
||||||
|
|
||||||
|
void SetDefaultConfig(SettingsInterface& si) override;
|
||||||
|
|
||||||
|
bool CreatePlatformWindow(std::string title) override;
|
||||||
|
void DestroyPlatformWindow() override;
|
||||||
|
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||||
|
void SetPlatformWindowTitle(std::string title) override;
|
||||||
|
void* GetPlatformWindowHandle() override;
|
||||||
|
|
||||||
|
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) override;
|
||||||
|
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) override;
|
||||||
|
|
||||||
|
void RunMessageLoop() override;
|
||||||
|
void ExecuteInMessageLoop(std::function<void()> func) override;
|
||||||
|
void QuitMessageLoop() override;
|
||||||
|
|
||||||
|
void SetFullscreen(bool enabled) override;
|
||||||
|
|
||||||
|
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum : u32
|
||||||
|
{
|
||||||
|
WM_FIRST = WM_USER + 1337,
|
||||||
|
WM_FUNC = WM_FIRST,
|
||||||
|
WM_WAKEUP,
|
||||||
|
WM_LAST = WM_WAKEUP
|
||||||
|
};
|
||||||
|
|
||||||
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
HWND m_hwnd{};
|
||||||
|
DWORD m_window_thread_id = 0;
|
||||||
|
RECT m_windowed_rect = {};
|
||||||
|
float m_window_scale = 1.0f;
|
||||||
|
|
||||||
|
std::atomic_bool m_message_loop_running{false};
|
||||||
|
std::atomic_bool m_fullscreen{false};
|
||||||
|
|
||||||
|
DWORD m_last_mouse_buttons = 0;
|
||||||
|
};
|
327
src/duckstation-nogui/x11_nogui_platform.cpp
Normal file
327
src/duckstation-nogui/x11_nogui_platform.cpp
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
#include "x11_nogui_platform.h"
|
||||||
|
|
||||||
|
Log_SetChannel(X11NoGUIPlatform);
|
||||||
|
|
||||||
|
X11NoGUIPlatform::X11NoGUIPlatform()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
X11NoGUIPlatform::~X11NoGUIPlatform()
|
||||||
|
{
|
||||||
|
if (m_display)
|
||||||
|
{
|
||||||
|
// Segfaults somewhere in an unloaded module on Ubuntu 22.04 :S
|
||||||
|
// I really don't care enough about X to figure out why. The application is shutting down
|
||||||
|
// anyway, so a leak here isn't a big deal.
|
||||||
|
// XCloseDisplay(m_display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool X11NoGUIPlatform::Initialize()
|
||||||
|
{
|
||||||
|
const int res = XInitThreads();
|
||||||
|
if (res != 0)
|
||||||
|
Log_WarningPrintf("XInitThreads() returned %d, things might not be stable.", res);
|
||||||
|
|
||||||
|
m_display = XOpenDisplay(nullptr);
|
||||||
|
if (!m_display)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to connect to X11 display.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||||
|
{
|
||||||
|
const std::string title_copy(title);
|
||||||
|
const std::string message_copy(message);
|
||||||
|
Log_ErrorPrintf("%s: %s", title_copy.c_str(), message_copy.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::SetDefaultConfig(SettingsInterface& si) {}
|
||||||
|
|
||||||
|
bool X11NoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||||
|
{
|
||||||
|
s32 window_x, window_y, window_width, window_height;
|
||||||
|
bool has_window_pos = NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height);
|
||||||
|
if (!has_window_pos)
|
||||||
|
{
|
||||||
|
window_x = 0;
|
||||||
|
window_y = 0;
|
||||||
|
window_width = DEFAULT_WINDOW_WIDTH;
|
||||||
|
window_height = DEFAULT_WINDOW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
XDisplayLocker locker(m_display);
|
||||||
|
{
|
||||||
|
m_window = XCreateSimpleWindow(m_display, DefaultRootWindow(m_display), window_x, window_y, window_width,
|
||||||
|
window_height, 0, 0, BlackPixel(m_display, 0));
|
||||||
|
if (!m_window)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to create X window");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
XSelectInput(m_display, m_window,
|
||||||
|
StructureNotifyMask | KeyPressMask | KeyReleaseMask | FocusChangeMask | PointerMotionMask |
|
||||||
|
ButtonPressMask | ButtonReleaseMask);
|
||||||
|
XStoreName(m_display, m_window, title.c_str());
|
||||||
|
|
||||||
|
// Enable close notifications.
|
||||||
|
Atom wmProtocols[1];
|
||||||
|
wmProtocols[0] = XInternAtom(m_display, "WM_DELETE_WINDOW", True);
|
||||||
|
XSetWMProtocols(m_display, m_window, wmProtocols, 1);
|
||||||
|
|
||||||
|
m_window_info.surface_width = static_cast<u32>(window_width);
|
||||||
|
m_window_info.surface_height = static_cast<u32>(window_height);
|
||||||
|
m_window_info.surface_scale = 1.0f;
|
||||||
|
m_window_info.type = WindowInfo::Type::X11;
|
||||||
|
m_window_info.window_handle = reinterpret_cast<void*>(m_window);
|
||||||
|
m_window_info.display_connection = m_display;
|
||||||
|
|
||||||
|
XMapRaised(m_display, m_window);
|
||||||
|
XFlush(m_display);
|
||||||
|
XSync(m_display, True);
|
||||||
|
InitializeKeyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessXEvents();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::DestroyPlatformWindow()
|
||||||
|
{
|
||||||
|
m_window_info = {};
|
||||||
|
|
||||||
|
if (m_window)
|
||||||
|
{
|
||||||
|
XDisplayLocker locker(m_display);
|
||||||
|
SaveWindowGeometry();
|
||||||
|
XUnmapWindow(m_display, m_window);
|
||||||
|
XDestroyWindow(m_display, m_window);
|
||||||
|
m_window = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<WindowInfo> X11NoGUIPlatform::GetPlatformWindowInfo()
|
||||||
|
{
|
||||||
|
if (m_window_info.type == WindowInfo::Type::X11)
|
||||||
|
return m_window_info;
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||||
|
{
|
||||||
|
ExecuteInMessageLoop([this, title = std::move(title)]() {
|
||||||
|
if (m_window)
|
||||||
|
{
|
||||||
|
XDisplayLocker locker(m_display);
|
||||||
|
XStoreName(m_display, m_window, title.c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void* X11NoGUIPlatform::GetPlatformWindowHandle()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<void*>(m_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::InitializeKeyMap()
|
||||||
|
{
|
||||||
|
int min_keycode = 0, max_keycode = -1;
|
||||||
|
XDisplayKeycodes(m_display, &min_keycode, &max_keycode);
|
||||||
|
for (int keycode = 0; keycode <= max_keycode; keycode++)
|
||||||
|
{
|
||||||
|
KeySym keysym = NoSymbol;
|
||||||
|
for (int i = 0; i < 8 && keysym == NoSymbol; i++)
|
||||||
|
keysym = XKeycodeToKeysym(m_display, static_cast<KeyCode>(keycode), i);
|
||||||
|
if (keysym == NoSymbol)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
KeySym upper_sym;
|
||||||
|
XConvertCase(keysym, &keysym, &upper_sym);
|
||||||
|
|
||||||
|
// Would this fail?
|
||||||
|
const char* keyname = XKeysymToString(keysym);
|
||||||
|
if (!keyname)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_key_map.emplace(static_cast<s32>(keysym), keyname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> X11NoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||||
|
{
|
||||||
|
for (const auto& it : m_key_map)
|
||||||
|
{
|
||||||
|
if (StringUtil::Strncasecmp(it.second.c_str(), str.data(), str.length()) == 0)
|
||||||
|
return it.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> X11NoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||||
|
{
|
||||||
|
const auto it = m_key_map.find(static_cast<s32>(code));
|
||||||
|
return (it != m_key_map.end()) ? std::optional<std::string>(it->second) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::ProcessXEvents()
|
||||||
|
{
|
||||||
|
XLockDisplay(m_display);
|
||||||
|
|
||||||
|
for (int num_events = XPending(m_display); num_events > 0; num_events--)
|
||||||
|
{
|
||||||
|
XEvent event;
|
||||||
|
XNextEvent(m_display, &event);
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case KeyPress:
|
||||||
|
case KeyRelease:
|
||||||
|
{
|
||||||
|
const KeySym sym = XLookupKeysym(&event.xkey, 0);
|
||||||
|
if (sym != NoSymbol)
|
||||||
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(sym), (event.type == KeyPress));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ButtonPress:
|
||||||
|
case ButtonRelease:
|
||||||
|
{
|
||||||
|
if (event.xbutton.button >= Button1)
|
||||||
|
{
|
||||||
|
NoGUIHost::ProcessPlatformMouseButtonEvent(static_cast<s32>(event.xbutton.button - Button1),
|
||||||
|
event.type == ButtonPress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MotionNotify:
|
||||||
|
{
|
||||||
|
NoGUIHost::ProcessPlatformMouseMoveEvent(static_cast<float>(event.xmotion.x),
|
||||||
|
static_cast<float>(event.xmotion.y));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConfigureNotify:
|
||||||
|
{
|
||||||
|
const s32 width = std::max<s32>(static_cast<s32>(event.xconfigure.width), 1);
|
||||||
|
const s32 height = std::max<s32>(static_cast<s32>(event.xconfigure.height), 1);
|
||||||
|
NoGUIHost::ProcessPlatformWindowResize(width, height, m_window_info.surface_scale);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FocusIn:
|
||||||
|
{
|
||||||
|
NoGUIHost::PlatformWindowFocusGained();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FocusOut:
|
||||||
|
{
|
||||||
|
NoGUIHost::PlatformWindowFocusGained();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ClientMessage:
|
||||||
|
{
|
||||||
|
if (static_cast<Atom>(event.xclient.data.l[0]) == XInternAtom(m_display, "WM_DELETE_WINDOW", False))
|
||||||
|
Host::RequestExit(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XUnlockDisplay(m_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::RunMessageLoop()
|
||||||
|
{
|
||||||
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
ProcessXEvents();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
while (!m_callback_queue.empty())
|
||||||
|
{
|
||||||
|
std::function<void()> func = std::move(m_callback_queue.front());
|
||||||
|
m_callback_queue.pop_front();
|
||||||
|
lock.unlock();
|
||||||
|
func();
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make this suck less.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_callback_queue_mutex);
|
||||||
|
m_callback_queue.push_back(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::QuitMessageLoop()
|
||||||
|
{
|
||||||
|
m_message_loop_running.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::SetFullscreen(bool enabled)
|
||||||
|
{
|
||||||
|
if (!m_window || m_fullscreen.load(std::memory_order_acquire) == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
XDisplayLocker locker(m_display);
|
||||||
|
|
||||||
|
XEvent event;
|
||||||
|
event.xclient.type = ClientMessage;
|
||||||
|
event.xclient.message_type = XInternAtom(m_display, "_NET_WM_STATE", False);
|
||||||
|
event.xclient.window = m_window;
|
||||||
|
event.xclient.format = 32;
|
||||||
|
event.xclient.data.l[0] = _NET_WM_STATE_TOGGLE;
|
||||||
|
event.xclient.data.l[1] = XInternAtom(m_display, "_NET_WM_STATE_FULLSCREEN", False);
|
||||||
|
if (!XSendEvent(m_display, DefaultRootWindow(m_display), False, SubstructureRedirectMask | SubstructureNotifyMask,
|
||||||
|
&event))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to switch to %s", enabled ? "Fullscreen" : "windowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_fullscreen.store(enabled, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11NoGUIPlatform::SaveWindowGeometry()
|
||||||
|
{
|
||||||
|
int x = 0, y = 0;
|
||||||
|
unsigned int width = 0, height = 0;
|
||||||
|
unsigned int dummy_border, dummy_depth;
|
||||||
|
Window dummy_window;
|
||||||
|
XGetGeometry(m_display, m_window, &dummy_window, &x, &y, &width, &height, &dummy_border, &dummy_depth);
|
||||||
|
if (width > 0 && height > 0)
|
||||||
|
NoGUIHost::SavePlatformWindowGeometry(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool X11NoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateX11Platform()
|
||||||
|
{
|
||||||
|
std::unique_ptr<X11NoGUIPlatform> ret = std::unique_ptr<X11NoGUIPlatform>(new X11NoGUIPlatform());
|
||||||
|
if (!ret->Initialize())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
91
src/duckstation-nogui/x11_nogui_platform.h
Normal file
91
src/duckstation-nogui/x11_nogui_platform.h
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
|
||||||
|
// Why do we have all these here instead of in the source?
|
||||||
|
// Because X11 is a giant turd and #defines commonly used words.
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/threading.h"
|
||||||
|
#include "core/host.h"
|
||||||
|
#include "core/host_settings.h"
|
||||||
|
#include "nogui_host.h"
|
||||||
|
#include "nogui_platform.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Include X stuff *last*.
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
#define _NET_WM_STATE_REMOVE 0
|
||||||
|
#define _NET_WM_STATE_ADD 1
|
||||||
|
#define _NET_WM_STATE_TOGGLE 2
|
||||||
|
|
||||||
|
class X11NoGUIPlatform : public NoGUIPlatform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
X11NoGUIPlatform();
|
||||||
|
~X11NoGUIPlatform();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
|
||||||
|
void ReportError(const std::string_view& title, const std::string_view& message) override;
|
||||||
|
|
||||||
|
void SetDefaultConfig(SettingsInterface& si) override;
|
||||||
|
|
||||||
|
bool CreatePlatformWindow(std::string title) override;
|
||||||
|
void DestroyPlatformWindow() override;
|
||||||
|
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||||
|
void SetPlatformWindowTitle(std::string title) override;
|
||||||
|
void* GetPlatformWindowHandle() override;
|
||||||
|
|
||||||
|
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str) override;
|
||||||
|
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code) override;
|
||||||
|
|
||||||
|
void RunMessageLoop() override;
|
||||||
|
void ExecuteInMessageLoop(std::function<void()> func) override;
|
||||||
|
void QuitMessageLoop() override;
|
||||||
|
|
||||||
|
void SetFullscreen(bool enabled) override;
|
||||||
|
|
||||||
|
bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitializeKeyMap();
|
||||||
|
void SaveWindowGeometry();
|
||||||
|
void ProcessXEvents();
|
||||||
|
|
||||||
|
std::atomic_bool m_message_loop_running{false};
|
||||||
|
std::atomic_bool m_fullscreen{false};
|
||||||
|
|
||||||
|
WindowInfo m_window_info = {};
|
||||||
|
|
||||||
|
Display* m_display = nullptr;
|
||||||
|
Window m_window = {};
|
||||||
|
|
||||||
|
std::unordered_map<s32, std::string> m_key_map;
|
||||||
|
|
||||||
|
std::deque<std::function<void()>> m_callback_queue;
|
||||||
|
std::mutex m_callback_queue_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class XDisplayLocker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
XDisplayLocker(Display* dpy) : m_display(dpy) { XLockDisplay(m_display); }
|
||||||
|
|
||||||
|
~XDisplayLocker() { XUnlockDisplay(m_display); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Display* m_display;
|
||||||
|
};
|
Loading…
Reference in a new issue