diff --git a/src/duckstation-nogui/CMakeLists.txt b/src/duckstation-nogui/CMakeLists.txt index bb59fa149..cbfad2364 100644 --- a/src/duckstation-nogui/CMakeLists.txt +++ b/src/duckstation-nogui/CMakeLists.txt @@ -1,16 +1,35 @@ add_executable(duckstation-nogui - imgui_impl_sdl.cpp - imgui_impl_sdl.h main.cpp - sdl_host_interface.cpp - sdl_host_interface.h - sdl_key_names.h - sdl_util.cpp - sdl_util.h + nogui_host_interface.cpp + nogui_host_interface.h ) -target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS}) -target_link_libraries(duckstation-nogui PRIVATE core common imgui glad frontend-common scmversion vulkan-loader ${SDL2_LIBRARIES}) +target_link_libraries(duckstation-nogui PRIVATE core common imgui glad frontend-common scmversion vulkan-loader) + +if(USE_SDL2) + target_sources(duckstation-nogui PRIVATE + imgui_impl_sdl.cpp + imgui_impl_sdl.h + sdl_host_interface.cpp + sdl_host_interface.h + sdl_key_names.h + ) + target_include_directories(duckstation-nogui PRIVATE ${SDL2_INCLUDE_DIRS}) + target_link_libraries(duckstation-nogui PRIVATE ${SDL2_LIBRARIES}) +endif() + +if(USE_DRMKMS) + find_package(LIBEVDEV REQUIRED) + + target_sources(duckstation-nogui PRIVATE + drm_host_interface.cpp + drm_host_interface.h + ) + target_compile_definitions(duckstation-nogui PRIVATE "-DUSE_DRMKMS=1") + target_compile_definitions(duckstation-nogui PRIVATE "-DUSE_LIBEVDEV=1") + target_include_directories(duckstation-nogui PRIVATE ${LIBEVDEV_INCLUDE_DIRS}) + target_link_libraries(duckstation-nogui PRIVATE ${LIBEVDEV_LIBRARIES}) +endif() if(WIN32) target_sources(duckstation-nogui PRIVATE diff --git a/src/duckstation-nogui/drm_host_interface.cpp b/src/duckstation-nogui/drm_host_interface.cpp new file mode 100644 index 000000000..af95ced68 --- /dev/null +++ b/src/duckstation-nogui/drm_host_interface.cpp @@ -0,0 +1,211 @@ +#include "drm_host_interface.h" +#include "common/log.h" +#include "common/string_util.h" +#include "evdev_key_names.h" +#include "imgui.h" +#include +#include +#include +#include +Log_SetChannel(DRMHostInterface); + +DRMHostInterface::DRMHostInterface() = default; + +DRMHostInterface::~DRMHostInterface() +{ + CloseEVDevFDs(); +} + +std::unique_ptr DRMHostInterface::Create() +{ + return std::make_unique(); +} + +bool DRMHostInterface::Initialize() +{ + if (!NoGUIHostInterface::Initialize()) + return false; + + OpenEVDevFDs(); + + signal(SIGTERM, SIGTERMHandler); + signal(SIGINT, SIGTERMHandler); + signal(SIGQUIT, SIGTERMHandler); + return true; +} + +void DRMHostInterface::Shutdown() +{ + CloseEVDevFDs(); + NoGUIHostInterface::Shutdown(); +} + +bool DRMHostInterface::IsFullscreen() const +{ + return true; +} + +bool DRMHostInterface::SetFullscreen(bool enabled) +{ + return enabled; +} + +void DRMHostInterface::FixIncompatibleSettings(bool display_osd_messages) +{ + NoGUIHostInterface::FixIncompatibleSettings(display_osd_messages); + + // Some things we definitely don't want. + g_settings.confim_power_off = false; +} + +bool DRMHostInterface::CreatePlatformWindow() +{ + Assert(!m_drm_display); + m_drm_display = std::make_unique(); + if (!m_drm_display->Initialize()) + { + m_drm_display.reset(); + return false; + } + + SetImGuiKeyMap(); + return true; +} + +void DRMHostInterface::DestroyPlatformWindow() +{ + m_drm_display.reset(); +} + +std::optional DRMHostInterface::GetPlatformWindowInfo() +{ + WindowInfo wi; + wi.type = WindowInfo::Type::DRM; + wi.display_connection = m_drm_display.get(); + wi.surface_width = m_drm_display->GetWidth(); + wi.surface_height = m_drm_display->GetHeight(); + wi.surface_format = WindowInfo::SurfaceFormat::Auto; + return wi; +} + +void DRMHostInterface::PollAndUpdate() +{ + PollEvDevKeyboards(); + + NoGUIHostInterface::PollAndUpdate(); +} + +void DRMHostInterface::OpenEVDevFDs() +{ + for (int i = 0; i < 1000; i++) + { + TinyString path; + path.Format("/dev/input/event%d", i); + + int fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) + break; + + struct libevdev* obj; + if (libevdev_new_from_fd(fd, &obj) != 0) + { + Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray()); + close(fd); + continue; + } + + Log_DevPrintf("Input path: %s", path.GetCharArray()); + Log_DevPrintf("Input device name: \"%s\"", libevdev_get_name(obj)); + Log_DevPrintf("Input device ID: bus %#x vendor %#x product %#x", libevdev_get_id_bustype(obj), + libevdev_get_id_vendor(obj), libevdev_get_id_product(obj)); + if (!libevdev_has_event_code(obj, EV_KEY, KEY_SPACE)) + { + Log_DevPrintf("This device does not look like a keyboard"); + libevdev_free(obj); + close(fd); + continue; + } + + const int grab_res = libevdev_grab(obj, LIBEVDEV_GRAB); + if (grab_res != 0) + Log_WarningPrintf("Failed to grab '%s' (%s): %d", libevdev_get_name(obj), path.GetCharArray(), grab_res); + + m_evdev_keyboards.push_back({obj, fd}); + } +} + +void DRMHostInterface::CloseEVDevFDs() +{ + for (const EvDevKeyboard& kb : m_evdev_keyboards) + { + libevdev_grab(kb.obj, LIBEVDEV_UNGRAB); + libevdev_free(kb.obj); + close(kb.fd); + } + m_evdev_keyboards.clear(); +} + +void DRMHostInterface::PollEvDevKeyboards() +{ + for (const EvDevKeyboard& kb : m_evdev_keyboards) + { + struct input_event ev; + while (libevdev_next_event(kb.obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0) + { + // auto-repeat + if (ev.value == 2) + continue; + + const bool pressed = (ev.value == 1); + const HostKeyCode code = static_cast(ev.code); + if (code >= 0 && code < countof(ImGuiIO::KeysDown)) + ImGui::GetIO().KeysDown[code] = pressed; + + HandleHostKeyEvent(code, pressed); + } + } +} + +void DRMHostInterface::SetImGuiKeyMap() +{ + ImGuiIO& io = ImGui::GetIO(); + + io.KeyMap[ImGuiKey_Tab] = KEY_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN; + io.KeyMap[ImGuiKey_PageUp] = KEY_PAGEUP; + io.KeyMap[ImGuiKey_PageDown] = KEY_PAGEDOWN; + io.KeyMap[ImGuiKey_Home] = KEY_HOME; + io.KeyMap[ImGuiKey_End] = KEY_END; + io.KeyMap[ImGuiKey_Insert] = KEY_INSERT; + io.KeyMap[ImGuiKey_Delete] = KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Space] = KEY_SPACE; + io.KeyMap[ImGuiKey_Enter] = KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = KEY_ESC; + io.KeyMap[ImGuiKey_KeyPadEnter] = KEY_KPENTER; + io.KeyMap[ImGuiKey_A] = KEY_A; + io.KeyMap[ImGuiKey_C] = KEY_C; + io.KeyMap[ImGuiKey_V] = KEY_V; + io.KeyMap[ImGuiKey_X] = KEY_X; + io.KeyMap[ImGuiKey_Y] = KEY_Y; + io.KeyMap[ImGuiKey_Z] = KEY_Z; +} + +std::optional DRMHostInterface::GetHostKeyCode(const std::string_view key_code) const +{ + std::optional kc = EvDevKeyNames::GetKeyCodeForName(key_code); + if (!kc.has_value()) + return std::nullopt; + + return static_cast(kc.value()); +} + +void DRMHostInterface::SIGTERMHandler(int sig) +{ + Log_InfoPrintf("Recieved SIGTERM"); + static_cast(g_host_interface)->m_quit_request = true; + signal(sig, SIG_DFL); +} diff --git a/src/duckstation-nogui/drm_host_interface.h b/src/duckstation-nogui/drm_host_interface.h new file mode 100644 index 000000000..49c9247ef --- /dev/null +++ b/src/duckstation-nogui/drm_host_interface.h @@ -0,0 +1,50 @@ +#pragma once +#include "common/drm_display.h" +#include "nogui_host_interface.h" +#include +#include +#include + +class DRMHostInterface final : public NoGUIHostInterface +{ +public: + DRMHostInterface(); + ~DRMHostInterface(); + + bool Initialize(); + void Shutdown(); + + bool IsFullscreen() const override; + bool SetFullscreen(bool enabled) override; + + static std::unique_ptr Create(); + +protected: + virtual void FixIncompatibleSettings(bool display_osd_messages) override; + + bool CreatePlatformWindow() override; + void DestroyPlatformWindow() override; + std::optional GetPlatformWindowInfo() override; + + std::optional GetHostKeyCode(const std::string_view key_code) const override; + + void PollAndUpdate() override; + +private: + static void SIGTERMHandler(int sig); + + void OpenEVDevFDs(); + void CloseEVDevFDs(); + void PollEvDevKeyboards(); + void SetImGuiKeyMap(); + + std::unique_ptr m_drm_display; + + struct EvDevKeyboard + { + struct libevdev* obj; + int fd; + }; + + std::vector m_evdev_keyboards; +}; diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj b/src/duckstation-nogui/duckstation-nogui.vcxproj index 72f908258..d5522c346 100644 --- a/src/duckstation-nogui/duckstation-nogui.vcxproj +++ b/src/duckstation-nogui/duckstation-nogui.vcxproj @@ -68,17 +68,61 @@ + + true + true + true + true + true + true + true + true + true + true + true + true + + - + + + true + true + true + true + true + true + true + true + true + true + true + true + + + true + true + true + true + true + true + true + true + true + true + true + true + + - + @@ -103,44 +147,44 @@ Application true v142 - NotSet + Unicode Application true v142 - NotSet + Unicode Application true v142 - NotSet + Unicode Application true v142 - NotSet + Unicode Application true v142 - NotSet + Unicode Application true v142 - NotSet + Unicode Application false v142 true - NotSet + Unicode false @@ -148,7 +192,7 @@ false v142 true - NotSet + Unicode false @@ -156,7 +200,7 @@ false v142 true - NotSet + Unicode false @@ -164,7 +208,7 @@ false v142 true - NotSet + Unicode false @@ -172,7 +216,7 @@ false v142 true - NotSet + Unicode false @@ -180,7 +224,7 @@ false v142 true - NotSet + Unicode false @@ -311,7 +355,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -333,7 +377,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -355,7 +399,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -380,7 +424,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -405,7 +449,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -430,7 +474,7 @@ Windows true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) @@ -453,7 +497,7 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) Default @@ -478,7 +522,7 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration @@ -502,7 +546,7 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) Default @@ -526,7 +570,7 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) Default @@ -551,7 +595,7 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration @@ -576,11 +620,11 @@ true true true - SDL2.lib;SDL2main.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) + SDL2.lib;dxgi.lib;d3d11.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration - + \ No newline at end of file diff --git a/src/duckstation-nogui/duckstation-nogui.vcxproj.filters b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters index a54cb633f..553f62864 100644 --- a/src/duckstation-nogui/duckstation-nogui.vcxproj.filters +++ b/src/duckstation-nogui/duckstation-nogui.vcxproj.filters @@ -4,22 +4,27 @@ - + + + - + + + + - + - + - + \ No newline at end of file diff --git a/src/duckstation-nogui/evdev_key_names.h b/src/duckstation-nogui/evdev_key_names.h new file mode 100644 index 000000000..093d5d38c --- /dev/null +++ b/src/duckstation-nogui/evdev_key_names.h @@ -0,0 +1,278 @@ +#pragma once +#include "common/string.h" +#include "common/types.h" +#include +#include +#include +#include +#include +#include + +namespace EvDevKeyNames { + +static const std::map s_evdev_key_names = {{KEY_ESC, "Escape"}, + {KEY_1, "1"}, + {KEY_2, "2"}, + {KEY_3, "3"}, + {KEY_4, "4"}, + {KEY_5, "5"}, + {KEY_6, "6"}, + {KEY_7, "7"}, + {KEY_8, "8"}, + {KEY_9, "9"}, + {KEY_0, "0"}, + {KEY_MINUS, "Minus"}, + {KEY_EQUAL, "Equal"}, + {KEY_BACKSPACE, "Backspace"}, + {KEY_TAB, "Tab"}, + {KEY_Q, "Q"}, + {KEY_W, "W"}, + {KEY_E, "E"}, + {KEY_R, "R"}, + {KEY_T, "T"}, + {KEY_Y, "Y"}, + {KEY_U, "U"}, + {KEY_I, "I"}, + {KEY_O, "O"}, + {KEY_P, "P"}, + {KEY_LEFTBRACE, "Leftbrace"}, + {KEY_RIGHTBRACE, "Rightbrace"}, + {KEY_ENTER, "Return"}, + {KEY_LEFTCTRL, "Leftctrl"}, + {KEY_A, "A"}, + {KEY_S, "S"}, + {KEY_D, "D"}, + {KEY_F, "F"}, + {KEY_G, "G"}, + {KEY_H, "H"}, + {KEY_J, "J"}, + {KEY_K, "K"}, + {KEY_L, "L"}, + {KEY_SEMICOLON, "Semicolon"}, + {KEY_APOSTROPHE, "Apostrophe"}, + {KEY_GRAVE, "Grave"}, + {KEY_LEFTSHIFT, "Leftshift"}, + {KEY_BACKSLASH, "Backslash"}, + {KEY_Z, "Z"}, + {KEY_X, "X"}, + {KEY_C, "C"}, + {KEY_V, "V"}, + {KEY_B, "B"}, + {KEY_N, "N"}, + {KEY_M, "M"}, + {KEY_COMMA, "Comma"}, + {KEY_DOT, "Dot"}, + {KEY_SLASH, "Slash"}, + {KEY_RIGHTSHIFT, "Rightshift"}, + {KEY_KPASTERISK, "Kpasterisk"}, + {KEY_LEFTALT, "Leftalt"}, + {KEY_SPACE, "Space"}, + {KEY_CAPSLOCK, "Capslock"}, + {KEY_F1, "F1"}, + {KEY_F2, "F2"}, + {KEY_F3, "F3"}, + {KEY_F4, "F4"}, + {KEY_F5, "F5"}, + {KEY_F6, "F6"}, + {KEY_F7, "F7"}, + {KEY_F8, "F8"}, + {KEY_F9, "F9"}, + {KEY_F10, "F10"}, + {KEY_NUMLOCK, "Numlock"}, + {KEY_SCROLLLOCK, "Scrolllock"}, + {KEY_KP7, "Kp7"}, + {KEY_KP8, "Kp8"}, + {KEY_KP9, "Kp9"}, + {KEY_KPMINUS, "Kpminus"}, + {KEY_KP4, "Kp4"}, + {KEY_KP5, "Kp5"}, + {KEY_KP6, "Kp6"}, + {KEY_KPPLUS, "Kpplus"}, + {KEY_KP1, "Kp1"}, + {KEY_KP2, "Kp2"}, + {KEY_KP3, "Kp3"}, + {KEY_KP0, "Kp0"}, + {KEY_KPDOT, "Kpdot"}, + {KEY_ZENKAKUHANKAKU, "Zenkakuhankaku"}, + {KEY_102ND, "102nd"}, + {KEY_F11, "F11"}, + {KEY_F12, "F12"}, + {KEY_RO, "Ro"}, + {KEY_KATAKANA, "Katakana"}, + {KEY_HIRAGANA, "Hiragana"}, + {KEY_HENKAN, "Henkan"}, + {KEY_KATAKANAHIRAGANA, "Katakanahiragana"}, + {KEY_MUHENKAN, "Muhenkan"}, + {KEY_KPJPCOMMA, "Kpjpcomma"}, + {KEY_KPENTER, "Kpenter"}, + {KEY_RIGHTCTRL, "Rightctrl"}, + {KEY_KPSLASH, "Kpslash"}, + {KEY_SYSRQ, "Sysrq"}, + {KEY_RIGHTALT, "RightAlt"}, + {KEY_LINEFEED, "Linefeed"}, + {KEY_HOME, "Home"}, + {KEY_UP, "Up"}, + {KEY_PAGEUP, "PageUp"}, + {KEY_LEFT, "Left"}, + {KEY_RIGHT, "Right"}, + {KEY_END, "End"}, + {KEY_DOWN, "Down"}, + {KEY_PAGEDOWN, "PageDown"}, + {KEY_INSERT, "Insert"}, + {KEY_DELETE, "Delete"}, + {KEY_MACRO, "Macro"}, + {KEY_MUTE, "Mute"}, + {KEY_VOLUMEDOWN, "VolumeDown"}, + {KEY_VOLUMEUP, "VolumeUp"}, + {KEY_POWER, "Power"}, + {KEY_KPEQUAL, "Kpequal"}, + {KEY_KPPLUSMINUS, "Kpplusminus"}, + {KEY_PAUSE, "Pause"}, + {KEY_SCALE, "Scale"}, + {KEY_KPCOMMA, "Kpcomma"}, + {KEY_HANGEUL, "Hangeul"}, + {KEY_HANGUEL, "Hanguel"}, + {KEY_HANJA, "Hanja"}, + {KEY_YEN, "Yen"}, + {KEY_LEFTMETA, "Leftmeta"}, + {KEY_RIGHTMETA, "Rightmeta"}, + {KEY_COMPOSE, "Compose"}, + {KEY_STOP, "Stop"}, + {KEY_AGAIN, "Again"}, + {KEY_PROPS, "Props"}, + {KEY_UNDO, "Undo"}, + {KEY_FRONT, "Front"}, + {KEY_COPY, "Copy"}, + {KEY_OPEN, "Open"}, + {KEY_PASTE, "Paste"}, + {KEY_FIND, "Find"}, + {KEY_CUT, "Cut"}, + {KEY_HELP, "Help"}, + {KEY_MENU, "Menu"}, + {KEY_CALC, "Calc"}, + {KEY_SETUP, "Setup"}, + {KEY_SLEEP, "Sleep"}, + {KEY_WAKEUP, "Wakeup"}, + {KEY_FILE, "File"}, + {KEY_SENDFILE, "Sendfile"}, + {KEY_DELETEFILE, "Deletefile"}, + {KEY_XFER, "Xfer"}, + {KEY_PROG1, "Prog1"}, + {KEY_PROG2, "Prog2"}, + {KEY_WWW, "Www"}, + {KEY_MSDOS, "Msdos"}, + {KEY_COFFEE, "Coffee"}, + {KEY_SCREENLOCK, "Screenlock"}, + {KEY_ROTATE_DISPLAY, "Rotate_display"}, + {KEY_DIRECTION, "Direction"}, + {KEY_CYCLEWINDOWS, "Cyclewindows"}, + {KEY_MAIL, "Mail"}, + {KEY_BOOKMARKS, "Bookmarks"}, + {KEY_COMPUTER, "Computer"}, + {KEY_BACK, "Back"}, + {KEY_FORWARD, "Forward"}, + {KEY_CLOSECD, "Closecd"}, + {KEY_EJECTCD, "Ejectcd"}, + {KEY_EJECTCLOSECD, "Ejectclosecd"}, + {KEY_NEXTSONG, "Nextsong"}, + {KEY_PLAYPAUSE, "Playpause"}, + {KEY_PREVIOUSSONG, "Previoussong"}, + {KEY_STOPCD, "Stopcd"}, + {KEY_RECORD, "Record"}, + {KEY_REWIND, "Rewind"}, + {KEY_PHONE, "Phone"}, + {KEY_ISO, "Iso"}, + {KEY_CONFIG, "Config"}, + {KEY_HOMEPAGE, "Homepage"}, + {KEY_REFRESH, "Refresh"}, + {KEY_EXIT, "Exit"}, + {KEY_MOVE, "Move"}, + {KEY_EDIT, "Edit"}, + {KEY_SCROLLUP, "Scrollup"}, + {KEY_SCROLLDOWN, "Scrolldown"}, + {KEY_KPLEFTPAREN, "Kpleftparen"}, + {KEY_KPRIGHTPAREN, "Kprightparen"}, + {KEY_NEW, "New"}, + {KEY_REDO, "Redo"}, + {KEY_F13, "F13"}, + {KEY_F14, "F14"}, + {KEY_F15, "F15"}, + {KEY_F16, "F16"}, + {KEY_F17, "F17"}, + {KEY_F18, "F18"}, + {KEY_F19, "F19"}, + {KEY_F20, "F20"}, + {KEY_F21, "F21"}, + {KEY_F22, "F22"}, + {KEY_F23, "F23"}, + {KEY_F24, "F24"}, + {KEY_PLAYCD, "Playcd"}, + {KEY_PAUSECD, "Pausecd"}, + {KEY_PROG3, "Prog3"}, + {KEY_PROG4, "Prog4"}, + {KEY_DASHBOARD, "Dashboard"}, + {KEY_SUSPEND, "Suspend"}, + {KEY_CLOSE, "Close"}, + {KEY_PLAY, "Play"}, + {KEY_FASTFORWARD, "Fastforward"}, + {KEY_BASSBOOST, "Bassboost"}, + {KEY_PRINT, "Print"}, + {KEY_HP, "Hp"}, + {KEY_CAMERA, "Camera"}, + {KEY_SOUND, "Sound"}, + {KEY_QUESTION, "Question"}, + {KEY_EMAIL, "Email"}, + {KEY_CHAT, "Chat"}, + {KEY_SEARCH, "Search"}, + {KEY_CONNECT, "Connect"}, + {KEY_FINANCE, "Finance"}, + {KEY_SPORT, "Sport"}, + {KEY_SHOP, "Shop"}, + {KEY_ALTERASE, "Alterase"}, + {KEY_CANCEL, "Cancel"}, + {KEY_BRIGHTNESSDOWN, "Brightnessdown"}, + {KEY_BRIGHTNESSUP, "Brightnessup"}, + {KEY_MEDIA, "Media"}, + {KEY_SWITCHVIDEOMODE, "Switchvideomode"}, + {KEY_KBDILLUMTOGGLE, "Kbdillumtoggle"}, + {KEY_KBDILLUMDOWN, "Kbdillumdown"}, + {KEY_KBDILLUMUP, "Kbdillumup"}, + {KEY_SEND, "Send"}, + {KEY_REPLY, "Reply"}, + {KEY_FORWARDMAIL, "Forwardmail"}, + {KEY_SAVE, "Save"}, + {KEY_DOCUMENTS, "Documents"}, + {KEY_BATTERY, "Battery"}, + {KEY_BLUETOOTH, "Bluetooth"}, + {KEY_WLAN, "Wlan"}, + {KEY_UWB, "Uwb"}, + {KEY_UNKNOWN, "Unknown"}, + {KEY_VIDEO_NEXT, "Video_next"}, + {KEY_VIDEO_PREV, "Video_prev"}, + {KEY_BRIGHTNESS_CYCLE, "Brightness_cycle"}, + {KEY_BRIGHTNESS_AUTO, "Brightness_auto"}, + {KEY_BRIGHTNESS_ZERO, "Brightness_zero"}, + {KEY_DISPLAY_OFF, "Display_off"}, + {KEY_WWAN, "Wwan"}, + {KEY_WIMAX, "Wimax"}, + {KEY_RFKILL, "Rfkill"}, + {KEY_MICMUTE, "Micmute"}}; + +const char* GetKeyName(int key) +{ + const auto it = s_evdev_key_names.find(key); + return it == s_evdev_key_names.end() ? nullptr : it->second; +} + +std::optional GetKeyCodeForName(const std::string_view key_name) +{ + for (const auto& it : s_evdev_key_names) + { + if (key_name == it.second) + return it.first; + } + + return std::nullopt; +} + +} // namespace EvDevKeyNames diff --git a/src/duckstation-nogui/imgui_impl_sdl.cpp b/src/duckstation-nogui/imgui_impl_sdl.cpp index 60a8e88b2..4169deb4d 100644 --- a/src/duckstation-nogui/imgui_impl_sdl.cpp +++ b/src/duckstation-nogui/imgui_impl_sdl.cpp @@ -131,7 +131,7 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) return false; } -bool ImGui_ImplSDL2_Init(SDL_Window* window) +void ImGui_ImplSDL2_Init(SDL_Window* window) { g_Window = window; @@ -176,7 +176,6 @@ bool ImGui_ImplSDL2_Init(SDL_Window* window) g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); - return true; } void ImGui_ImplSDL2_Shutdown() diff --git a/src/duckstation-nogui/imgui_impl_sdl.h b/src/duckstation-nogui/imgui_impl_sdl.h index 0728620a4..8eda4ecf2 100644 --- a/src/duckstation-nogui/imgui_impl_sdl.h +++ b/src/duckstation-nogui/imgui_impl_sdl.h @@ -22,7 +22,7 @@ struct SDL_Window; typedef union SDL_Event SDL_Event; -IMGUI_IMPL_API bool ImGui_ImplSDL2_Init(SDL_Window* window); +IMGUI_IMPL_API void ImGui_ImplSDL2_Init(SDL_Window* window); IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); diff --git a/src/duckstation-nogui/main.cpp b/src/duckstation-nogui/main.cpp index 1f48cf920..9eff50cbe 100644 --- a/src/duckstation-nogui/main.cpp +++ b/src/duckstation-nogui/main.cpp @@ -1,48 +1,129 @@ #include "common/assert.h" +#include "common/file_system.h" #include "common/log.h" +#include "common/string_util.h" #include "core/system.h" -#include "frontend-common/sdl_initializer.h" -#include "sdl_host_interface.h" -#include #include #include +#include +#include -int main(int argc, char* argv[]) +#ifdef USE_DRMKMS +#include "drm_host_interface.h" +#endif + +#ifdef WITH_SDL2 +#include "sdl_host_interface.h" +#endif + +#ifdef _WIN32 +#include "common/windows_headers.h" +#include "win32_host_interface.h" +#include +#endif + +static std::unique_ptr CreateHostInterface(const char* platform) { - FrontendCommon::EnsureSDLInitialized(); + std::unique_ptr host_interface; - std::unique_ptr host_interface = SDLHostInterface::Create(); - std::unique_ptr boot_params; - if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params)) - { - SDL_Quit(); - return EXIT_FAILURE; - } +#ifdef USE_DRMKMS + // TODO: We should detect if we have a display here... + if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "drm") == 0)) + host_interface = DRMHostInterface::Create(); +#endif +#ifdef WITH_SDL2 + if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "sdl") == 0)) + host_interface = SDLHostInterface::Create(); +#endif + +#ifdef _WIN32 + if (!host_interface && (!platform || StringUtil::Strcasecmp(platform, "win32") == 0)) + host_interface = Win32HostInterface::Create(); +#endif + + return host_interface; +} + +static int Run(std::unique_ptr host_interface, std::unique_ptr boot_params) +{ if (!host_interface->Initialize()) { host_interface->Shutdown(); - SDL_Quit(); return EXIT_FAILURE; } if (boot_params) - { - if (!host_interface->BootSystem(*boot_params) && host_interface->InBatchMode()) - { - host_interface->Shutdown(); - host_interface.reset(); - SDL_Quit(); - return EXIT_FAILURE; - } + host_interface->BootSystem(*boot_params); - boot_params.reset(); + int result; + if (System::IsValid() || !host_interface->InBatchMode()) + { + host_interface->Run(); + result = EXIT_SUCCESS; + } + else + { + host_interface->ReportError("No file specified, and we're in batch mode. Exiting."); + result = EXIT_FAILURE; } - host_interface->Run(); host_interface->Shutdown(); - host_interface.reset(); - - SDL_Quit(); - return EXIT_SUCCESS; + return result; } + +#ifdef _WIN32 + +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + std::unique_ptr host_interface = CreateHostInterface(nullptr); + std::unique_ptr boot_params; + + { + std::vector 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 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(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 host_interface = CreateHostInterface(nullptr); + std::unique_ptr boot_params; + if (!host_interface->ParseCommandLineParameters(argc, argv, &boot_params)) + return EXIT_FAILURE; + + return Run(std::move(host_interface), std::move(boot_params)); +} + +#endif \ No newline at end of file diff --git a/src/duckstation-nogui/nogui_host_interface.cpp b/src/duckstation-nogui/nogui_host_interface.cpp new file mode 100644 index 000000000..671262121 --- /dev/null +++ b/src/duckstation-nogui/nogui_host_interface.cpp @@ -0,0 +1,410 @@ +#include "nogui_host_interface.h" +#include "common/assert.h" +#include "common/byte_stream.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/string_util.h" +#include "core/controller.h" +#include "core/gpu.h" +#include "core/host_display.h" +#include "core/system.h" +#include "frontend-common/controller_interface.h" +#include "frontend-common/fullscreen_ui.h" +#include "frontend-common/icon.h" +#include "frontend-common/imgui_fullscreen.h" +#include "frontend-common/imgui_styles.h" +#include "frontend-common/ini_settings_interface.h" +#include "frontend-common/opengl_host_display.h" +#include "frontend-common/vulkan_host_display.h" +#include +#include +#include +#include +Log_SetChannel(NoGUIHostInterface); + +#ifdef WIN32 +#include "frontend-common/d3d11_host_display.h" +#endif + +NoGUIHostInterface::NoGUIHostInterface() = default; + +NoGUIHostInterface::~NoGUIHostInterface() = default; + +const char* NoGUIHostInterface::GetFrontendName() const +{ + return "DuckStation NoGUI Frontend"; +} + +bool NoGUIHostInterface::Initialize() +{ + // TODO: Make command line. + m_fullscreen_ui_enabled = true; + + // we're always in batch mode for now + m_command_line_flags.batch_mode = !m_fullscreen_ui_enabled; + + if (!CommonHostInterface::Initialize()) + return false; + + CreateImGuiContext(); + + if (!CreatePlatformWindow()) + { + Log_ErrorPrintf("Failed to create platform window"); + ImGui::DestroyContext(); + return false; + } + + if (!CreateDisplay()) + { + Log_ErrorPrintf("Failed to create host display"); + DestroyPlatformWindow(); + ImGui::DestroyContext(); + return false; + } + + // process events to pick up controllers before updating input map + PollAndUpdate(); + UpdateInputMap(); + return true; +} + +void NoGUIHostInterface::Shutdown() +{ + DestroySystem(); + + CommonHostInterface::Shutdown(); + + if (m_display) + { + DestroyDisplay(); + ImGui::DestroyContext(); + } + + DestroyPlatformWindow(); +} + +std::string NoGUIHostInterface::GetStringSettingValue(const char* section, const char* key, + const char* default_value /*= ""*/) +{ + return m_settings_interface->GetStringValue(section, key, default_value); +} + +bool NoGUIHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */) +{ + return m_settings_interface->GetBoolValue(section, key, default_value); +} + +int NoGUIHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */) +{ + return m_settings_interface->GetIntValue(section, key, default_value); +} + +float NoGUIHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */) +{ + return m_settings_interface->GetFloatValue(section, key, default_value); +} + +void NoGUIHostInterface::LoadSettings() +{ + m_settings_interface = std::make_unique(GetSettingsFileName()); + CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::FixIncompatibleSettings(false); +} + +void NoGUIHostInterface::UpdateInputMap() +{ + CommonHostInterface::UpdateInputMap(*m_settings_interface.get()); +} + +void NoGUIHostInterface::ApplySettings(bool display_osd_messages) +{ + Settings old_settings(std::move(g_settings)); + CommonHostInterface::LoadSettings(*m_settings_interface.get()); + CommonHostInterface::ApplyGameSettings(display_osd_messages); + CommonHostInterface::FixIncompatibleSettings(display_osd_messages); + CheckForSettingsChanges(old_settings); +} + +void NoGUIHostInterface::CreateImGuiContext() +{ + ImGui::CreateContext(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; +} + +void NoGUIHostInterface::OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale) +{ + if (new_scale != ImGui::GetIO().DisplayFramebufferScale.x) + { + ImGui::GetIO().DisplayFramebufferScale = ImVec2(new_scale, new_scale); + ImGui::GetStyle() = ImGuiStyle(); + ImGui::StyleColorsDarker(); + ImGui::GetStyle().ScaleAllSizes(new_scale); + } + + if (ImGuiFullscreen::UpdateLayoutScale()) + { + if (ImGuiFullscreen::UpdateFonts()) + { + if (!m_display->UpdateImGuiFontTexture()) + Panic("Failed to update font texture"); + } + } + + if (!System::IsShutdown()) + g_gpu->UpdateResolutionScale(); +} + +bool NoGUIHostInterface::CreateDisplay() +{ + std::optional wi = GetPlatformWindowInfo(); + if (!wi) + { + ReportError("Failed to get platform window info"); + return false; + } + + // imgui init from window + ImGui::GetIO().DisplayFramebufferScale.x = wi->surface_scale; + ImGui::GetIO().DisplayFramebufferScale.y = wi->surface_scale; + ImGui::GetStyle() = ImGuiStyle(); + ImGui::GetStyle().ScaleAllSizes(wi->surface_scale); + ImGui::StyleColorsDarker(); + + Assert(!m_display); + switch (g_settings.gpu_renderer) + { + case GPURenderer::HardwareVulkan: + m_display = std::make_unique(); + break; + + case GPURenderer::HardwareOpenGL: +#ifndef WIN32 + default: +#endif + m_display = std::make_unique(); + break; + +#ifdef WIN32 + case GPURenderer::HardwareD3D11: + default: + m_display = std::make_unique(); + break; +#endif + } + + if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device, + g_settings.gpu_threaded_presentation) || + !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, + g_settings.gpu_threaded_presentation)) + { + ReportError("Failed to create/initialize display render device"); + m_display->DestroyRenderDevice(); + m_display.reset(); + return false; + } + + if (!m_display->CreateImGuiContext() || + (m_fullscreen_ui_enabled && !FullscreenUI::Initialize(this, m_settings_interface.get())) || + !m_display->UpdateImGuiFontTexture()) + { + ReportError("Failed to initialize imgui/fonts/fullscreen UI"); + if (m_fullscreen_ui_enabled) + FullscreenUI::Shutdown(); + + m_display->DestroyImGuiContext(); + m_display->DestroyRenderDevice(); + m_display.reset(); + return false; + } + + return true; +} + +void NoGUIHostInterface::DestroyDisplay() +{ + if (m_fullscreen_ui_enabled) + FullscreenUI::Shutdown(); + + if (m_display) + { + m_display->DestroyImGuiContext(); + m_display->DestroyRenderDevice(); + } + m_display.reset(); +} + +bool NoGUIHostInterface::AcquireHostDisplay() +{ + // Handle renderer switch if required. + const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI(); + bool needs_switch = false; + switch (g_settings.gpu_renderer) + { +#ifdef WIN32 + case GPURenderer::HardwareD3D11: + needs_switch = (render_api != HostDisplay::RenderAPI::D3D11); + break; +#endif + + case GPURenderer::HardwareVulkan: + needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan); + break; + + case GPURenderer::HardwareOpenGL: + needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES); + break; + + case GPURenderer::Software: + default: + needs_switch = false; + break; + } + + if (needs_switch) + { + ImGui::EndFrame(); + DestroyDisplay(); + + // We need to recreate the window, otherwise bad things happen... + DestroyPlatformWindow(); + if (!CreatePlatformWindow()) + Panic("Failed to recreate platform window on GPU renderer switch"); + + if (!CreateDisplay()) + Panic("Failed to recreate display on GPU renderer switch"); + + ImGui::NewFrame(); + } + + if (!CreateHostDisplayResources()) + return false; + + return true; +} + +void NoGUIHostInterface::ReleaseHostDisplay() +{ + ReleaseHostDisplayResources(); + + // restore vsync, since we don't want to burn cycles at the menu + m_display->SetVSync(true); +} + +void NoGUIHostInterface::OnSystemCreated() +{ + CommonHostInterface::OnSystemCreated(); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemCreated(); +} + +void NoGUIHostInterface::OnSystemPaused(bool paused) +{ + CommonHostInterface::OnSystemPaused(paused); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemPaused(paused); +} + +void NoGUIHostInterface::OnSystemDestroyed() +{ + CommonHostInterface::OnSystemDestroyed(); + ReportFormattedMessage("System shut down."); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemDestroyed(); +} + +void NoGUIHostInterface::OnRunningGameChanged() +{ + CommonHostInterface::OnRunningGameChanged(); + + // TODO: Move to common + if (g_settings.apply_game_settings) + ApplySettings(true); +} + +void NoGUIHostInterface::RequestExit() +{ + m_quit_request = true; +} + +void NoGUIHostInterface::PollAndUpdate() +{ + CommonHostInterface::PollAndUpdate(); + + if (m_controller_interface) + m_controller_interface->PollEvents(); +} + +void NoGUIHostInterface::Run() +{ + while (!m_quit_request) + { + RunCallbacks(); + PollAndUpdate(); + if (m_fullscreen_ui_enabled) + FullscreenUI::SetImGuiNavInputs(); + + ImGui::NewFrame(); + + if (System::IsRunning()) + { + if (m_display_all_frames) + System::RunFrame(); + else + System::RunFrames(); + + UpdateControllerRumble(); + if (m_frame_step_request) + { + m_frame_step_request = false; + PauseSystem(true); + } + } + + // rendering + { + DrawImGuiWindows(); + ImGui::Render(); + ImGui::EndFrame(); + + m_display->Render(); + + if (System::IsRunning()) + { + System::UpdatePerformanceCounters(); + + if (m_throttler_enabled) + System::Throttle(); + } + } + } + + // Save state on exit so it can be resumed + if (!System::IsShutdown()) + { + if (g_settings.save_state_on_exit) + SaveResumeSaveState(); + DestroySystem(); + } +} + +void NoGUIHostInterface::RunLater(std::function callback) +{ + std::unique_lock lock(m_queued_callbacks_lock); + m_queued_callbacks.push_back(std::move(callback)); +} + +void NoGUIHostInterface::RunCallbacks() +{ + std::unique_lock 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(); + } +} \ No newline at end of file diff --git a/src/duckstation-nogui/nogui_host_interface.h b/src/duckstation-nogui/nogui_host_interface.h new file mode 100644 index 000000000..28b2c79bc --- /dev/null +++ b/src/duckstation-nogui/nogui_host_interface.h @@ -0,0 +1,73 @@ +#pragma once +#include "common/window_info.h" +#include "core/host_display.h" +#include "core/host_interface.h" +#include "frontend-common/common_host_interface.h" +#include +#include +#include +#include +#include +#include +#include + +class INISettingsInterface; + +class NoGUIHostInterface : public CommonHostInterface +{ +public: + NoGUIHostInterface(); + ~NoGUIHostInterface(); + + const char* GetFrontendName() const override; + + virtual bool Initialize() override; + virtual void Shutdown() override; + virtual void Run(); + + std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override; + bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override; + int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override; + float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; + + void RunLater(std::function callback) override; + void ApplySettings(bool display_osd_messages) override; + +protected: + enum : u32 + { + DEFAULT_WINDOW_WIDTH = 1280, + DEFAULT_WINDOW_HEIGHT = 720 + }; + + virtual void LoadSettings() override; + + bool AcquireHostDisplay() override; + void ReleaseHostDisplay() override; + + void UpdateInputMap() override; + + void OnSystemCreated() override; + void OnSystemPaused(bool paused) override; + void OnSystemDestroyed() override; + void OnRunningGameChanged() override; + + void RequestExit() override; + virtual void PollAndUpdate() override; + + virtual bool CreatePlatformWindow() = 0; + virtual void DestroyPlatformWindow() = 0; + virtual std::optional GetPlatformWindowInfo() = 0; + void OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale); + + bool CreateDisplay(); + void DestroyDisplay(); + void CreateImGuiContext(); + void RunCallbacks(); + + std::unique_ptr m_settings_interface; + std::deque> m_queued_callbacks; + std::mutex m_queued_callbacks_lock; + + bool m_quit_request = false; +}; diff --git a/src/duckstation-nogui/sdl_host_interface.cpp b/src/duckstation-nogui/sdl_host_interface.cpp index 319129e4a..483fc10d4 100644 --- a/src/duckstation-nogui/sdl_host_interface.cpp +++ b/src/duckstation-nogui/sdl_host_interface.cpp @@ -1,41 +1,61 @@ #include "sdl_host_interface.h" -#include "common/assert.h" -#include "common/byte_stream.h" -#include "common/file_system.h" -#include "common/image.h" -#include "common/log.h" -#include "common/string_util.h" -#include "core/gpu.h" -#include "core/host_display.h" -#include "core/system.h" -#include "frontend-common/fullscreen_ui.h" +#include "frontend-common/controller_interface.h" #include "frontend-common/icon.h" -#include "frontend-common/imgui_fullscreen.h" -#include "frontend-common/imgui_styles.h" #include "frontend-common/ini_settings_interface.h" -#include "frontend-common/opengl_host_display.h" -#include "frontend-common/sdl_audio_stream.h" #include "frontend-common/sdl_controller_interface.h" -#include "frontend-common/vulkan_host_display.h" +#include "frontend-common/sdl_initializer.h" #include "imgui.h" #include "imgui_impl_sdl.h" -#include "imgui_stdlib.h" #include "scmversion/scmversion.h" #include "sdl_key_names.h" -#include "sdl_util.h" +#include #include #include Log_SetChannel(SDLHostInterface); -#ifdef WIN32 -#include "frontend-common/d3d11_host_display.h" +#ifdef __APPLE__ +#include +struct NSView; + +static NSView* GetContentViewFromWindow(NSWindow* window) +{ + // window.contentView + return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView")); +} #endif -SDLHostInterface::SDLHostInterface() +static float GetDPIScaleFactor(SDL_Window* window) { - m_run_later_event_id = SDL_RegisterEvents(1); +#ifdef __APPLE__ + static constexpr float DEFAULT_DPI = 72.0f; +#else + static constexpr float DEFAULT_DPI = 96.0f; +#endif + + if (!window) + { + SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1, + SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); + if (!dummy_window) + return 1.0f; + + const float scale = GetDPIScaleFactor(dummy_window); + + SDL_DestroyWindow(dummy_window); + + return scale; + } + + int display_index = SDL_GetWindowDisplayIndex(window); + float display_dpi = DEFAULT_DPI; + if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0) + return 1.0f; + + return display_dpi / DEFAULT_DPI; } +SDLHostInterface::SDLHostInterface() = default; + SDLHostInterface::~SDLHostInterface() = default; const char* SDLHostInterface::GetFrontendName() const @@ -43,16 +63,76 @@ const char* SDLHostInterface::GetFrontendName() const return "DuckStation NoGUI Frontend"; } +std::unique_ptr SDLHostInterface::Create() +{ + return std::make_unique(); +} + +bool SDLHostInterface::Initialize() +{ + FrontendCommon::EnsureSDLInitialized(); + + if (!NoGUIHostInterface::Initialize()) + return false; + + return true; +} + +void SDLHostInterface::Shutdown() +{ + NoGUIHostInterface::Shutdown(); +} + +bool SDLHostInterface::IsFullscreen() const +{ + return m_fullscreen; +} + +bool SDLHostInterface::SetFullscreen(bool enabled) +{ + if (m_fullscreen == enabled) + return true; + + SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + + int window_width, window_height; + SDL_GetWindowSize(m_window, &window_width, &window_height); + m_display->ResizeRenderWindow(window_width, window_height); + OnPlatformWindowResized(window_width, window_height, GetDPIScaleFactor(m_window)); + + m_fullscreen = enabled; + return true; +} + +bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) +{ + if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen) + return false; + + // use imgui scale as the dpr + const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x; + const s32 scaled_width = + std::max(static_cast(std::ceil(static_cast(new_window_width) * dpi_scale)), 1); + const s32 scaled_height = std::max( + static_cast(std::ceil(static_cast(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(), + 1); + + SDL_SetWindowSize(m_window, scaled_width, scaled_height); + + s32 window_width, window_height; + SDL_GetWindowSize(m_window, &window_width, &window_height); + m_display->ResizeRenderWindow(window_width, window_height); + + return true; +} + ALWAYS_INLINE static TinyString GetWindowTitle() { return TinyString::FromFormat("DuckStation %s (%s)", g_scm_tag_str, g_scm_branch_str); } -bool SDLHostInterface::CreateSDLWindow() +bool SDLHostInterface::CreatePlatformWindow() { - static constexpr u32 DEFAULT_WINDOW_WIDTH = 1280; - static constexpr u32 DEFAULT_WINDOW_HEIGHT = 720; - // Create window. const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; @@ -63,7 +143,7 @@ bool SDLHostInterface::CreateSDLWindow() #ifndef __APPLE__ { // scale by default monitor's DPI - float scale = SDLUtil::GetDPIScaleFactor(nullptr); + float scale = GetDPIScaleFactor(nullptr); window_width = static_cast(std::round(static_cast(window_width) * scale)); window_height = static_cast(std::round(static_cast(window_height) * scale)); } @@ -88,179 +168,77 @@ bool SDLHostInterface::CreateSDLWindow() if (m_fullscreen) SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP); + ImGui_ImplSDL2_Init(m_window); + // Process events so that we have everything sorted out before creating a child window for the GL context (X11). SDL_PumpEvents(); return true; } -void SDLHostInterface::DestroySDLWindow() +void SDLHostInterface::DestroyPlatformWindow() { + ImGui_ImplSDL2_Shutdown(); SDL_DestroyWindow(m_window); m_window = nullptr; } -bool SDLHostInterface::CreateDisplay() +std::optional SDLHostInterface::GetPlatformWindowInfo() { - std::optional wi = SDLUtil::GetWindowInfoForSDLWindow(m_window); - if (!wi.has_value()) + SDL_SysWMinfo syswm = {}; + SDL_VERSION(&syswm.version); + if (!SDL_GetWindowWMInfo(m_window, &syswm)) { - ReportError("Failed to get window info from SDL window"); - return false; + Log_ErrorPrintf("SDL_GetWindowWMInfo failed"); + return std::nullopt; } - switch (g_settings.gpu_renderer) + int window_width, window_height; + SDL_GetWindowSize(m_window, &window_width, &window_height); + + WindowInfo wi; + wi.surface_width = static_cast(window_width); + wi.surface_height = static_cast(window_height); + wi.surface_scale = GetDPIScaleFactor(m_window); + wi.surface_format = WindowInfo::SurfaceFormat::RGB8; + + switch (syswm.subsystem) { - case GPURenderer::HardwareVulkan: - m_display = std::make_unique(); - break; - - case GPURenderer::HardwareOpenGL: -#ifndef WIN32 - default: -#endif - m_display = std::make_unique(); - break; - -#ifdef WIN32 - case GPURenderer::HardwareD3D11: - default: - m_display = std::make_unique(); - break; -#endif - } - - Assert(m_display); - if (!m_display->CreateRenderDevice(wi.value(), g_settings.gpu_adapter, g_settings.gpu_use_debug_device, - g_settings.gpu_threaded_presentation) || - !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, - g_settings.gpu_threaded_presentation)) - { - ReportError("Failed to create/initialize display render device"); - m_display.reset(); - return false; - } - - if (!ImGui_ImplSDL2_Init(m_window) || !m_display->CreateImGuiContext()) - { - ReportError("Failed to initialize ImGui SDL2 wrapper"); - ImGui_ImplSDL2_Shutdown(); - m_display->DestroyRenderDevice(); - m_display.reset(); - return false; - } - - if (!FullscreenUI::Initialize(this, m_settings_interface.get()) || !m_display->UpdateImGuiFontTexture()) - { - ReportError("Failed to initialize fonts/fullscreen UI"); - FullscreenUI::Shutdown(); - m_display->DestroyImGuiContext(); - ImGui_ImplSDL2_Shutdown(); - m_display->DestroyRenderDevice(); - m_display.reset(); - return false; - } - - m_fullscreen_ui_enabled = true; - return true; -} - -void SDLHostInterface::DestroyDisplay() -{ - FullscreenUI::Shutdown(); - m_display->DestroyImGuiContext(); - m_display->DestroyRenderDevice(); - m_display.reset(); -} - -void SDLHostInterface::CreateImGuiContext() -{ - const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window); - - ImGui::CreateContext(); - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; - ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale; - ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale; - ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); - - ImGui::StyleColorsDarker(); -} - -void SDLHostInterface::UpdateFramebufferScale() -{ - ImGuiIO& io = ImGui::GetIO(); - const float framebuffer_scale = SDLUtil::GetDPIScaleFactor(m_window); - if (framebuffer_scale != io.DisplayFramebufferScale.x) - { - io.DisplayFramebufferScale = ImVec2(framebuffer_scale, framebuffer_scale); - ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); - } - - if (ImGuiFullscreen::UpdateLayoutScale()) - { - if (ImGuiFullscreen::UpdateFonts()) - { - if (!m_display->UpdateImGuiFontTexture()) - Panic("Failed to update font texture"); - } - } -} - -bool SDLHostInterface::AcquireHostDisplay() -{ - // Handle renderer switch if required. - const HostDisplay::RenderAPI render_api = m_display->GetRenderAPI(); - bool needs_switch = false; - switch (g_settings.gpu_renderer) - { -#ifdef WIN32 - case GPURenderer::HardwareD3D11: - needs_switch = (render_api != HostDisplay::RenderAPI::D3D11); +#ifdef SDL_VIDEO_DRIVER_WINDOWS + case SDL_SYSWM_WINDOWS: + wi.type = WindowInfo::Type::Win32; + wi.window_handle = syswm.info.win.window; break; #endif - case GPURenderer::HardwareVulkan: - needs_switch = (render_api != HostDisplay::RenderAPI::Vulkan); +#ifdef SDL_VIDEO_DRIVER_COCOA + case SDL_SYSWM_COCOA: + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window); break; +#endif - case GPURenderer::HardwareOpenGL: - needs_switch = (render_api != HostDisplay::RenderAPI::OpenGL && render_api != HostDisplay::RenderAPI::OpenGLES); +#ifdef SDL_VIDEO_DRIVER_X11 + case SDL_SYSWM_X11: + wi.type = WindowInfo::Type::X11; + wi.window_handle = reinterpret_cast(static_cast(syswm.info.x11.window)); + wi.display_connection = syswm.info.x11.display; break; +#endif + +#ifdef SDL_VIDEO_DRIVER_WAYLAND + case SDL_SYSWM_WAYLAND: + wi.type = WindowInfo::Type::Wayland; + wi.window_handle = syswm.info.wl.surface; + wi.display_connection = syswm.info.wl.display; + break; +#endif - case GPURenderer::Software: default: - needs_switch = false; - break; + Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem)); + return std::nullopt; } - if (needs_switch) - { - ImGui::EndFrame(); - DestroyDisplay(); - - // We need to recreate the window, otherwise bad things happen... - DestroySDLWindow(); - if (!CreateSDLWindow()) - Panic("Failed to recreate SDL window on GPU renderer switch"); - - if (!CreateDisplay()) - Panic("Failed to recreate display on GPU renderer switch"); - - ImGui::NewFrame(); - } - - if (!CreateHostDisplayResources()) - return false; - - return true; -} - -void SDLHostInterface::ReleaseHostDisplay() -{ - ReleaseHostDisplayResources(); - - // restore vsync, since we don't want to burn cycles at the menu - m_display->SetVSync(true); + return wi; } std::optional SDLHostInterface::GetHostKeyCode(const std::string_view key_code) const @@ -272,197 +250,6 @@ std::optional SDLHostInterface::GetHostKeyCode return static_cast(*code); } -void SDLHostInterface::UpdateInputMap() -{ - CommonHostInterface::UpdateInputMap(*m_settings_interface.get()); -} - -void SDLHostInterface::OnSystemCreated() -{ - CommonHostInterface::OnSystemCreated(); - FullscreenUI::SystemCreated(); -} - -void SDLHostInterface::OnSystemPaused(bool paused) -{ - CommonHostInterface::OnSystemPaused(paused); - FullscreenUI::SystemPaused(paused); -} - -void SDLHostInterface::OnSystemDestroyed() -{ - CommonHostInterface::OnSystemDestroyed(); - ReportFormattedMessage("System shut down."); - FullscreenUI::SystemDestroyed(); -} - -void SDLHostInterface::OnRunningGameChanged() -{ - CommonHostInterface::OnRunningGameChanged(); - - Settings old_settings(std::move(g_settings)); - CommonHostInterface::LoadSettings(*m_settings_interface.get()); - CommonHostInterface::ApplyGameSettings(true); - CommonHostInterface::FixIncompatibleSettings(true); - CheckForSettingsChanges(old_settings); - - if (!System::GetRunningTitle().empty()) - SDL_SetWindowTitle(m_window, System::GetRunningTitle().c_str()); - else - SDL_SetWindowTitle(m_window, GetWindowTitle()); -} - -void SDLHostInterface::RequestExit() -{ - m_quit_request = true; -} - -void SDLHostInterface::RunLater(std::function callback) -{ - SDL_Event ev = {}; - ev.type = SDL_USEREVENT; - ev.user.code = m_run_later_event_id; - ev.user.data1 = new std::function(std::move(callback)); - SDL_PushEvent(&ev); -} - -void SDLHostInterface::ApplySettings(bool display_osd_messages) -{ - Settings old_settings(std::move(g_settings)); - CommonHostInterface::LoadSettings(*m_settings_interface.get()); - CommonHostInterface::ApplyGameSettings(display_osd_messages); - CommonHostInterface::FixIncompatibleSettings(display_osd_messages); - CheckForSettingsChanges(old_settings); -} - -bool SDLHostInterface::IsFullscreen() const -{ - return m_fullscreen; -} - -bool SDLHostInterface::SetFullscreen(bool enabled) -{ - if (m_fullscreen == enabled) - return true; - - SDL_SetWindowFullscreen(m_window, enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - - int window_width, window_height; - SDL_GetWindowSize(m_window, &window_width, &window_height); - m_display->ResizeRenderWindow(window_width, window_height); - - if (!System::IsShutdown()) - g_gpu->UpdateResolutionScale(); - - m_fullscreen = enabled; - return true; -} - -std::unique_ptr SDLHostInterface::Create() -{ - return std::make_unique(); -} - -bool SDLHostInterface::Initialize() -{ - if (!CommonHostInterface::Initialize()) - return false; - - // Change to the user directory so that all default/relative paths in the config are after this. - if (!FileSystem::SetWorkingDirectory(m_user_directory.c_str())) - Log_ErrorPrintf("Failed to set working directory to '%s'", m_user_directory.c_str()); - - if (!CreateSDLWindow()) - { - Log_ErrorPrintf("Failed to create SDL window"); - return false; - } - - CreateImGuiContext(); - if (!CreateDisplay()) - { - Log_ErrorPrintf("Failed to create host display"); - return false; - } - - // process events to pick up controllers before updating input map - ProcessEvents(); - UpdateInputMap(); - return true; -} - -void SDLHostInterface::Shutdown() -{ - DestroySystem(); - - CommonHostInterface::Shutdown(); - - if (m_display) - { - DestroyDisplay(); - ImGui::DestroyContext(); - } - - if (m_window) - DestroySDLWindow(); -} - -std::string SDLHostInterface::GetStringSettingValue(const char* section, const char* key, - const char* default_value /*= ""*/) -{ - return m_settings_interface->GetStringValue(section, key, default_value); -} - -bool SDLHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */) -{ - return m_settings_interface->GetBoolValue(section, key, default_value); -} - -int SDLHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */) -{ - return m_settings_interface->GetIntValue(section, key, default_value); -} - -float SDLHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */) -{ - return m_settings_interface->GetFloatValue(section, key, default_value); -} - -bool SDLHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) -{ - if (new_window_width <= 0 || new_window_height <= 0 || m_fullscreen) - return false; - - // use imgui scale as the dpr - const float dpi_scale = ImGui::GetIO().DisplayFramebufferScale.x; - const s32 scaled_width = - std::max(static_cast(std::ceil(static_cast(new_window_width) * dpi_scale)), 1); - const s32 scaled_height = std::max( - static_cast(std::ceil(static_cast(new_window_height) * dpi_scale)) + m_display->GetDisplayTopMargin(), - 1); - - SDL_SetWindowSize(m_window, scaled_width, scaled_height); - - s32 window_width, window_height; - SDL_GetWindowSize(m_window, &window_width, &window_height); - m_display->ResizeRenderWindow(window_width, window_height); - - UpdateFramebufferScale(); - - if (!System::IsShutdown()) - g_gpu->UpdateResolutionScale(); - - return true; -} - -void SDLHostInterface::LoadSettings() -{ - // Settings need to be loaded prior to creating the window for OpenGL bits. - m_settings_interface = std::make_unique(GetSettingsFileName()); - CommonHostInterface::LoadSettings(*m_settings_interface.get()); - CommonHostInterface::FixIncompatibleSettings(false); -} - void SDLHostInterface::ReportError(const char* message) { const bool was_fullscreen = IsFullscreen(); @@ -514,16 +301,35 @@ bool SDLHostInterface::ConfirmMessage(const char* message) return result; } +void SDLHostInterface::PollAndUpdate() +{ + // Process SDL events before the controller interface can steal them. + const bool is_sdl_controller_interface = + (m_controller_interface && m_controller_interface->GetBackend() == ControllerInterface::Backend::SDL); + + for (;;) + { + SDL_Event ev; + if (!SDL_PollEvent(&ev)) + break; + + if (is_sdl_controller_interface && + static_cast(m_controller_interface.get())->ProcessSDLEvent(&ev)) + { + continue; + } + + HandleSDLEvent(&ev); + } + + ImGui_ImplSDL2_NewFrame(); + CommonHostInterface::PollAndUpdate(); +} + void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) { ImGui_ImplSDL2_ProcessEvent(event); - if (m_controller_interface && - static_cast(m_controller_interface.get())->ProcessSDLEvent(event)) - { - return; - } - switch (event->type) { case SDL_WINDOWEVENT: @@ -531,14 +337,11 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) if (event->window.event == SDL_WINDOWEVENT_RESIZED) { m_display->ResizeRenderWindow(event->window.data1, event->window.data2); - UpdateFramebufferScale(); - - if (!System::IsShutdown()) - g_gpu->UpdateResolutionScale(); + OnPlatformWindowResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window)); } else if (event->window.event == SDL_WINDOWEVENT_MOVED) { - UpdateFramebufferScale(); + // TODO: Do we want to update DPI scale here? } } break; @@ -576,86 +379,5 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) } } break; - - case SDL_USEREVENT: - { - if (static_cast(event->user.code) == m_run_later_event_id) - { - std::function* callback = static_cast*>(event->user.data1); - Assert(callback); - (*callback)(); - delete callback; - } - } - break; - } -} - -void SDLHostInterface::PollAndUpdate() -{ - ProcessEvents(); - CommonHostInterface::PollAndUpdate(); -} - -void SDLHostInterface::ProcessEvents() -{ - for (;;) - { - SDL_Event ev; - if (SDL_PollEvent(&ev)) - HandleSDLEvent(&ev); - else - break; - } -} - -void SDLHostInterface::Run() -{ - while (!m_quit_request) - { - PollAndUpdate(); - - if (System::IsRunning()) - { - if (m_display_all_frames) - System::RunFrame(); - else - System::RunFrames(); - - UpdateControllerRumble(); - if (m_frame_step_request) - { - m_frame_step_request = false; - PauseSystem(true); - } - } - - // rendering - { - ImGui_ImplSDL2_NewFrame(); - FullscreenUI::SetImGuiNavInputs(); - ImGui::NewFrame(); - DrawImGuiWindows(); - ImGui::Render(); - ImGui::EndFrame(); - - m_display->Render(); - - if (System::IsRunning()) - { - System::UpdatePerformanceCounters(); - - if (m_throttler_enabled) - System::Throttle(); - } - } - } - - // Save state on exit so it can be resumed - if (!System::IsShutdown()) - { - if (g_settings.save_state_on_exit) - SaveResumeSaveState(); - DestroySystem(); } } diff --git a/src/duckstation-nogui/sdl_host_interface.h b/src/duckstation-nogui/sdl_host_interface.h index cb3d81727..2954a6075 100644 --- a/src/duckstation-nogui/sdl_host_interface.h +++ b/src/duckstation-nogui/sdl_host_interface.h @@ -1,24 +1,8 @@ #pragma once -#include "common/gl/program.h" -#include "common/gl/texture.h" -#include "core/host_display.h" -#include "core/host_interface.h" -#include "frontend-common/common_host_interface.h" +#include "nogui_host_interface.h" #include -#include -#include -#include -#include -#include -#include -class AudioStream; - -class INISettingsInterface; - -struct GameListEntry; - -class SDLHostInterface final : public CommonHostInterface +class SDLHostInterface final : public NoGUIHostInterface { public: SDLHostInterface(); @@ -35,53 +19,23 @@ public: bool Initialize() override; void Shutdown() override; - std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override; - bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override; - int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override; - float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; - bool RequestRenderWindowSize(s32 new_window_width, s32 new_window_height) override; bool IsFullscreen() const override; bool SetFullscreen(bool enabled) override; - void RunLater(std::function callback) override; - void ApplySettings(bool display_osd_messages) override; - - void Run(); - protected: - void LoadSettings() override; - - bool AcquireHostDisplay() override; - void ReleaseHostDisplay() override; - - void OnSystemCreated() override; - void OnSystemPaused(bool paused) override; - void OnSystemDestroyed() override; - void OnRunningGameChanged() override; - - void RequestExit() override; void PollAndUpdate() override; std::optional GetHostKeyCode(const std::string_view key_code) const override; - void UpdateInputMap() override; + + bool CreatePlatformWindow() override; + void DestroyPlatformWindow() override; + std::optional GetPlatformWindowInfo() override; private: - bool CreateSDLWindow(); - void DestroySDLWindow(); - bool CreateDisplay(); - void DestroyDisplay(); - void CreateImGuiContext(); - void UpdateFramebufferScale(); - void HandleSDLEvent(const SDL_Event* event); - void ProcessEvents(); SDL_Window* m_window = nullptr; - std::unique_ptr m_settings_interface; - u32 m_run_later_event_id = 0; - bool m_fullscreen = false; - bool m_quit_request = false; }; diff --git a/src/duckstation-nogui/sdl_util.cpp b/src/duckstation-nogui/sdl_util.cpp deleted file mode 100644 index 318afab3c..000000000 --- a/src/duckstation-nogui/sdl_util.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "sdl_util.h" -#include "common/log.h" -#include -Log_SetChannel(SDLUtil); - -#ifdef __APPLE__ -#include -struct NSView; - -static NSView* GetContentViewFromWindow(NSWindow* window) -{ - // window.contentView - return reinterpret_cast(objc_msgSend)(reinterpret_cast(window), sel_getUid("contentView")); -} -#endif - -namespace SDLUtil { - -std::optional GetWindowInfoForSDLWindow(SDL_Window* window) -{ - SDL_SysWMinfo syswm = {}; - SDL_VERSION(&syswm.version); - if (!SDL_GetWindowWMInfo(window, &syswm)) - { - Log_ErrorPrintf("SDL_GetWindowWMInfo failed"); - return std::nullopt; - } - - int window_width, window_height; - SDL_GetWindowSize(window, &window_width, &window_height); - - WindowInfo wi; - wi.surface_width = static_cast(window_width); - wi.surface_height = static_cast(window_height); - wi.surface_scale = GetDPIScaleFactor(window); - wi.surface_format = WindowInfo::SurfaceFormat::RGB8; - - switch (syswm.subsystem) - { -#ifdef SDL_VIDEO_DRIVER_WINDOWS - case SDL_SYSWM_WINDOWS: - wi.type = WindowInfo::Type::Win32; - wi.window_handle = syswm.info.win.window; - break; -#endif - -#ifdef SDL_VIDEO_DRIVER_COCOA - case SDL_SYSWM_COCOA: - wi.type = WindowInfo::Type::MacOS; - wi.window_handle = GetContentViewFromWindow(syswm.info.cocoa.window); - break; -#endif - -#ifdef SDL_VIDEO_DRIVER_X11 - case SDL_SYSWM_X11: - wi.type = WindowInfo::Type::X11; - wi.window_handle = reinterpret_cast(static_cast(syswm.info.x11.window)); - wi.display_connection = syswm.info.x11.display; - break; -#endif - -#ifdef SDL_VIDEO_DRIVER_WAYLAND - case SDL_SYSWM_WAYLAND: - wi.type = WindowInfo::Type::Wayland; - wi.window_handle = syswm.info.wl.surface; - wi.display_connection = syswm.info.wl.display; - break; -#endif - - default: - Log_ErrorPrintf("Unhandled syswm subsystem %u", static_cast(syswm.subsystem)); - return std::nullopt; - } - - return wi; -} - -float GetDPIScaleFactor(SDL_Window* window) -{ -#ifdef __APPLE__ - static constexpr float DEFAULT_DPI = 72.0f; -#else - static constexpr float DEFAULT_DPI = 96.0f; -#endif - - if (!window) - { - SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1, - SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); - if (!dummy_window) - return 1.0f; - - const float scale = GetDPIScaleFactor(dummy_window); - - SDL_DestroyWindow(dummy_window); - - return scale; - } - - int display_index = SDL_GetWindowDisplayIndex(window); - float display_dpi = DEFAULT_DPI; - if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0) - return 1.0f; - - return display_dpi / DEFAULT_DPI; -} -} // namespace SDLUtil \ No newline at end of file diff --git a/src/duckstation-nogui/sdl_util.h b/src/duckstation-nogui/sdl_util.h deleted file mode 100644 index 685df5e25..000000000 --- a/src/duckstation-nogui/sdl_util.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "common/types.h" -#include "common/window_info.h" -#include - -struct SDL_Window; - -namespace SDLUtil { -std::optional GetWindowInfoForSDLWindow(SDL_Window* window); -float GetDPIScaleFactor(SDL_Window* window); -} \ No newline at end of file diff --git a/src/duckstation-nogui/win32_host_interface.cpp b/src/duckstation-nogui/win32_host_interface.cpp new file mode 100644 index 000000000..30fd41f75 --- /dev/null +++ b/src/duckstation-nogui/win32_host_interface.cpp @@ -0,0 +1,150 @@ +#include "win32_host_interface.h" +#include "common/log.h" +#include "common/string_util.h" +#include "resource.h" +#include +Log_SetChannel(Win32HostInterface); + +static constexpr _TCHAR WINDOW_CLASS_NAME[] = _T("DuckStationNoGUI"); + +Win32HostInterface::Win32HostInterface() = default; + +Win32HostInterface::~Win32HostInterface() = default; + +std::unique_ptr Win32HostInterface::Create() +{ + return std::make_unique(); +} + +bool Win32HostInterface::Initialize() +{ + if (!RegisterWindowClass()) + return false; + + if (!NoGUIHostInterface::Initialize()) + return false; + + return true; +} + +void Win32HostInterface::Shutdown() +{ + NoGUIHostInterface::Shutdown(); +} + +bool Win32HostInterface::RegisterWindowClass() +{ + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = 0; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = LoadIconA(NULL, (LPCSTR)IDI_ICON1); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = WINDOW_CLASS_NAME; + wc.hIconSm = LoadIconA(NULL, (LPCSTR)IDI_ICON1); + + if (!RegisterClassExW(&wc)) + { + MessageBoxA(nullptr, "Window registration failed.", "Error", MB_ICONERROR | MB_OK); + return false; + } + + return true; +} + +bool Win32HostInterface::CreatePlatformWindow() +{ + m_hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, _T("DuckStation"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT, nullptr, nullptr, + GetModuleHandleA(nullptr), this); + if (!m_hwnd) + { + MessageBoxA(nullptr, "CreateWindowEx failed.", "Error", MB_ICONERROR | MB_OK); + return false; + } + + ShowWindow(m_hwnd, SW_SHOW); + UpdateWindow(m_hwnd); + ProcessWin32Events(); + + return true; +} + +void Win32HostInterface::DestroyPlatformWindow() +{ + if (m_hwnd) + { + DestroyWindow(m_hwnd); + m_hwnd = {}; + } +} + +std::optional Win32HostInterface::GetPlatformWindowInfo() +{ + RECT rc = {}; + GetClientRect(m_hwnd, &rc); + + WindowInfo wi; + wi.window_handle = static_cast(m_hwnd); + wi.type = WindowInfo::Type::Win32; + wi.surface_width = static_cast(rc.right - rc.left); + wi.surface_height = static_cast(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(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(width), static_cast(height)); + } + break; + + case WM_CLOSE: + hi->m_quit_request = true; + break; + + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + return 0; +} + +std::optional Win32HostInterface::GetHostKeyCode(const std::string_view key_code) const +{ + std::optional kc; // = EvDevKeyNames::GetKeyCodeForName(key_code); + if (!kc.has_value()) + return std::nullopt; + + return static_cast(kc.value()); +} diff --git a/src/duckstation-nogui/win32_host_interface.h b/src/duckstation-nogui/win32_host_interface.h new file mode 100644 index 000000000..c25a067ec --- /dev/null +++ b/src/duckstation-nogui/win32_host_interface.h @@ -0,0 +1,34 @@ +#pragma once +#include "common/windows_headers.h" +#include "nogui_host_interface.h" +#include +#include + +class Win32HostInterface final : public NoGUIHostInterface +{ +public: + Win32HostInterface(); + ~Win32HostInterface(); + + bool Initialize(); + void Shutdown(); + + static std::unique_ptr Create(); + +protected: + bool CreatePlatformWindow() override; + void DestroyPlatformWindow() override; + std::optional GetPlatformWindowInfo() override; + + std::optional 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{}; +};