aboutsummaryrefslogtreecommitdiff
path: root/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu_cmd/emu_window/emu_window_sdl3.cpp')
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl3.cpp269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp
new file mode 100644
index 0000000000..cd390d29f9
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp
@@ -0,0 +1,269 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <SDL3/SDL.h>
+
+#include "common/logging.h"
+#include "common/scm_rev.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/perf_stats.h"
+#include "hid_core/hid_core.h"
+#include "input_common/drivers/keyboard.h"
+#include "input_common/drivers/mouse.h"
+#include "input_common/drivers/touch_screen.h"
+#include "input_common/main.h"
+#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
+#include "yuzu_cmd/yuzu_icon.h"
+
+EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
+ : input_subsystem{input_subsystem_}, system{system_} {
+ input_subsystem->Initialize();
+ if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
+ LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
+ exit(1);
+ }
+}
+
+EmuWindow_SDL3::~EmuWindow_SDL3() {
+ system.HIDCore().UnloadInputDevices();
+ input_subsystem->Shutdown();
+ SDL_Quit();
+}
+
+InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const {
+ switch (button) {
+ case SDL_BUTTON_LEFT:
+ return InputCommon::MouseButton::Left;
+ case SDL_BUTTON_RIGHT:
+ return InputCommon::MouseButton::Right;
+ case SDL_BUTTON_MIDDLE:
+ return InputCommon::MouseButton::Wheel;
+ case SDL_BUTTON_X1:
+ return InputCommon::MouseButton::Backward;
+ case SDL_BUTTON_X2:
+ return InputCommon::MouseButton::Forward;
+ default:
+ return InputCommon::MouseButton::Undefined;
+ }
+}
+
+/// @brief Translates pixel position to float position
+EmuWindow_SDL3::FloatPairNonHFA EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const {
+ int w = 0, h = 0;
+ SDL_GetWindowSize(render_window, &w, &h);
+ const float fx = float(touch_x) / w;
+ const float fy = float(touch_y) / h;
+ return {
+ std::clamp<float>(fx, 0.0f, 1.0f),
+ std::clamp<float>(fy, 0.0f, 1.0f),
+ 0
+ };
+}
+
+void EmuWindow_SDL3::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
+ const auto mouse_button = SDLButtonToMouseButton(button);
+ if (state != 0) {
+ auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
+ input_subsystem->GetMouse()->PressButton(x, y, mouse_button);
+ input_subsystem->GetMouse()->PressMouseButton(mouse_button);
+ input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, mouse_button);
+ } else {
+ input_subsystem->GetMouse()->ReleaseButton(mouse_button);
+ }
+}
+
+void EmuWindow_SDL3::OnMouseMotion(s32 x, s32 y) {
+ auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y);
+ input_subsystem->GetMouse()->Move(x, y, 0, 0);
+ input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
+ input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
+}
+
+void EmuWindow_SDL3::OnFingerDown(float x, float y, std::size_t id) {
+ input_subsystem->GetTouchScreen()->TouchPressed(x, y, id);
+}
+
+void EmuWindow_SDL3::OnFingerMotion(float x, float y, std::size_t id) {
+ input_subsystem->GetTouchScreen()->TouchMoved(x, y, id);
+}
+
+void EmuWindow_SDL3::OnFingerUp() {
+ input_subsystem->GetTouchScreen()->ReleaseAllTouch();
+}
+
+void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) {
+ if (state != 0) {
+ input_subsystem->GetKeyboard()->PressKey(static_cast<std::size_t>(key));
+ } else {
+ input_subsystem->GetKeyboard()->ReleaseKey(static_cast<std::size_t>(key));
+ }
+}
+
+bool EmuWindow_SDL3::IsOpen() const {
+ return is_open;
+}
+
+bool EmuWindow_SDL3::IsShown() const {
+ return is_shown;
+}
+
+void EmuWindow_SDL3::OnResize() {
+ int width, height;
+ SDL_GetWindowSizeInPixels(render_window, &width, &height);
+ UpdateCurrentFramebufferLayout(width, height);
+}
+
+void EmuWindow_SDL3::ShowCursor(bool show_cursor) {
+ if (show_cursor) {
+ SDL_ShowCursor();
+ } else {
+ SDL_HideCursor();
+ }
+}
+
+void EmuWindow_SDL3::Fullscreen() {
+ SDL_DisplayMode display_mode;
+ switch (Settings::values.fullscreen_mode.GetValue()) {
+ case Settings::FullscreenMode::Exclusive:
+ // Set window size to render size before entering fullscreen in exclusive mode.
+ if (const SDL_DisplayMode* display_mode_ptr =
+ SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
+ display_mode = *display_mode_ptr;
+ SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
+ SDL_SetWindowFullscreenMode(render_window, &display_mode);
+ } else {
+ LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError());
+ }
+
+ if (SDL_SetWindowFullscreen(render_window, true)) {
+ return;
+ }
+
+ LOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError());
+ LOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
+ [[fallthrough]];
+ case Settings::FullscreenMode::Borderless:
+ SDL_SetWindowFullscreenMode(render_window, nullptr);
+ if (SDL_SetWindowFullscreen(render_window, true)) {
+ return;
+ }
+
+ LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
+ [[fallthrough]];
+ default:
+ // Fallback algorithm: Maximise window.
+ // Works on all systems (unless something is seriously wrong), so no fallback for this one.
+ LOG_INFO(Frontend, "Falling back on a maximised window...");
+ SDL_MaximizeWindow(render_window);
+ break;
+ }
+}
+
+void EmuWindow_SDL3::WaitEvent() {
+ // Called on main thread
+ SDL_Event event;
+
+ if (!SDL_WaitEvent(&event)) {
+ const char* error = SDL_GetError();
+ if (!error || strcmp(error, "") == 0) {
+ // https://github.com/libsdl-org/SDL/issues/5780
+ // Sometimes SDL will return without actually having hit an error condition;
+ // just ignore it in this case.
+ return;
+ }
+
+ LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
+ exit(1);
+ }
+
+ switch (event.type) {
+ case SDL_EVENT_WINDOW_RESIZED:
+ case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+ case SDL_EVENT_WINDOW_MAXIMIZED:
+ case SDL_EVENT_WINDOW_RESTORED:
+ OnResize();
+ break;
+ case SDL_EVENT_WINDOW_MINIMIZED:
+ is_shown = false;
+ OnResize();
+ break;
+ case SDL_EVENT_WINDOW_EXPOSED:
+ is_shown = true;
+ OnResize();
+ break;
+ case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ is_open = false;
+ break;
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP:
+ OnKeyEvent(static_cast<int>(event.key.scancode), event.key.down ? 1 : 0);
+ break;
+ case SDL_EVENT_MOUSE_MOTION:
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID)
+ OnMouseMotion(event.motion.x, event.motion.y);
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID) {
+ OnMouseButton(event.button.button, event.button.down ? 1 : 0,
+ static_cast<s32>(event.button.x), static_cast<s32>(event.button.y));
+ }
+ break;
+ case SDL_EVENT_FINGER_DOWN:
+ OnFingerDown(event.tfinger.x, event.tfinger.y,
+ static_cast<std::size_t>(event.tfinger.touchID));
+ break;
+ case SDL_EVENT_FINGER_MOTION:
+ OnFingerMotion(event.tfinger.x, event.tfinger.y,
+ static_cast<std::size_t>(event.tfinger.touchID));
+ break;
+ case SDL_EVENT_FINGER_UP:
+ OnFingerUp();
+ break;
+ case SDL_EVENT_QUIT:
+ is_open = false;
+ break;
+ default:
+ break;
+ }
+
+ const u32 current_time = SDL_GetTicks();
+ if (current_time > last_time + 2000) {
+ const auto results = system.GetAndResetPerfStats();
+ const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)",
+ Common::g_build_fullname,
+ Common::g_scm_branch,
+ Common::g_scm_desc,
+ results.average_game_fps,
+ results.emulation_speed * 100.0);
+ SDL_SetWindowTitle(render_window, title.c_str());
+ last_time = current_time;
+ }
+}
+
+// Credits to Samantas5855 and others for this function.
+void EmuWindow_SDL3::SetWindowIcon() {
+ SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
+ if (yuzu_icon_stream == nullptr) {
+ LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
+ return;
+ }
+ SDL_Surface* const window_icon = SDL_LoadBMP_IO(yuzu_icon_stream, true);
+ if (window_icon == nullptr) {
+ LOG_WARNING(Frontend, "Failed to read BMP from stream.");
+ return;
+ }
+ // The icon is attached to the window pointer
+ SDL_SetWindowIcon(render_window, window_icon);
+ SDL_DestroySurface(window_icon);
+}
+
+void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
+ SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
+}