// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) // This file is part of the "Nazara Engine - Platform module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include #include #include #include #include #include #include #include #ifdef NAZARA_PLATFORM_MACOS // I'm not sure why, but SDL_VIDEO_DRIVER_X11 is automatically defined here by SDL_config.h // This is problematic as it requires X11/X.h which is not present (adding libxext/libx11/xorgproto packages didn't help) #undef SDL_VIDEO_DRIVER_X11 #endif #include #include #include #include #include namespace Nz { namespace { Mouse::Button SDLToNazaraButton(Uint8 sdlButton) { switch (sdlButton) { case SDL_BUTTON_LEFT: return Mouse::Left; case SDL_BUTTON_MIDDLE: return Mouse::Middle; case SDL_BUTTON_RIGHT: return Mouse::Right; case SDL_BUTTON_X1: return Mouse::XButton1; case SDL_BUTTON_X2: return Mouse::XButton2; default: NazaraAssert(false, "Unkown mouse button"); return Mouse::Left; } } } WindowImpl::WindowImpl(Window* parent) : m_cursor(nullptr), m_handle(nullptr), m_parent(parent), m_eventListener(false), m_ignoreNextMouseMove(false), m_lastEditEventLength(0) { m_cursor = SDL_GetDefaultCursor(); } bool WindowImpl::Create(const VideoMode& mode, const std::string& title, WindowStyleFlags style) { bool fullscreen = (style & WindowStyle::Fullscreen) != 0; Uint32 winStyle = 0; unsigned int x, y; unsigned int width = mode.width; unsigned int height = mode.height; if (fullscreen) winStyle |= SDL_WINDOW_FULLSCREEN; if (fullscreen) { x = 0; y = 0; } else { if (!(style & WindowStyle::Titlebar)) winStyle |= SDL_WINDOW_BORDERLESS; x = SDL_WINDOWPOS_CENTERED; y = SDL_WINDOWPOS_CENTERED; } if (style & WindowStyle::Resizable) winStyle |= SDL_WINDOW_RESIZABLE; m_ownsWindow = true; m_handle = SDL_CreateWindow(title.c_str(), x, y, width, height, winStyle); if (!m_handle) { NazaraError("Failed to create window: " + Error::GetLastSystemError()); return false; } m_windowId = SDL_GetWindowID(m_handle); SetEventListener(true); return true; } bool WindowImpl::Create(WindowHandle handle) { void* systemHandle = nullptr; switch (handle.type) { case WindowBackend::Invalid: { NazaraError("unsupported creation from a Wayland handle"); return false; } case WindowBackend::Wayland: { NazaraError("unsupported creation from a Wayland handle"); return false; } case WindowBackend::Cocoa: systemHandle = handle.cocoa.window; break; case WindowBackend::X11: systemHandle = (void*) handle.x11.window; break; case WindowBackend::Windows: systemHandle = handle.windows.window; break; } m_ownsWindow = false; m_handle = SDL_CreateWindowFrom(systemHandle); if (!m_handle) { NazaraError("Invalid handle"); return false; } m_windowId = SDL_GetWindowID(m_handle); SetEventListener(true); return true; } void WindowImpl::Destroy() { SetEventListener(false); if (m_handle) { if (m_ownsWindow) SDL_DestroyWindow(m_handle); m_handle = nullptr; } } Vector2i WindowImpl::FetchPosition() const { int x, y; SDL_GetWindowPosition(m_handle, &x, &y); return { x, y }; } Vector2ui WindowImpl::FetchSize() const { int width, height; SDL_GetWindowSize(m_handle, &width, &height); return { SafeCast(width), SafeCast(height) }; } WindowStyleFlags WindowImpl::FetchStyle() const { UInt32 windowFlags = SDL_GetWindowFlags(m_handle); WindowStyleFlags styleFlags; if (windowFlags & SDL_WINDOW_RESIZABLE) styleFlags |= WindowStyle::Resizable; if ((windowFlags & SDL_WINDOW_BORDERLESS) == 0) styleFlags |= WindowStyle::Titlebar | WindowStyle::Closable; if (windowFlags & SDL_WINDOW_FULLSCREEN) styleFlags |= WindowStyle::Fullscreen; return styleFlags; } std::string WindowImpl::FetchTitle() const { return SDL_GetWindowTitle(m_handle); } SDL_Window* WindowImpl::GetHandle() const { return m_handle; } WindowHandle WindowImpl::GetSystemHandle() const { #ifdef NAZARA_PLATFORM_WEB WindowHandle handle; handle.type = WindowBackend::Web; return handle; #else SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); if (SDL_GetWindowWMInfo(m_handle, &wmInfo) != SDL_TRUE) { #ifndef NAZARA_PLATFORM_WEB ErrorFlags flags(ErrorMode::ThrowException, true); NazaraError(std::string("failed to retrieve window manager info: ") + SDL_GetError()); #endif } WindowHandle handle; switch (wmInfo.subsystem) { #if defined(SDL_VIDEO_DRIVER_COCOA) case SDL_SYSWM_COCOA: { handle.type = WindowBackend::Cocoa; handle.cocoa.window = wmInfo.info.cocoa.window; break; } #endif #if defined(SDL_VIDEO_DRIVER_X11) case SDL_SYSWM_X11: { handle.type = WindowBackend::X11; handle.x11.display = wmInfo.info.x11.display; handle.x11.window = wmInfo.info.x11.window; break; } #endif #if defined(SDL_VIDEO_DRIVER_WAYLAND) case SDL_SYSWM_WAYLAND: { handle.type = WindowBackend::Wayland; handle.wayland.display = wmInfo.info.wl.display; handle.wayland.surface = wmInfo.info.wl.surface; handle.wayland.shellSurface = wmInfo.info.wl.shell_surface; break; } #endif #if defined(SDL_VIDEO_DRIVER_WINDOWS) case SDL_SYSWM_WINDOWS: { handle.type = WindowBackend::Windows; handle.windows.window = wmInfo.info.win.window; break; } #endif default: { #if defined(NAZARA_PLATFORM_WEB) handle.type = WindowBackend::Web; #else ErrorFlags flags(ErrorMode::ThrowException, true); NazaraError("unhandled window subsystem"); #endif } } return handle; #endif } bool WindowImpl::HasFocus() const { return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_INPUT_FOCUS) != 0; } void WindowImpl::IgnoreNextMouseEvent(int /*mouseX*/, int /*mouseY*/) { m_ignoreNextMouseMove = true; } bool WindowImpl::IsMinimized() const { return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_MINIMIZED) != 0; } bool WindowImpl::IsVisible() const { return (SDL_GetWindowFlags(m_handle) & SDL_WINDOW_SHOWN) != 0; } void WindowImpl::RefreshCursor() { if (!m_cursor) { if (SDL_ShowCursor(SDL_DISABLE) < 0) NazaraWarning(SDL_GetError()); } else { if (SDL_ShowCursor(SDL_ENABLE) < 0) NazaraWarning(SDL_GetError()); SDL_SetCursor(m_cursor); } } int WindowImpl::HandleEvent(void* userdata, SDL_Event* event) { try { WindowImpl* window = static_cast(userdata); switch (event->type) { case SDL_WINDOWEVENT: { if (window->m_windowId != event->window.windowID) return 0; WindowEvent windowEvent; switch (event->window.event) { case SDL_WINDOWEVENT_CLOSE: windowEvent.type = WindowEventType::Quit; break; case SDL_WINDOWEVENT_SIZE_CHANGED: windowEvent.type = WindowEventType::Resized; windowEvent.size.width = static_cast(std::max(0, event->window.data1)); windowEvent.size.height = static_cast(std::max(0, event->window.data2)); break; case SDL_WINDOWEVENT_MOVED: windowEvent.type = WindowEventType::Moved; windowEvent.position.x = event->window.data1; windowEvent.position.y = event->window.data2; break; case SDL_WINDOWEVENT_FOCUS_GAINED: windowEvent.type = WindowEventType::GainedFocus; window->RefreshCursor(); break; case SDL_WINDOWEVENT_FOCUS_LOST: windowEvent.type = WindowEventType::LostFocus; break; case SDL_WINDOWEVENT_ENTER: windowEvent.type = WindowEventType::MouseEntered; window->RefreshCursor(); break; case SDL_WINDOWEVENT_LEAVE: windowEvent.type = WindowEventType::MouseLeft; break; case SDL_WINDOWEVENT_MINIMIZED: windowEvent.type = WindowEventType::Minimized; break; case SDL_WINDOWEVENT_RESTORED: windowEvent.type = WindowEventType::Restored; break; default: return 0; } window->m_parent->HandleEvent(windowEvent); break; } case SDL_MOUSEMOTION: { if (window->m_windowId != event->motion.windowID) return 0; if (window->m_ignoreNextMouseMove) { window->m_ignoreNextMouseMove = false; return 0; } WindowEvent windowEvent; windowEvent.type = WindowEventType::MouseMoved; windowEvent.mouseMove.x = event->motion.x; windowEvent.mouseMove.y = event->motion.y; windowEvent.mouseMove.deltaX = event->motion.xrel; windowEvent.mouseMove.deltaY = event->motion.yrel; window->m_parent->HandleEvent(windowEvent); break; } case SDL_MOUSEBUTTONDOWN: { if (window->m_windowId != event->button.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::MouseButtonPressed; windowEvent.mouseButton.button = SDLToNazaraButton(event->button.button); windowEvent.mouseButton.x = event->button.x; windowEvent.mouseButton.y = event->button.y; windowEvent.mouseButton.clickCount = event->button.clicks; window->m_parent->HandleEvent(windowEvent); break; } case SDL_MOUSEBUTTONUP: { if (window->m_windowId != event->button.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::MouseButtonReleased; windowEvent.mouseButton.button = SDLToNazaraButton(event->button.button); windowEvent.mouseButton.x = event->button.x; windowEvent.mouseButton.y = event->button.y; window->m_parent->HandleEvent(windowEvent); break; } case SDL_MOUSEWHEEL: { if (window->m_windowId != event->wheel.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::MouseWheelMoved; windowEvent.mouseWheel.delta = event->wheel.preciseY; windowEvent.mouseWheel.x = event->wheel.mouseX; windowEvent.mouseWheel.y = event->wheel.mouseY; window->m_parent->HandleEvent(windowEvent); break; } case SDL_KEYDOWN: { if (window->m_windowId != event->key.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::KeyPressed; windowEvent.key.alt = (event->key.keysym.mod & KMOD_ALT) != 0; windowEvent.key.control = (event->key.keysym.mod & KMOD_CTRL) != 0; windowEvent.key.repeated = event->key.repeat != 0; windowEvent.key.scancode = SDLHelper::FromSDL(event->key.keysym.scancode); windowEvent.key.shift = (event->key.keysym.mod & KMOD_SHIFT) != 0; windowEvent.key.system = (event->key.keysym.mod & KMOD_GUI) != 0; windowEvent.key.virtualKey = SDLHelper::FromSDL(event->key.keysym.sym); window->m_parent->HandleEvent(windowEvent); // implements X11/Win32 APIs behavior for Enter and Backspace switch (windowEvent.key.virtualKey) { case Nz::Keyboard::VKey::NumpadReturn: case Nz::Keyboard::VKey::Return: { if (window->m_lastEditEventLength != 0) break; windowEvent.type = WindowEventType::TextEntered; windowEvent.text.character = U'\n'; windowEvent.text.repeated = event->key.repeat != 0; window->m_parent->HandleEvent(windowEvent); break; } case Nz::Keyboard::VKey::Backspace: windowEvent.type = WindowEventType::TextEntered; windowEvent.text.character = U'\b'; windowEvent.text.repeated = event->key.repeat != 0; window->m_parent->HandleEvent(windowEvent); break; default: break; } break; } case SDL_KEYUP: { if (window->m_windowId != event->key.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::KeyReleased; windowEvent.key.alt = (event->key.keysym.mod & KMOD_ALT) != 0; windowEvent.key.control = (event->key.keysym.mod & KMOD_CTRL) != 0; windowEvent.key.repeated = event->key.repeat != 0; windowEvent.key.scancode = SDLHelper::FromSDL(event->key.keysym.scancode); windowEvent.key.shift = (event->key.keysym.mod & KMOD_SHIFT) != 0; windowEvent.key.system = (event->key.keysym.mod & KMOD_GUI) != 0; windowEvent.key.virtualKey = SDLHelper::FromSDL(event->key.keysym.sym); window->m_parent->HandleEvent(windowEvent); break; } case SDL_TEXTINPUT: { if (window->m_windowId != event->text.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::TextEntered; windowEvent.text.repeated = false; utf8::unchecked::iterator it(event->text.text); do { windowEvent.text.character = *it; window->m_parent->HandleEvent(windowEvent); } while (*it++); break; } case SDL_TEXTEDITING: { if (window->m_windowId != event->edit.windowID) return 0; WindowEvent windowEvent; windowEvent.type = WindowEventType::TextEdited; windowEvent.edit.length = event->edit.length; window->m_lastEditEventLength = windowEvent.edit.length; for (std::size_t i = 0; i < 32; i++) { windowEvent.edit.text[i] = event->edit.text[i]; } window->m_parent->HandleEvent(windowEvent); break; } } } catch (const std::exception& e) { NazaraError(e.what()); } catch (...) // Don't let any exceptions go through C calls { NazaraError("An unknown error happened"); } return 0; } void WindowImpl::UpdateCursor(const Cursor& cursor) { m_cursor = cursor.m_impl->GetCursor(); if (HasFocus()) RefreshCursor(); } void WindowImpl::RaiseFocus() { SDL_RaiseWindow(m_handle); } void WindowImpl::UpdateIcon(const Icon& icon) { SDL_SetWindowIcon(m_handle, icon.m_impl->GetIcon()); } void WindowImpl::UpdateMaximumSize(int width, int height) { SDL_SetWindowMaximumSize(m_handle, width, height); } void WindowImpl::UpdateMinimumSize(int width, int height) { SDL_SetWindowMinimumSize(m_handle, width, height); } void WindowImpl::UpdatePosition(int x, int y) { SDL_SetWindowPosition(m_handle, x, y); } void WindowImpl::UpdateSize(unsigned int width, unsigned int height) { SDL_SetWindowSize(m_handle, width, height); } void WindowImpl::UpdateStayOnTop(bool stayOnTop) { SDL_SetWindowAlwaysOnTop(m_handle, (stayOnTop) ? SDL_TRUE : SDL_FALSE); } void WindowImpl::UpdateTitle(const std::string& title) { SDL_SetWindowTitle(m_handle, title.c_str()); } void WindowImpl::Show(bool visible) { if (visible) SDL_ShowWindow(m_handle); else SDL_HideWindow(m_handle); } void WindowImpl::ProcessEvents() { SDL_PumpEvents(); } bool WindowImpl::Initialize() { if (SDL_Init(SDL_INIT_VIDEO) < 0) { NazaraError(SDL_GetError()); return false; } return true; } void WindowImpl::Uninitialize() { SDL_Quit(); } void WindowImpl::SetEventListener(bool listener) { if (m_eventListener == listener) return; if (listener) SDL_AddEventWatch(HandleEvent, this); else SDL_DelEventWatch(HandleEvent, this); m_eventListener = listener; } } #if defined(NAZARA_PLATFORM_WINDOWS) #include #elif defined(NAZARA_PLATFORM_LINUX) #include #endif