diff --git a/.gitignore b/.gitignore index 77605cc..79a760c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ diff --git a/vendor/SFML/src/SFML/System/Win32/SleepImpl.cpp b/vendor/SFML/src/SFML/System/Win32/SleepImpl.cpp new file mode 100644 index 0000000..5cd85c1 --- /dev/null +++ b/vendor/SFML/src/SFML/System/Win32/SleepImpl.cpp @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +#include + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +void sleepImpl(Time time) +{ + // Get the minimum supported timer resolution on this system + static const UINT periodMin = [] + { + TIMECAPS tc; + timeGetDevCaps(&tc, sizeof(TIMECAPS)); + return tc.wPeriodMin; + }(); + + // Set the timer resolution to the minimum for the Sleep call + timeBeginPeriod(periodMin); + + // Wait... + ::Sleep(static_cast(time.asMilliseconds())); + + // Reset the timer resolution back to the system default + timeEndPeriod(periodMin); +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/System/Win32/SleepImpl.hpp b/vendor/SFML/src/SFML/System/Win32/SleepImpl.hpp new file mode 100644 index 0000000..e232c0d --- /dev/null +++ b/vendor/SFML/src/SFML/System/Win32/SleepImpl.hpp @@ -0,0 +1,48 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +namespace sf +{ +class Time; +} + +namespace sf::priv +{ + +//////////////////////////////////////////////////////////// +/// \brief Windows implementation of sf::Sleep +/// +/// \param time Time to sleep +/// +//////////////////////////////////////////////////////////// +void sleepImpl(Time time); + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/System/Win32/WindowsHeader.hpp b/vendor/SFML/src/SFML/System/Win32/WindowsHeader.hpp new file mode 100644 index 0000000..0e6c5dd --- /dev/null +++ b/vendor/SFML/src/SFML/System/Win32/WindowsHeader.hpp @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef _WIN32_WINDOWS +#define _WIN32_WINDOWS 0x0501 // NOLINT(bugprone-reserved-identifier) +#endif + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#ifndef WINVER +#define WINVER 0x0501 +#endif + +#ifndef UNICODE +#define UNICODE 1 +#endif + +#ifndef _UNICODE +#define _UNICODE 1 // NOLINT(bugprone-reserved-identifier) +#endif + +#include diff --git a/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.cpp new file mode 100644 index 0000000..6b133ed --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.cpp @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include +#include +#include + +#include + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +String ClipboardImpl::getString() +{ + String text; + + if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) + { + err() << "Failed to get the clipboard data in Unicode format: " << getErrorString(GetLastError()) << std::endl; + return text; + } + + if (!OpenClipboard(nullptr)) + { + err() << "Failed to open the Win32 clipboard: " << getErrorString(GetLastError()) << std::endl; + return text; + } + + HANDLE clipboardHandle = GetClipboardData(CF_UNICODETEXT); + + if (!clipboardHandle) + { + err() << "Failed to get Win32 handle for clipboard content: " << getErrorString(GetLastError()) << std::endl; + CloseClipboard(); + return text; + } + + text = String(static_cast(GlobalLock(clipboardHandle))); + GlobalUnlock(clipboardHandle); + + CloseClipboard(); + return text; +} + + +//////////////////////////////////////////////////////////// +void ClipboardImpl::setString(const String& text) +{ + if (!OpenClipboard(nullptr)) + { + err() << "Failed to open the Win32 clipboard: " << getErrorString(GetLastError()) << std::endl; + return; + } + + if (!EmptyClipboard()) + { + err() << "Failed to empty the Win32 clipboard: " << getErrorString(GetLastError()) << std::endl; + CloseClipboard(); + return; + } + + // Create a Win32-compatible string + const std::size_t stringSize = (text.getSize() + 1) * sizeof(WCHAR); + if (const HANDLE stringHandle = GlobalAlloc(GMEM_MOVEABLE, stringSize)) + { + std::memcpy(GlobalLock(stringHandle), text.toWideString().data(), stringSize); + GlobalUnlock(stringHandle); + SetClipboardData(CF_UNICODETEXT, stringHandle); + } + + CloseClipboard(); +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.hpp b/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.hpp new file mode 100644 index 0000000..dc83abe --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/ClipboardImpl.hpp @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + + +namespace sf +{ +class String; + +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Give access to the system clipboard +/// +//////////////////////////////////////////////////////////// +class ClipboardImpl +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Get the content of the clipboard as string data + /// + /// This function returns the content of the clipboard + /// as a string. If the clipboard does not contain string + /// it returns an empty sf::String object. + /// + /// \return Current content of the clipboard + /// + //////////////////////////////////////////////////////////// + static String getString(); + + //////////////////////////////////////////////////////////// + /// \brief Set the content of the clipboard as string data + /// + /// This function sets the content of the clipboard as a + /// string. + /// + /// \param text sf::String object containing the data to be sent + /// to the clipboard + /// + //////////////////////////////////////////////////////////// + static void setString(const String& text); +}; + +} // namespace priv + +} // namespace sf diff --git a/vendor/SFML/src/SFML/Window/Win32/CursorImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/CursorImpl.cpp new file mode 100644 index 0000000..28fbbf5 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/CursorImpl.cpp @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include +#include + +#include + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +CursorImpl::~CursorImpl() +{ + release(); +} + + +//////////////////////////////////////////////////////////// +bool CursorImpl::loadFromPixels(const std::uint8_t* pixels, Vector2u size, Vector2u hotspot) +{ + release(); + + // Create the bitmap that will hold our color data + auto bitmapHeader = BITMAPV5HEADER(); + bitmapHeader.bV5Size = sizeof(BITMAPV5HEADER); + bitmapHeader.bV5Width = static_cast(size.x); + bitmapHeader.bV5Height = -static_cast(size.y); // Negative indicates origin is in upper-left corner + bitmapHeader.bV5Planes = 1; + bitmapHeader.bV5BitCount = 32; + bitmapHeader.bV5Compression = BI_BITFIELDS; + bitmapHeader.bV5RedMask = 0x00ff0000; + bitmapHeader.bV5GreenMask = 0x0000ff00; + bitmapHeader.bV5BlueMask = 0x000000ff; + bitmapHeader.bV5AlphaMask = 0xff000000; + + std::uint32_t* bitmapData = nullptr; + + HDC screenDC = GetDC(nullptr); + HBITMAP color = CreateDIBSection(screenDC, + reinterpret_cast(&bitmapHeader), + DIB_RGB_COLORS, + reinterpret_cast(&bitmapData), + nullptr, + 0); + ReleaseDC(nullptr, screenDC); + + if (!color) + { + err() << "Failed to create cursor color bitmap" << std::endl; + return false; + } + + // Fill our bitmap with the cursor color data + // We'll have to swap the red and blue channels here + std::uint32_t* bitmapOffset = bitmapData; + for (std::size_t remaining = size.x * size.y; remaining > 0; --remaining, pixels += 4) + { + *bitmapOffset++ = static_cast((pixels[3] << 24) | (pixels[0] << 16) | (pixels[1] << 8) | pixels[2]); + } + + // Create a dummy mask bitmap (it won't be used) + HBITMAP mask = CreateBitmap(static_cast(size.x), static_cast(size.y), 1, 1, nullptr); + + if (!mask) + { + DeleteObject(color); + err() << "Failed to create cursor mask bitmap" << std::endl; + return false; + } + + // Create the structure that describes our cursor + auto cursorInfo = ICONINFO(); + cursorInfo.fIcon = FALSE; // This is a cursor and not an icon + cursorInfo.xHotspot = hotspot.x; + cursorInfo.yHotspot = hotspot.y; + cursorInfo.hbmColor = color; + cursorInfo.hbmMask = mask; + + // Create the cursor + m_cursor = reinterpret_cast(CreateIconIndirect(&cursorInfo)); + m_systemCursor = false; + + // The data has been copied into the cursor, so get rid of these + DeleteObject(color); + DeleteObject(mask); + + if (m_cursor) + { + return true; + } + + err() << "Failed to create cursor from bitmaps" << std::endl; + return false; +} + + +//////////////////////////////////////////////////////////// +bool CursorImpl::loadFromSystem(Cursor::Type type) +{ + release(); + + LPCTSTR shape = nullptr; + + // clang-format off + switch (type) + { + case Cursor::Type::Arrow: shape = IDC_ARROW; break; + case Cursor::Type::ArrowWait: shape = IDC_APPSTARTING; break; + case Cursor::Type::Wait: shape = IDC_WAIT; break; + case Cursor::Type::Text: shape = IDC_IBEAM; break; + case Cursor::Type::Hand: shape = IDC_HAND; break; + case Cursor::Type::SizeHorizontal: shape = IDC_SIZEWE; break; + case Cursor::Type::SizeVertical: shape = IDC_SIZENS; break; + case Cursor::Type::SizeTopLeftBottomRight: shape = IDC_SIZENWSE; break; + case Cursor::Type::SizeBottomLeftTopRight: shape = IDC_SIZENESW; break; + case Cursor::Type::SizeLeft: shape = IDC_SIZEWE; break; + case Cursor::Type::SizeRight: shape = IDC_SIZEWE; break; + case Cursor::Type::SizeTop: shape = IDC_SIZENS; break; + case Cursor::Type::SizeBottom: shape = IDC_SIZENS; break; + case Cursor::Type::SizeTopLeft: shape = IDC_SIZENWSE; break; + case Cursor::Type::SizeBottomRight: shape = IDC_SIZENWSE; break; + case Cursor::Type::SizeBottomLeft: shape = IDC_SIZENESW; break; + case Cursor::Type::SizeTopRight: shape = IDC_SIZENESW; break; + case Cursor::Type::SizeAll: shape = IDC_SIZEALL; break; + case Cursor::Type::Cross: shape = IDC_CROSS; break; + case Cursor::Type::Help: shape = IDC_HELP; break; + case Cursor::Type::NotAllowed: shape = IDC_NO; break; + } + // clang-format on + + // Get the shared system cursor and make sure not to destroy it + m_cursor = LoadCursor(nullptr, shape); + m_systemCursor = true; + + if (m_cursor) + { + return true; + } + + err() << "Could not create copy of a system cursor" << std::endl; + return false; +} + + +//////////////////////////////////////////////////////////// +void CursorImpl::release() +{ + if (m_cursor && !m_systemCursor) + { + DestroyCursor(static_cast(m_cursor)); + m_cursor = nullptr; + } +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/CursorImpl.hpp b/vendor/SFML/src/SFML/Window/Win32/CursorImpl.hpp new file mode 100644 index 0000000..2e93363 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/CursorImpl.hpp @@ -0,0 +1,106 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +/// \brief Win32 implementation of Cursor +/// +//////////////////////////////////////////////////////////// +class CursorImpl +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + /// Refer to sf::Cursor::Cursor(). + /// + //////////////////////////////////////////////////////////// + CursorImpl() = default; + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + /// Refer to sf::Cursor::~Cursor(). + /// + //////////////////////////////////////////////////////////// + ~CursorImpl(); + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy constructor + /// + //////////////////////////////////////////////////////////// + CursorImpl(const CursorImpl&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Deleted copy assignment + /// + //////////////////////////////////////////////////////////// + CursorImpl& operator=(const CursorImpl&) = delete; + + //////////////////////////////////////////////////////////// + /// \brief Create a cursor with the provided image + /// + /// Refer to sf::Cursor::createFromPixels(). + /// + //////////////////////////////////////////////////////////// + bool loadFromPixels(const std::uint8_t* pixels, Vector2u size, Vector2u hotspot); + + //////////////////////////////////////////////////////////// + /// \brief Create a native system cursor + /// + /// Refer to sf::Cursor::createFromSystem(). + /// + //////////////////////////////////////////////////////////// + bool loadFromSystem(Cursor::Type type); + +private: + friend class WindowImplWin32; + + //////////////////////////////////////////////////////////// + /// \brief Release the cursor, if we have loaded one. + /// + //////////////////////////////////////////////////////////// + void release(); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + void* m_cursor{}; // Type erasure via `void*` is used here to avoid depending on `windows.h` + bool m_systemCursor{}; +}; + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/InputImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/InputImpl.cpp new file mode 100644 index 0000000..d6ba170 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/InputImpl.cpp @@ -0,0 +1,757 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include +#include +#include + +#include + + +namespace +{ +sf::priv::EnumArray keyToScancodeMapping; ///< Mapping from Key to Scancode +sf::priv::EnumArray scancodeToKeyMapping; ///< Mapping from Scancode to Key + +sf::Keyboard::Key virtualKeyToSfKey(UINT virtualKey) +{ + // clang-format off + switch (virtualKey) + { + case 'A': return sf::Keyboard::Key::A; + case 'B': return sf::Keyboard::Key::B; + case 'C': return sf::Keyboard::Key::C; + case 'D': return sf::Keyboard::Key::D; + case 'E': return sf::Keyboard::Key::E; + case 'F': return sf::Keyboard::Key::F; + case 'G': return sf::Keyboard::Key::G; + case 'H': return sf::Keyboard::Key::H; + case 'I': return sf::Keyboard::Key::I; + case 'J': return sf::Keyboard::Key::J; + case 'K': return sf::Keyboard::Key::K; + case 'L': return sf::Keyboard::Key::L; + case 'M': return sf::Keyboard::Key::M; + case 'N': return sf::Keyboard::Key::N; + case 'O': return sf::Keyboard::Key::O; + case 'P': return sf::Keyboard::Key::P; + case 'Q': return sf::Keyboard::Key::Q; + case 'R': return sf::Keyboard::Key::R; + case 'S': return sf::Keyboard::Key::S; + case 'T': return sf::Keyboard::Key::T; + case 'U': return sf::Keyboard::Key::U; + case 'V': return sf::Keyboard::Key::V; + case 'W': return sf::Keyboard::Key::W; + case 'X': return sf::Keyboard::Key::X; + case 'Y': return sf::Keyboard::Key::Y; + case 'Z': return sf::Keyboard::Key::Z; + case '0': return sf::Keyboard::Key::Num0; + case '1': return sf::Keyboard::Key::Num1; + case '2': return sf::Keyboard::Key::Num2; + case '3': return sf::Keyboard::Key::Num3; + case '4': return sf::Keyboard::Key::Num4; + case '5': return sf::Keyboard::Key::Num5; + case '6': return sf::Keyboard::Key::Num6; + case '7': return sf::Keyboard::Key::Num7; + case '8': return sf::Keyboard::Key::Num8; + case '9': return sf::Keyboard::Key::Num9; + case VK_ESCAPE: return sf::Keyboard::Key::Escape; + case VK_LCONTROL: return sf::Keyboard::Key::LControl; + case VK_LSHIFT: return sf::Keyboard::Key::LShift; + case VK_LMENU: return sf::Keyboard::Key::LAlt; + case VK_LWIN: return sf::Keyboard::Key::LSystem; + case VK_RCONTROL: return sf::Keyboard::Key::RControl; + case VK_RSHIFT: return sf::Keyboard::Key::RShift; + case VK_RMENU: return sf::Keyboard::Key::RAlt; + case VK_RWIN: return sf::Keyboard::Key::RSystem; + case VK_APPS: return sf::Keyboard::Key::Menu; + case VK_OEM_4: return sf::Keyboard::Key::LBracket; + case VK_OEM_6: return sf::Keyboard::Key::RBracket; + case VK_OEM_1: return sf::Keyboard::Key::Semicolon; + case VK_OEM_COMMA: return sf::Keyboard::Key::Comma; + case VK_OEM_PERIOD: return sf::Keyboard::Key::Period; + case VK_OEM_7: return sf::Keyboard::Key::Apostrophe; + case VK_OEM_2: return sf::Keyboard::Key::Slash; + case VK_OEM_5: return sf::Keyboard::Key::Backslash; + case VK_OEM_3: return sf::Keyboard::Key::Grave; + case VK_OEM_PLUS: return sf::Keyboard::Key::Equal; + case VK_OEM_MINUS: return sf::Keyboard::Key::Hyphen; + case VK_SPACE: return sf::Keyboard::Key::Space; + case VK_RETURN: return sf::Keyboard::Key::Enter; + case VK_BACK: return sf::Keyboard::Key::Backspace; + case VK_TAB: return sf::Keyboard::Key::Tab; + case VK_PRIOR: return sf::Keyboard::Key::PageUp; + case VK_NEXT: return sf::Keyboard::Key::PageDown; + case VK_END: return sf::Keyboard::Key::End; + case VK_HOME: return sf::Keyboard::Key::Home; + case VK_INSERT: return sf::Keyboard::Key::Insert; + case VK_DELETE: return sf::Keyboard::Key::Delete; + case VK_ADD: return sf::Keyboard::Key::Add; + case VK_SUBTRACT: return sf::Keyboard::Key::Subtract; + case VK_MULTIPLY: return sf::Keyboard::Key::Multiply; + case VK_DIVIDE: return sf::Keyboard::Key::Divide; + case VK_LEFT: return sf::Keyboard::Key::Left; + case VK_RIGHT: return sf::Keyboard::Key::Right; + case VK_UP: return sf::Keyboard::Key::Up; + case VK_DOWN: return sf::Keyboard::Key::Down; + case VK_NUMPAD0: return sf::Keyboard::Key::Numpad0; + case VK_NUMPAD1: return sf::Keyboard::Key::Numpad1; + case VK_NUMPAD2: return sf::Keyboard::Key::Numpad2; + case VK_NUMPAD3: return sf::Keyboard::Key::Numpad3; + case VK_NUMPAD4: return sf::Keyboard::Key::Numpad4; + case VK_NUMPAD5: return sf::Keyboard::Key::Numpad5; + case VK_NUMPAD6: return sf::Keyboard::Key::Numpad6; + case VK_NUMPAD7: return sf::Keyboard::Key::Numpad7; + case VK_NUMPAD8: return sf::Keyboard::Key::Numpad8; + case VK_NUMPAD9: return sf::Keyboard::Key::Numpad9; + case VK_F1: return sf::Keyboard::Key::F1; + case VK_F2: return sf::Keyboard::Key::F2; + case VK_F3: return sf::Keyboard::Key::F3; + case VK_F4: return sf::Keyboard::Key::F4; + case VK_F5: return sf::Keyboard::Key::F5; + case VK_F6: return sf::Keyboard::Key::F6; + case VK_F7: return sf::Keyboard::Key::F7; + case VK_F8: return sf::Keyboard::Key::F8; + case VK_F9: return sf::Keyboard::Key::F9; + case VK_F10: return sf::Keyboard::Key::F10; + case VK_F11: return sf::Keyboard::Key::F11; + case VK_F12: return sf::Keyboard::Key::F12; + case VK_F13: return sf::Keyboard::Key::F13; + case VK_F14: return sf::Keyboard::Key::F14; + case VK_F15: return sf::Keyboard::Key::F15; + case VK_PAUSE: return sf::Keyboard::Key::Pause; + default: return sf::Keyboard::Key::Unknown; + } + // clang-format on +} + +int sfKeyToVirtualKey(sf::Keyboard::Key key) +{ + // clang-format off + switch (key) + { + case sf::Keyboard::Key::A: return 'A'; + case sf::Keyboard::Key::B: return 'B'; + case sf::Keyboard::Key::C: return 'C'; + case sf::Keyboard::Key::D: return 'D'; + case sf::Keyboard::Key::E: return 'E'; + case sf::Keyboard::Key::F: return 'F'; + case sf::Keyboard::Key::G: return 'G'; + case sf::Keyboard::Key::H: return 'H'; + case sf::Keyboard::Key::I: return 'I'; + case sf::Keyboard::Key::J: return 'J'; + case sf::Keyboard::Key::K: return 'K'; + case sf::Keyboard::Key::L: return 'L'; + case sf::Keyboard::Key::M: return 'M'; + case sf::Keyboard::Key::N: return 'N'; + case sf::Keyboard::Key::O: return 'O'; + case sf::Keyboard::Key::P: return 'P'; + case sf::Keyboard::Key::Q: return 'Q'; + case sf::Keyboard::Key::R: return 'R'; + case sf::Keyboard::Key::S: return 'S'; + case sf::Keyboard::Key::T: return 'T'; + case sf::Keyboard::Key::U: return 'U'; + case sf::Keyboard::Key::V: return 'V'; + case sf::Keyboard::Key::W: return 'W'; + case sf::Keyboard::Key::X: return 'X'; + case sf::Keyboard::Key::Y: return 'Y'; + case sf::Keyboard::Key::Z: return 'Z'; + case sf::Keyboard::Key::Num0: return '0'; + case sf::Keyboard::Key::Num1: return '1'; + case sf::Keyboard::Key::Num2: return '2'; + case sf::Keyboard::Key::Num3: return '3'; + case sf::Keyboard::Key::Num4: return '4'; + case sf::Keyboard::Key::Num5: return '5'; + case sf::Keyboard::Key::Num6: return '6'; + case sf::Keyboard::Key::Num7: return '7'; + case sf::Keyboard::Key::Num8: return '8'; + case sf::Keyboard::Key::Num9: return '9'; + case sf::Keyboard::Key::Escape: return VK_ESCAPE; + case sf::Keyboard::Key::LControl: return VK_LCONTROL; + case sf::Keyboard::Key::LShift: return VK_LSHIFT; + case sf::Keyboard::Key::LAlt: return VK_LMENU; + case sf::Keyboard::Key::LSystem: return VK_LWIN; + case sf::Keyboard::Key::RControl: return VK_RCONTROL; + case sf::Keyboard::Key::RShift: return VK_RSHIFT; + case sf::Keyboard::Key::RAlt: return VK_RMENU; + case sf::Keyboard::Key::RSystem: return VK_RWIN; + case sf::Keyboard::Key::Menu: return VK_APPS; + case sf::Keyboard::Key::LBracket: return VK_OEM_4; + case sf::Keyboard::Key::RBracket: return VK_OEM_6; + case sf::Keyboard::Key::Semicolon: return VK_OEM_1; + case sf::Keyboard::Key::Comma: return VK_OEM_COMMA; + case sf::Keyboard::Key::Period: return VK_OEM_PERIOD; + case sf::Keyboard::Key::Apostrophe: return VK_OEM_7; + case sf::Keyboard::Key::Slash: return VK_OEM_2; + case sf::Keyboard::Key::Backslash: return VK_OEM_5; + case sf::Keyboard::Key::Grave: return VK_OEM_3; + case sf::Keyboard::Key::Equal: return VK_OEM_PLUS; + case sf::Keyboard::Key::Hyphen: return VK_OEM_MINUS; + case sf::Keyboard::Key::Space: return VK_SPACE; + case sf::Keyboard::Key::Enter: return VK_RETURN; + case sf::Keyboard::Key::Backspace: return VK_BACK; + case sf::Keyboard::Key::Tab: return VK_TAB; + case sf::Keyboard::Key::PageUp: return VK_PRIOR; + case sf::Keyboard::Key::PageDown: return VK_NEXT; + case sf::Keyboard::Key::End: return VK_END; + case sf::Keyboard::Key::Home: return VK_HOME; + case sf::Keyboard::Key::Insert: return VK_INSERT; + case sf::Keyboard::Key::Delete: return VK_DELETE; + case sf::Keyboard::Key::Add: return VK_ADD; + case sf::Keyboard::Key::Subtract: return VK_SUBTRACT; + case sf::Keyboard::Key::Multiply: return VK_MULTIPLY; + case sf::Keyboard::Key::Divide: return VK_DIVIDE; + case sf::Keyboard::Key::Left: return VK_LEFT; + case sf::Keyboard::Key::Right: return VK_RIGHT; + case sf::Keyboard::Key::Up: return VK_UP; + case sf::Keyboard::Key::Down: return VK_DOWN; + case sf::Keyboard::Key::Numpad0: return VK_NUMPAD0; + case sf::Keyboard::Key::Numpad1: return VK_NUMPAD1; + case sf::Keyboard::Key::Numpad2: return VK_NUMPAD2; + case sf::Keyboard::Key::Numpad3: return VK_NUMPAD3; + case sf::Keyboard::Key::Numpad4: return VK_NUMPAD4; + case sf::Keyboard::Key::Numpad5: return VK_NUMPAD5; + case sf::Keyboard::Key::Numpad6: return VK_NUMPAD6; + case sf::Keyboard::Key::Numpad7: return VK_NUMPAD7; + case sf::Keyboard::Key::Numpad8: return VK_NUMPAD8; + case sf::Keyboard::Key::Numpad9: return VK_NUMPAD9; + case sf::Keyboard::Key::F1: return VK_F1; + case sf::Keyboard::Key::F2: return VK_F2; + case sf::Keyboard::Key::F3: return VK_F3; + case sf::Keyboard::Key::F4: return VK_F4; + case sf::Keyboard::Key::F5: return VK_F5; + case sf::Keyboard::Key::F6: return VK_F6; + case sf::Keyboard::Key::F7: return VK_F7; + case sf::Keyboard::Key::F8: return VK_F8; + case sf::Keyboard::Key::F9: return VK_F9; + case sf::Keyboard::Key::F10: return VK_F10; + case sf::Keyboard::Key::F11: return VK_F11; + case sf::Keyboard::Key::F12: return VK_F12; + case sf::Keyboard::Key::F13: return VK_F13; + case sf::Keyboard::Key::F14: return VK_F14; + case sf::Keyboard::Key::F15: return VK_F15; + case sf::Keyboard::Key::Pause: return VK_PAUSE; + default: return 0; + } + // clang-format on +} + +WORD sfScanToWinScan(sf::Keyboard::Scancode code) +{ + // Convert an SFML scancode to a Windows scancode + // Reference: https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx + // clang-format off + switch (code) + { + case sf::Keyboard::Scan::A: return 0x1E; + case sf::Keyboard::Scan::B: return 0x30; + case sf::Keyboard::Scan::C: return 0x2E; + case sf::Keyboard::Scan::D: return 0x20; + case sf::Keyboard::Scan::E: return 0x12; + case sf::Keyboard::Scan::F: return 0x21; + case sf::Keyboard::Scan::G: return 0x22; + case sf::Keyboard::Scan::H: return 0x23; + case sf::Keyboard::Scan::I: return 0x17; + case sf::Keyboard::Scan::J: return 0x24; + case sf::Keyboard::Scan::K: return 0x25; + case sf::Keyboard::Scan::L: return 0x26; + case sf::Keyboard::Scan::M: return 0x32; + case sf::Keyboard::Scan::N: return 0x31; + case sf::Keyboard::Scan::O: return 0x18; + case sf::Keyboard::Scan::P: return 0x19; + case sf::Keyboard::Scan::Q: return 0x10; + case sf::Keyboard::Scan::R: return 0x13; + case sf::Keyboard::Scan::S: return 0x1F; + case sf::Keyboard::Scan::T: return 0x14; + case sf::Keyboard::Scan::U: return 0x16; + case sf::Keyboard::Scan::V: return 0x2F; + case sf::Keyboard::Scan::W: return 0x11; + case sf::Keyboard::Scan::X: return 0x2D; + case sf::Keyboard::Scan::Y: return 0x15; + case sf::Keyboard::Scan::Z: return 0x2C; + + case sf::Keyboard::Scan::Num1: return 0x02; + case sf::Keyboard::Scan::Num2: return 0x03; + case sf::Keyboard::Scan::Num3: return 0x04; + case sf::Keyboard::Scan::Num4: return 0x05; + case sf::Keyboard::Scan::Num5: return 0x06; + case sf::Keyboard::Scan::Num6: return 0x07; + case sf::Keyboard::Scan::Num7: return 0x08; + case sf::Keyboard::Scan::Num8: return 0x09; + case sf::Keyboard::Scan::Num9: return 0x0A; + case sf::Keyboard::Scan::Num0: return 0x0B; + + case sf::Keyboard::Scan::Enter: return 0x1C; + case sf::Keyboard::Scan::Escape: return 0x01; + case sf::Keyboard::Scan::Backspace: return 0x0E; + case sf::Keyboard::Scan::Tab: return 0x0F; + case sf::Keyboard::Scan::Space: return 0x39; + case sf::Keyboard::Scan::Hyphen: return 0x0C; + case sf::Keyboard::Scan::Equal: return 0x0D; + case sf::Keyboard::Scan::LBracket: return 0x1A; + case sf::Keyboard::Scan::RBracket: return 0x1B; + case sf::Keyboard::Scan::Backslash: return 0x2B; + case sf::Keyboard::Scan::Semicolon: return 0x27; + case sf::Keyboard::Scan::Apostrophe: return 0x28; + case sf::Keyboard::Scan::Grave: return 0x29; + case sf::Keyboard::Scan::Comma: return 0x33; + case sf::Keyboard::Scan::Period: return 0x34; + case sf::Keyboard::Scan::Slash: return 0x35; + + case sf::Keyboard::Scan::F1: return 0x3B; + case sf::Keyboard::Scan::F2: return 0x3C; + case sf::Keyboard::Scan::F3: return 0x3D; + case sf::Keyboard::Scan::F4: return 0x3E; + case sf::Keyboard::Scan::F5: return 0x3F; + case sf::Keyboard::Scan::F6: return 0x40; + case sf::Keyboard::Scan::F7: return 0x41; + case sf::Keyboard::Scan::F8: return 0x42; + case sf::Keyboard::Scan::F9: return 0x43; + case sf::Keyboard::Scan::F10: return 0x44; + case sf::Keyboard::Scan::F11: return 0x57; + case sf::Keyboard::Scan::F12: return 0x58; + case sf::Keyboard::Scan::F13: return 0x64; + case sf::Keyboard::Scan::F14: return 0x65; + case sf::Keyboard::Scan::F15: return 0x66; + case sf::Keyboard::Scan::F16: return 0x67; + case sf::Keyboard::Scan::F17: return 0x68; + case sf::Keyboard::Scan::F18: return 0x69; + case sf::Keyboard::Scan::F19: return 0x6A; + case sf::Keyboard::Scan::F20: return 0x6B; + case sf::Keyboard::Scan::F21: return 0x6C; + case sf::Keyboard::Scan::F22: return 0x6D; + case sf::Keyboard::Scan::F23: return 0x6E; + case sf::Keyboard::Scan::F24: return 0x76; + + case sf::Keyboard::Scan::CapsLock: return 0x3A; + case sf::Keyboard::Scan::PrintScreen: return 0xE037; + case sf::Keyboard::Scan::ScrollLock: return 0x46; + case sf::Keyboard::Scan::Pause: return 0x45; + case sf::Keyboard::Scan::Insert: return 0xE052; + case sf::Keyboard::Scan::Home: return 0xE047; + case sf::Keyboard::Scan::PageUp: return 0xE049; + case sf::Keyboard::Scan::Delete: return 0xE053; + case sf::Keyboard::Scan::End: return 0xE04F; + case sf::Keyboard::Scan::PageDown: return 0xE051; + case sf::Keyboard::Scan::Right: return 0xE04D; + case sf::Keyboard::Scan::Left: return 0xE04B; + case sf::Keyboard::Scan::Down: return 0xE050; + case sf::Keyboard::Scan::Up: return 0xE048; + case sf::Keyboard::Scan::NumLock: return 0xE045; + + case sf::Keyboard::Scan::NumpadDivide: return 0xE035; + case sf::Keyboard::Scan::NumpadMultiply: return 0x37; + case sf::Keyboard::Scan::NumpadMinus: return 0x4A; + case sf::Keyboard::Scan::NumpadPlus: return 0x4E; + case sf::Keyboard::Scan::NumpadEqual: return 0x7E; + case sf::Keyboard::Scan::NumpadEnter: return 0xE01C; + case sf::Keyboard::Scan::NumpadDecimal: return 0x53; + + case sf::Keyboard::Scan::Numpad1: return 0x4F; + case sf::Keyboard::Scan::Numpad2: return 0x50; + case sf::Keyboard::Scan::Numpad3: return 0x51; + case sf::Keyboard::Scan::Numpad4: return 0x4B; + case sf::Keyboard::Scan::Numpad5: return 0x4C; + case sf::Keyboard::Scan::Numpad6: return 0x4D; + case sf::Keyboard::Scan::Numpad7: return 0x47; + case sf::Keyboard::Scan::Numpad8: return 0x48; + case sf::Keyboard::Scan::Numpad9: return 0x49; + case sf::Keyboard::Scan::Numpad0: return 0x52; + + case sf::Keyboard::Scan::NonUsBackslash: return 0x56; + // No known scancode for Keyboard::Scan::Application + // No known scancode for Keyboard::Scan::Execute + // No known scancode for Keyboard::Scan::ModeChange + case sf::Keyboard::Scan::Help: return 0xE061; + case sf::Keyboard::Scan::Menu: return 0xE05D; + case sf::Keyboard::Scan::Select: return 0xE01E; + // No known scancode for Keyboard::Scan::Redo + // No known scancode for Keyboard::Scan::Undo + // No known scancode for Keyboard::Scan::Cut + // No known scancode for Keyboard::Scan::Copy + // No known scancode for Keyboard::Scan::Paste + + case sf::Keyboard::Scan::VolumeMute: return 0xE020; + case sf::Keyboard::Scan::VolumeUp: return 0xE02E; + case sf::Keyboard::Scan::VolumeDown: return 0xE02C; + case sf::Keyboard::Scan::MediaPlayPause: return 0xE022; + case sf::Keyboard::Scan::MediaStop: return 0xE024; + case sf::Keyboard::Scan::MediaNextTrack: return 0xE019; + case sf::Keyboard::Scan::MediaPreviousTrack: return 0xE010; + + case sf::Keyboard::Scan::LControl: return 0x1D; + case sf::Keyboard::Scan::LShift: return 0x2A; + case sf::Keyboard::Scan::LAlt: return 0x38; + case sf::Keyboard::Scan::LSystem: return 0xE05B; + case sf::Keyboard::Scan::RControl: return 0xE01D; + case sf::Keyboard::Scan::RShift: return 0x36; + case sf::Keyboard::Scan::RAlt: return 0xE038; + case sf::Keyboard::Scan::RSystem: return 0xE05C; + + case sf::Keyboard::Scan::Back: return 0xE06A; + case sf::Keyboard::Scan::Forward: return 0xE069; + case sf::Keyboard::Scan::Refresh: return 0xE067; + case sf::Keyboard::Scan::Stop: return 0xE068; + case sf::Keyboard::Scan::Search: return 0xE065; + case sf::Keyboard::Scan::Favorites: return 0xE066; + case sf::Keyboard::Scan::HomePage: return 0xE030; + + case sf::Keyboard::Scan::LaunchApplication1: return 0xE06B; + case sf::Keyboard::Scan::LaunchApplication2: return 0xE021; + case sf::Keyboard::Scan::LaunchMail: return 0xE06C; + case sf::Keyboard::Scan::LaunchMediaSelect: return 0xE06D; + + // Unable to map to a scancode + default: return 0x0; + } + // clang-format on +} + +WORD sfScanToWinScanExtended(sf::Keyboard::Scancode code) +{ + // Convert an SFML scancode to a Windows scancode + // Reference: https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx + // clang-format off + switch (code) + { + case sf::Keyboard::Scan::PrintScreen: return 55 | 0xE100; + case sf::Keyboard::Scan::Insert: return 82 | 0xE100; + case sf::Keyboard::Scan::Home: return 71 | 0xE100; + case sf::Keyboard::Scan::PageUp: return 73 | 0xE100; + case sf::Keyboard::Scan::Delete: return 83 | 0xE100; + case sf::Keyboard::Scan::End: return 79 | 0xE100; + case sf::Keyboard::Scan::PageDown: return 81 | 0xE100; + case sf::Keyboard::Scan::Right: return 77 | 0xE100; + case sf::Keyboard::Scan::Left: return 75 | 0xE100; + case sf::Keyboard::Scan::Down: return 80 | 0xE100; + case sf::Keyboard::Scan::Up: return 72 | 0xE100; + case sf::Keyboard::Scan::NumLock: return 69 | 0xE100; + case sf::Keyboard::Scan::NumpadEnter: return 28 | 0xE100; + case sf::Keyboard::Scan::NumpadDivide: return 53 | 0xE100; + case sf::Keyboard::Scan::Help: return 97 | 0xE100; + case sf::Keyboard::Scan::Menu: return 93 | 0xE100; + case sf::Keyboard::Scan::Select: return 30 | 0xE100; + case sf::Keyboard::Scan::VolumeMute: return 32 | 0xE100; + case sf::Keyboard::Scan::VolumeUp: return 46 | 0xE100; + case sf::Keyboard::Scan::VolumeDown: return 44 | 0xE100; + case sf::Keyboard::Scan::MediaPlayPause: return 34 | 0xE100; + case sf::Keyboard::Scan::MediaStop: return 36 | 0xE100; + case sf::Keyboard::Scan::MediaNextTrack: return 25 | 0xE100; + case sf::Keyboard::Scan::MediaPreviousTrack: return 16 | 0xE100; + case sf::Keyboard::Scan::LSystem: return 91 | 0xE100; + case sf::Keyboard::Scan::RControl: return 29 | 0xE100; + case sf::Keyboard::Scan::RAlt: return 56 | 0xE100; + case sf::Keyboard::Scan::RSystem: return 92 | 0xE100; + case sf::Keyboard::Scan::Back: return 106 | 0xE100; + case sf::Keyboard::Scan::Forward: return 105 | 0xE100; + case sf::Keyboard::Scan::Refresh: return 103 | 0xE100; + case sf::Keyboard::Scan::Stop: return 104 | 0xE100; + case sf::Keyboard::Scan::Search: return 101 | 0xE100; + case sf::Keyboard::Scan::Favorites: return 102 | 0xE100; + case sf::Keyboard::Scan::HomePage: return 48 | 0xE100; + case sf::Keyboard::Scan::LaunchApplication1: return 107 | 0xE100; + case sf::Keyboard::Scan::LaunchApplication2: return 33 | 0xE100; + case sf::Keyboard::Scan::LaunchMail: return 108 | 0xE100; + case sf::Keyboard::Scan::LaunchMediaSelect: return 109 | 0xE100; + + // Use non-extended mapping + default: return sfScanToWinScan(code); + } + // clang-format on +} + +UINT sfScanToVirtualKey(sf::Keyboard::Scancode code) +{ + const WORD winScancode = sfScanToWinScan(code); + + // Manually map non-extended key codes + // clang-format off + switch (code) + { + case sf::Keyboard::Scan::Numpad0: return VK_NUMPAD0; + case sf::Keyboard::Scan::Numpad1: return VK_NUMPAD1; + case sf::Keyboard::Scan::Numpad2: return VK_NUMPAD2; + case sf::Keyboard::Scan::Numpad3: return VK_NUMPAD3; + case sf::Keyboard::Scan::Numpad4: return VK_NUMPAD4; + case sf::Keyboard::Scan::Numpad5: return VK_NUMPAD5; + case sf::Keyboard::Scan::Numpad6: return VK_NUMPAD6; + case sf::Keyboard::Scan::Numpad7: return VK_NUMPAD7; + case sf::Keyboard::Scan::Numpad8: return VK_NUMPAD8; + case sf::Keyboard::Scan::Numpad9: return VK_NUMPAD9; + case sf::Keyboard::Scan::NumpadMinus: return VK_SUBTRACT; + case sf::Keyboard::Scan::NumpadDecimal: return VK_DECIMAL; + case sf::Keyboard::Scan::NumpadDivide: return VK_DIVIDE; + case sf::Keyboard::Scan::Pause: return VK_PAUSE; + case sf::Keyboard::Scan::RControl: return VK_RCONTROL; + case sf::Keyboard::Scan::RAlt: return VK_RMENU; + default: return MapVirtualKey(winScancode, MAPVK_VSC_TO_VK_EX); + } + // clang-format on +} + +std::optional sfScanToConsumerKeyName(sf::Keyboard::Scancode code) +{ + // Convert an SFML scancode to a Windows consumer keyboard key name + // Reference: https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-messages + // clang-format off + switch (code) + { + case sf::Keyboard::Scan::MediaNextTrack: return "Next Track"; + case sf::Keyboard::Scan::MediaPreviousTrack: return "Previous Track"; + case sf::Keyboard::Scan::MediaStop: return "Stop"; + case sf::Keyboard::Scan::MediaPlayPause: return "Play/Pause"; + case sf::Keyboard::Scan::VolumeMute: return "Mute"; + case sf::Keyboard::Scan::VolumeUp: return "Volume Increment"; + case sf::Keyboard::Scan::VolumeDown: return "Volume Decrement"; + case sf::Keyboard::Scan::LaunchMediaSelect: return "Consumer Control Configuration"; + case sf::Keyboard::Scan::LaunchMail: return "Email Reader"; + case sf::Keyboard::Scan::LaunchApplication2: return "Calculator"; + case sf::Keyboard::Scan::LaunchApplication1: return "Local Machine Browser"; + case sf::Keyboard::Scan::Search: return "Search"; + case sf::Keyboard::Scan::HomePage: return "Home"; + case sf::Keyboard::Scan::Back: return "Back"; + case sf::Keyboard::Scan::Forward: return "Forward"; + case sf::Keyboard::Scan::Stop: return "Stop"; + case sf::Keyboard::Scan::Refresh: return "Refresh"; + case sf::Keyboard::Scan::Favorites: return "Bookmarks"; + + // Not a consumer key + default: return std::nullopt; + } + // clang-format on +} + +/// Ensure the mappings are generated from/to Key and Scancode. +void ensureMappings() +{ + static bool isMappingInitialized = false; + + if (isMappingInitialized) + return; + + // Phase 1: Initialize mappings with default values + keyToScancodeMapping.fill(sf::Keyboard::Scan::Unknown); + scancodeToKeyMapping.fill(sf::Keyboard::Key::Unknown); + + // Phase 2: Translate scancode to virtual code to key names + for (unsigned int i = 0; i < sf::Keyboard::ScancodeCount; ++i) + { + const auto scan = static_cast(i); + const UINT virtualKey = sfScanToVirtualKey(scan); + const sf::Keyboard::Key key = virtualKeyToSfKey(virtualKey); + if (key != sf::Keyboard::Key::Unknown && keyToScancodeMapping[key] == sf::Keyboard::Scan::Unknown) + keyToScancodeMapping[key] = scan; + scancodeToKeyMapping[scan] = key; + } + + isMappingInitialized = true; +} + +bool isValidScancode(sf::Keyboard::Scancode code) +{ + return code > sf::Keyboard::Scan::Unknown && static_cast(code) < sf::Keyboard::ScancodeCount; +} + +bool isValidKey(sf::Keyboard::Key key) +{ + return key > sf::Keyboard::Key::Unknown && static_cast(key) < sf::Keyboard::KeyCount; +} + +} // namespace + +namespace sf::priv::InputImpl +{ +//////////////////////////////////////////////////////////// +bool isKeyPressed(Keyboard::Key key) +{ + const int virtualKey = sfKeyToVirtualKey(key); + return (GetAsyncKeyState(virtualKey) & 0x8000) != 0; +} + + +//////////////////////////////////////////////////////////// +bool isKeyPressed(Keyboard::Scancode code) +{ + const UINT virtualKey = sfScanToVirtualKey(code); + return (GetAsyncKeyState(static_cast(virtualKey)) & KF_UP) != 0; +} + + +//////////////////////////////////////////////////////////// +Keyboard::Key localize(Keyboard::Scancode code) +{ + if (!isValidScancode(code)) + return Keyboard::Key::Unknown; + + ensureMappings(); + + return scancodeToKeyMapping[code]; +} + + +//////////////////////////////////////////////////////////// +Keyboard::Scancode delocalize(Keyboard::Key key) +{ + if (!isValidKey(key)) + return Keyboard::Scan::Unknown; + + ensureMappings(); + + return keyToScancodeMapping[key]; +} + + +//////////////////////////////////////////////////////////// +String getDescription(Keyboard::Scancode code) +{ + // Try to translate the scan code to a consumer key + if (const auto consumerKeyName = sfScanToConsumerKeyName(code)) + return *consumerKeyName; + + WORD winCode = sfScanToWinScanExtended(code); + std::array name{}; + + // Remap F13-F23 to values supported by GetKeyNameText + if ((winCode >= 0x64) && (winCode <= 0x6E)) + winCode += 0x18; + // Remap F24 to value supported by GetKeyNameText + if (winCode == 0x76) + winCode = 0x87; + + if (GetKeyNameText(winCode << 16, name.data(), static_cast(name.size())) > 0) + return name.data(); + + return "Unknown"; +} + + +//////////////////////////////////////////////////////////// +void setVirtualKeyboardVisible(bool /*visible*/) +{ + // Not applicable +} + + +//////////////////////////////////////////////////////////// +bool isMouseButtonPressed(Mouse::Button button) +{ + int virtualKey = 0; + switch (button) + { + case Mouse::Button::Left: + virtualKey = GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON; + break; + case Mouse::Button::Right: + virtualKey = GetSystemMetrics(SM_SWAPBUTTON) ? VK_LBUTTON : VK_RBUTTON; + break; + case Mouse::Button::Middle: + virtualKey = VK_MBUTTON; + break; + case Mouse::Button::Extra1: + virtualKey = VK_XBUTTON1; + break; + case Mouse::Button::Extra2: + virtualKey = VK_XBUTTON2; + break; + default: + virtualKey = 0; + break; + } + + return (GetAsyncKeyState(virtualKey) & 0x8000) != 0; +} + + +//////////////////////////////////////////////////////////// +Vector2i getMousePosition() +{ + POINT point; + GetCursorPos(&point); + return {point.x, point.y}; +} + + +//////////////////////////////////////////////////////////// +Vector2i getMousePosition(const WindowBase& relativeTo) +{ + if (const WindowHandle handle = relativeTo.getNativeHandle()) + { + POINT point; + GetCursorPos(&point); + ScreenToClient(handle, &point); + return {point.x, point.y}; + } + + return {}; +} + + +//////////////////////////////////////////////////////////// +void setMousePosition(Vector2i position) +{ + SetCursorPos(position.x, position.y); +} + + +//////////////////////////////////////////////////////////// +void setMousePosition(Vector2i position, const WindowBase& relativeTo) +{ + if (const WindowHandle handle = relativeTo.getNativeHandle()) + { + POINT point = {position.x, position.y}; + ClientToScreen(handle, &point); + SetCursorPos(point.x, point.y); + } +} + + +//////////////////////////////////////////////////////////// +bool isTouchDown(unsigned int /*finger*/) +{ + // Not applicable + return false; +} + + +//////////////////////////////////////////////////////////// +Vector2i getTouchPosition(unsigned int /*finger*/) +{ + // Not applicable + return {}; +} + + +//////////////////////////////////////////////////////////// +Vector2i getTouchPosition(unsigned int /*finger*/, const WindowBase& /*relativeTo*/) +{ + // Not applicable + return {}; +} + +} // namespace sf::priv::InputImpl diff --git a/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.cpp new file mode 100644 index 0000000..aa1e1bf --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.cpp @@ -0,0 +1,1156 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +//////////////////////////////////////////////////////////// +// DirectInput +//////////////////////////////////////////////////////////// + + +#ifndef DIDFT_OPTIONAL +#define DIDFT_OPTIONAL 0x80000000 +#endif + + +namespace +{ +namespace guids +{ +// NOLINTBEGIN(readability-identifier-naming) +constexpr GUID IID_IDirectInput8W = {0xbf798031, 0x483a, 0x4da2, {0xaa, 0x99, 0x5d, 0x64, 0xed, 0x36, 0x97, 0x00}}; + +constexpr GUID GUID_XAxis = {0xa36d02e0, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +constexpr GUID GUID_YAxis = {0xa36d02e1, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +constexpr GUID GUID_ZAxis = {0xa36d02e2, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +constexpr GUID GUID_RzAxis = {0xa36d02e3, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +constexpr GUID GUID_Slider = {0xa36d02e4, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; + +constexpr GUID GUID_POV = {0xa36d02f2, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; + +constexpr GUID GUID_RxAxis = {0xa36d02f4, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +constexpr GUID GUID_RyAxis = {0xa36d02f5, 0xc9f3, 0x11cf, {0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}; +// NOLINTEND(readability-identifier-naming) +} // namespace guids + +HMODULE dinput8dll = nullptr; +IDirectInput8W* directInput = nullptr; + +struct JoystickRecord +{ + GUID guid{}; + unsigned int index{}; + bool plugged{}; +}; + +using JoystickList = std::vector; +JoystickList joystickList; + +struct JoystickBlacklistEntry +{ + unsigned int vendorId{}; + unsigned int productId{}; +}; + +using JoystickBlacklist = std::vector; +JoystickBlacklist joystickBlacklist; + +const DWORD directInputEventBufferSize = 32; +} // namespace + + +//////////////////////////////////////////////////////////// +// Legacy joystick API +//////////////////////////////////////////////////////////// +namespace +{ +struct ConnectionCache +{ + bool connected{}; + sf::Clock timer; +}; + +std::array connectionCache{}; + +// If true, will only update when WM_DEVICECHANGE message is received +bool lazyUpdates = false; + +// Get the joystick's name +sf::String getDeviceName(unsigned int index, JOYCAPS caps) +{ + // Give the joystick a default name + static const sf::String joystickDescription = "Unknown Joystick"; + + LONG result = 0; + HKEY rootKey = nullptr; + HKEY currentKey = nullptr; + std::basic_string subkey; + + subkey = REGSTR_PATH_JOYCONFIG; + subkey += TEXT('\\'); + subkey += caps.szRegKey; + subkey += TEXT('\\'); + subkey += REGSTR_KEY_JOYCURR; + + rootKey = HKEY_CURRENT_USER; + result = RegOpenKeyEx(rootKey, subkey.c_str(), 0, KEY_READ, ¤tKey); + + if (result != ERROR_SUCCESS) + { + rootKey = HKEY_LOCAL_MACHINE; + result = RegOpenKeyEx(rootKey, subkey.c_str(), 0, KEY_READ, ¤tKey); + + if (result != ERROR_SUCCESS) + { + sf::err() << "Unable to open registry for joystick at index " << index << ": " + << sf::priv::getErrorString(static_cast(result)) << std::endl; + return joystickDescription; + } + } + + std::basic_ostringstream indexString; + indexString << index + 1; + + subkey = TEXT("Joystick"); + subkey += indexString.str(); + subkey += REGSTR_VAL_JOYOEMNAME; + + std::array keyData{}; + DWORD keyDataSize = sizeof(keyData); + + result = RegQueryValueEx(currentKey, subkey.c_str(), nullptr, nullptr, reinterpret_cast(keyData.data()), &keyDataSize); + RegCloseKey(currentKey); + + if (result != ERROR_SUCCESS) + { + sf::err() << "Unable to query registry key for joystick at index " << index << ": " + << sf::priv::getErrorString(static_cast(result)) << std::endl; + return joystickDescription; + } + + subkey = REGSTR_PATH_JOYOEM; + subkey += TEXT('\\'); + subkey.append(keyData.data(), keyDataSize / sizeof(TCHAR)); + + result = RegOpenKeyEx(rootKey, subkey.c_str(), 0, KEY_READ, ¤tKey); + + if (result != ERROR_SUCCESS) + { + sf::err() << "Unable to open registry key for joystick at index " << index << ": " + << sf::priv::getErrorString(static_cast(result)) << std::endl; + return joystickDescription; + } + + keyDataSize = sizeof(keyData); + + result = RegQueryValueEx(currentKey, REGSTR_VAL_JOYOEMNAME, nullptr, nullptr, reinterpret_cast(keyData.data()), &keyDataSize); + RegCloseKey(currentKey); + + if (result != ERROR_SUCCESS) + { + sf::err() << "Unable to query name for joystick at index " << index << ": " + << sf::priv::getErrorString(static_cast(result)) << std::endl; + return joystickDescription; + } + + keyData.back() = TEXT('\0'); // Ensure null terminator in case the data is too long. + return keyData.data(); +} +} // namespace + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +void JoystickImpl::initialize() +{ + // Try to initialize DirectInput + initializeDInput(); + + if (!directInput) + err() << "DirectInput not available, falling back to Windows joystick API" << std::endl; + + // Perform the initial scan and populate the connection cache + updateConnections(); +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::cleanup() +{ + // Clean up DirectInput + cleanupDInput(); +} + + +//////////////////////////////////////////////////////////// +bool JoystickImpl::isConnected(unsigned int index) +{ + if (directInput) + return isConnectedDInput(index); + + ConnectionCache& cache = connectionCache[index]; + constexpr sf::Time connectionRefreshDelay = sf::milliseconds(500); + if (!lazyUpdates && cache.timer.getElapsedTime() > connectionRefreshDelay) + { + JOYINFOEX joyInfo; + joyInfo.dwSize = sizeof(joyInfo); + joyInfo.dwFlags = 0; + cache.connected = joyGetPosEx(JOYSTICKID1 + index, &joyInfo) == JOYERR_NOERROR; + + cache.timer.restart(); + } + return cache.connected; +} + +//////////////////////////////////////////////////////////// +void JoystickImpl::setLazyUpdates(bool status) +{ + lazyUpdates = status; +} + +//////////////////////////////////////////////////////////// +void JoystickImpl::updateConnections() +{ + if (directInput) + { + updateConnectionsDInput(); + return; + } + + for (unsigned int i = 0; i < Joystick::Count; ++i) + { + JOYINFOEX joyInfo; + joyInfo.dwSize = sizeof(joyInfo); + joyInfo.dwFlags = 0; + ConnectionCache& cache = connectionCache[i]; + cache.connected = joyGetPosEx(JOYSTICKID1 + i, &joyInfo) == JOYERR_NOERROR; + + cache.timer.restart(); + } +} + +//////////////////////////////////////////////////////////// +bool JoystickImpl::open(unsigned int index) +{ + if (directInput) + return openDInput(index); + + // No explicit "open" action is required + m_index = JOYSTICKID1 + index; + + // Store the joystick capabilities + const bool success = joyGetDevCaps(m_index, &m_caps, sizeof(m_caps)) == JOYERR_NOERROR; + + if (success) + { + m_identification.name = getDeviceName(m_index, m_caps); + m_identification.productId = m_caps.wPid; + m_identification.vendorId = m_caps.wMid; + } + + return success; +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::close() +{ + if (directInput) + closeDInput(); +} + +//////////////////////////////////////////////////////////// +JoystickCaps JoystickImpl::getCapabilities() const +{ + if (directInput) + return getCapabilitiesDInput(); + + JoystickCaps caps; + + caps.buttonCount = std::min(m_caps.wNumButtons, Joystick::ButtonCount); + + caps.axes[Joystick::Axis::X] = true; + caps.axes[Joystick::Axis::Y] = true; + caps.axes[Joystick::Axis::Z] = (m_caps.wCaps & JOYCAPS_HASZ) != 0; + caps.axes[Joystick::Axis::R] = (m_caps.wCaps & JOYCAPS_HASR) != 0; + caps.axes[Joystick::Axis::U] = (m_caps.wCaps & JOYCAPS_HASU) != 0; + caps.axes[Joystick::Axis::V] = (m_caps.wCaps & JOYCAPS_HASV) != 0; + caps.axes[Joystick::Axis::PovX] = (m_caps.wCaps & JOYCAPS_HASPOV) != 0; + caps.axes[Joystick::Axis::PovY] = (m_caps.wCaps & JOYCAPS_HASPOV) != 0; + + return caps; +} + + +//////////////////////////////////////////////////////////// +Joystick::Identification JoystickImpl::getIdentification() const +{ + return m_identification; +} + + +//////////////////////////////////////////////////////////// +JoystickState JoystickImpl::update() +{ + if (directInput) + { + if (m_buffered) + { + return updateDInputBuffered(); + } + + return updateDInputPolled(); + } + + JoystickState state; + + // Get the current joystick state + JOYINFOEX pos; + pos.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | JOY_RETURNBUTTONS; + pos.dwFlags |= (m_caps.wCaps & JOYCAPS_POVCTS) ? JOY_RETURNPOVCTS : JOY_RETURNPOV; + pos.dwSize = sizeof(JOYINFOEX); + if (joyGetPosEx(m_index, &pos) == JOYERR_NOERROR) + { + // The joystick is connected + state.connected = true; + + // Axes + state.axes[Joystick::Axis::X] = (static_cast(pos.dwXpos) - + static_cast(m_caps.wXmax + m_caps.wXmin) / 2.f) * + 200.f / static_cast(m_caps.wXmax - m_caps.wXmin); + state.axes[Joystick::Axis::Y] = (static_cast(pos.dwYpos) - + static_cast(m_caps.wYmax + m_caps.wYmin) / 2.f) * + 200.f / static_cast(m_caps.wYmax - m_caps.wYmin); + state.axes[Joystick::Axis::Z] = (static_cast(pos.dwZpos) - + static_cast(m_caps.wZmax + m_caps.wZmin) / 2.f) * + 200.f / static_cast(m_caps.wZmax - m_caps.wZmin); + state.axes[Joystick::Axis::R] = (static_cast(pos.dwRpos) - + static_cast(m_caps.wRmax + m_caps.wRmin) / 2.f) * + 200.f / static_cast(m_caps.wRmax - m_caps.wRmin); + state.axes[Joystick::Axis::U] = (static_cast(pos.dwUpos) - + static_cast(m_caps.wUmax + m_caps.wUmin) / 2.f) * + 200.f / static_cast(m_caps.wUmax - m_caps.wUmin); + state.axes[Joystick::Axis::V] = (static_cast(pos.dwVpos) - + static_cast(m_caps.wVmax + m_caps.wVmin) / 2.f) * + 200.f / static_cast(m_caps.wVmax - m_caps.wVmin); + + // Special case for POV, it is given as an angle + if (pos.dwPOV != 0xFFFF) + { + const float angle = static_cast(pos.dwPOV) / 18000.f * 3.141592654f; + state.axes[Joystick::Axis::PovX] = std::sin(angle) * 100; + state.axes[Joystick::Axis::PovY] = std::cos(angle) * 100; + } + else + { + state.axes[Joystick::Axis::PovX] = 0; + state.axes[Joystick::Axis::PovY] = 0; + } + + // Buttons + for (unsigned int i = 0; i < Joystick::ButtonCount; ++i) + state.buttons[i] = (pos.dwButtons & (1u << i)) != 0; + } + + return state; +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::initializeDInput() +{ + // Try to load dinput8.dll + dinput8dll = LoadLibraryA("dinput8.dll"); + + if (dinput8dll) + { + // Try to get the address of the DirectInput8Create entry point + using DirectInput8CreateFunc = HRESULT(WINAPI*)(HINSTANCE, DWORD, const IID&, LPVOID*, LPUNKNOWN); + auto directInput8Create = reinterpret_cast( + reinterpret_cast(GetProcAddress(dinput8dll, "DirectInput8Create"))); + + if (directInput8Create) + { + // Try to acquire a DirectInput 8.x interface + const HRESULT result = directInput8Create(GetModuleHandleW(nullptr), + 0x0800, + guids::IID_IDirectInput8W, + reinterpret_cast(&directInput), + nullptr); + + if (FAILED(result)) + { + // De-initialize everything + directInput = nullptr; + FreeLibrary(dinput8dll); + dinput8dll = nullptr; + + err() << "Failed to initialize DirectInput: " << result << std::endl; + } + } + else + { + // Unload dinput8.dll + FreeLibrary(dinput8dll); + dinput8dll = nullptr; + } + } +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::cleanupDInput() +{ + // Release the DirectInput interface + if (directInput) + { + directInput->Release(); + directInput = nullptr; + } + + // Unload dinput8.dll + if (dinput8dll) + FreeLibrary(dinput8dll); +} + + +//////////////////////////////////////////////////////////// +bool JoystickImpl::isConnectedDInput(unsigned int index) +{ + // Check if a joystick with the given index is in the connected list + return std::any_of(joystickList.cbegin(), + joystickList.cend(), + [index](const JoystickRecord& record) { return record.index == index; }); +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::updateConnectionsDInput() +{ + // Clear plugged flags so we can determine which devices were added/removed + for (JoystickRecord& record : joystickList) + record.plugged = false; + + // Enumerate devices + const HRESULT result = directInput->EnumDevices(DI8DEVCLASS_GAMECTRL, + &JoystickImpl::deviceEnumerationCallback, + nullptr, + DIEDFL_ATTACHEDONLY); + + // Remove devices that were not connected during the enumeration + joystickList.erase(std::remove_if(joystickList.begin(), + joystickList.end(), + [](const JoystickRecord& joystickRecord) { return !joystickRecord.plugged; }), + joystickList.end()); + + if (FAILED(result)) + { + err() << "Failed to enumerate DirectInput devices: " << result << std::endl; + + return; + } + + // Assign unused joystick indices to devices that were newly connected + for (unsigned int i = 0; i < Joystick::Count; ++i) + { + for (JoystickRecord& record : joystickList) + { + if (record.index == i) + break; + + if (record.index == Joystick::Count) + { + record.index = i; + break; + } + } + } +} + + +//////////////////////////////////////////////////////////// +bool JoystickImpl::openDInput(unsigned int index) +{ + // Initialize DirectInput members + m_device = nullptr; + + m_axes.fill(-1); + m_buttons.fill(-1); + + m_deviceCaps = {}; + m_deviceCaps.dwSize = sizeof(DIDEVCAPS); + m_state = JoystickState(); + m_buffered = false; + + // Search for a joystick with the given index in the connected list + for (const JoystickRecord& record : joystickList) + { + if (record.index == index) + { + // Create device + HRESULT result = directInput->CreateDevice(record.guid, &m_device, nullptr); + + if (FAILED(result)) + { + err() << "Failed to create DirectInput device: " << result << std::endl; + + return false; + } + + // Get vendor and product id of the device + auto property = DIPROPDWORD(); + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + + if (SUCCEEDED(m_device->GetProperty(DIPROP_VIDPID, &property.diph))) + { + m_identification.productId = HIWORD(property.dwData); + m_identification.vendorId = LOWORD(property.dwData); + + // Check if device is already blacklisted + if (m_identification.productId && m_identification.vendorId) + { + for (const JoystickBlacklistEntry& blacklistEntry : joystickBlacklist) + { + if ((m_identification.productId == blacklistEntry.productId) && + (m_identification.vendorId == blacklistEntry.vendorId)) + { + // Device is blacklisted + m_device->Release(); + m_device = nullptr; + + return false; + } + } + } + } + + // Get friendly product name of the device + auto stringProperty = DIPROPSTRING(); + stringProperty.diph.dwSize = sizeof(stringProperty); + stringProperty.diph.dwHeaderSize = sizeof(stringProperty.diph); + stringProperty.diph.dwHow = DIPH_DEVICE; + stringProperty.diph.dwObj = 0; + + if (SUCCEEDED(m_device->GetProperty(DIPROP_PRODUCTNAME, &stringProperty.diph))) + m_identification.name = stringProperty.wsz; + + static bool formatInitialized = false; + static DIDATAFORMAT format; + + if (!formatInitialized) + { + const DWORD axisType = DIDFT_AXIS | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE; + const DWORD povType = DIDFT_POV | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE; + const DWORD buttonType = DIDFT_BUTTON | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE; + + static std::array data{}; + + for (std::size_t i = 0; i < 4; ++i) + { + data[8 * i + 0].pguid = &guids::GUID_XAxis; + data[8 * i + 1].pguid = &guids::GUID_YAxis; + data[8 * i + 2].pguid = &guids::GUID_ZAxis; + data[8 * i + 3].pguid = &guids::GUID_RxAxis; + data[8 * i + 4].pguid = &guids::GUID_RyAxis; + data[8 * i + 5].pguid = &guids::GUID_RzAxis; + data[8 * i + 6].pguid = &guids::GUID_Slider; + data[8 * i + 7].pguid = &guids::GUID_Slider; + } + + data[0].dwOfs = DIJOFS_X; + data[1].dwOfs = DIJOFS_Y; + data[2].dwOfs = DIJOFS_Z; + data[3].dwOfs = DIJOFS_RX; + data[4].dwOfs = DIJOFS_RY; + data[5].dwOfs = DIJOFS_RZ; + data[6].dwOfs = DIJOFS_SLIDER(0); + data[7].dwOfs = DIJOFS_SLIDER(1); + data[8].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVX); + data[9].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVY); + data[10].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVZ); + data[11].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRx); + data[12].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRy); + data[13].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRz); + data[14].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[0]); + data[15].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[1]); + data[16].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAX); + data[17].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAY); + data[18].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAZ); + data[19].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARx); + data[20].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARy); + data[21].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARz); + data[22].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[0]); + data[23].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[1]); + data[24].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFX); + data[25].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFY); + data[26].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFZ); + data[27].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRx); + data[28].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRy); + data[29].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRz); + data[30].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[0]); + data[31].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[1]); + + for (std::size_t i = 0; i < 8 * 4; ++i) + { + data[i].dwType = axisType; + data[i].dwFlags = 0; + } + + for (std::size_t i = 0; i < 4; ++i) + { + data[8 * 4 + i].pguid = &guids::GUID_POV; + data[8 * 4 + i].dwOfs = static_cast(DIJOFS_POV(static_cast(i))); + data[8 * 4 + i].dwType = povType; + data[8 * 4 + i].dwFlags = 0; + } + + for (unsigned int i = 0; i < sf::Joystick::ButtonCount; ++i) + { + data[8 * 4 + 4 + i].pguid = nullptr; + data[8 * 4 + 4 + i].dwOfs = static_cast(DIJOFS_BUTTON(i)); + data[8 * 4 + 4 + i].dwType = buttonType; + data[8 * 4 + 4 + i].dwFlags = 0; + } + + format.dwSize = sizeof(DIDATAFORMAT); + format.dwObjSize = sizeof(DIOBJECTDATAFORMAT); + format.dwFlags = DIDFT_ABSAXIS; + format.dwDataSize = sizeof(DIJOYSTATE2); + format.dwNumObjs = 8 * 4 + 4 + sf::Joystick::ButtonCount; + format.rgodf = data.data(); + + formatInitialized = true; + } + + // Set device data format + result = m_device->SetDataFormat(&format); + + if (FAILED(result)) + { + err() << "Failed to set DirectInput device data format: " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + // Get device capabilities + result = m_device->GetCapabilities(&m_deviceCaps); + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device capabilities: " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + // Enumerate device objects (axes/povs/buttons) + result = m_device->EnumObjects(&JoystickImpl::deviceObjectEnumerationCallback, + this, + DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV); + + if (FAILED(result)) + { + err() << "Failed to enumerate DirectInput device objects: " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + // Set device's axis mode to absolute if the device reports having at least one axis + for (const int axis : m_axes) + { + if (axis != -1) + { + property = {}; + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.diph.dwObj = 0; + + result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph); + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device axis mode for device " + << std::quoted(m_identification.name.toAnsiString()) << ": " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + // If the axis mode is already set to absolute we don't need to set it again ourselves + if (property.dwData == DIPROPAXISMODE_ABS) + break; + + property = {}; + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.dwData = DIPROPAXISMODE_ABS; + + m_device->SetProperty(DIPROP_AXISMODE, &property.diph); + + // Check if the axis mode has been set to absolute + property = {}; + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.diph.dwObj = 0; + + result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph); + + if (FAILED(result)) + { + err() << "Failed to verify DirectInput device axis mode for device " + << std::quoted(m_identification.name.toAnsiString()) << ": " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + // If the axis mode hasn't been set to absolute fail here and blacklist the device + if (property.dwData != DIPROPAXISMODE_ABS) + { + if (m_identification.vendorId && m_identification.productId) + { + JoystickBlacklistEntry entry{}; + entry.vendorId = m_identification.vendorId; + entry.productId = m_identification.productId; + + joystickBlacklist.push_back(entry); + joystickBlacklist.shrink_to_fit(); + } + + m_device->Release(); + m_device = nullptr; + + return false; + } + + break; + } + } + + // Try to enable buffering by setting the buffer size + property = {}; + property.diph.dwSize = sizeof(property); + property.diph.dwHeaderSize = sizeof(property.diph); + property.diph.dwHow = DIPH_DEVICE; + property.dwData = directInputEventBufferSize; + + result = m_device->SetProperty(DIPROP_BUFFERSIZE, &property.diph); + + if (result == DI_OK) + { + // Buffering supported + m_buffered = true; + } + else if (result == DI_POLLEDDEVICE) + { + // Only polling supported + m_buffered = false; + } + else + { + err() << "Failed to set DirectInput device buffer size for device " + << std::quoted(m_identification.name.toAnsiString()) << ": " << result << std::endl; + + m_device->Release(); + m_device = nullptr; + + return false; + } + + return true; + } + } + + return false; +} + + +//////////////////////////////////////////////////////////// +void JoystickImpl::closeDInput() +{ + if (m_device) + { + // Release the device + m_device->Release(); + m_device = nullptr; + } +} + + +//////////////////////////////////////////////////////////// +JoystickCaps JoystickImpl::getCapabilitiesDInput() const +{ + JoystickCaps caps; + + // Count how many buttons have valid offsets + caps.buttonCount = 0; + + for (const int button : m_buttons) + { + if (button != -1) + ++caps.buttonCount; + } + + // Check which axes have valid offsets + for (unsigned int i = 0; i < Joystick::AxisCount; ++i) + { + const auto axis = static_cast(i); + caps.axes[axis] = (m_axes[axis] != -1); + } + + return caps; +} + + +//////////////////////////////////////////////////////////// +JoystickState JoystickImpl::updateDInputBuffered() +{ + // If we don't make it to the end of this function, mark the device as disconnected + m_state.connected = false; + + if (!m_device) + return m_state; + + std::array events{}; + DWORD eventCount = directInputEventBufferSize; + + // Try to get the device data + HRESULT result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events.data(), &eventCount, 0); + + // If we have not acquired or have lost the device, attempt to (re-)acquire it and get the device data again + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Acquire(); + result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events.data(), &eventCount, 0); + } + + // If we still can't get the device data, assume it has been disconnected + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Release(); + m_device = nullptr; + + return m_state; + } + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device data: " << result << std::endl; + + return m_state; + } + + // Iterate through all buffered events + for (DWORD i = 0; i < eventCount; ++i) + { + bool eventHandled = false; + + // Get the current state of each axis + for (unsigned int j = 0; j < Joystick::AxisCount; ++j) + { + const auto axis = static_cast(j); + if (m_axes[axis] == static_cast(events[i].dwOfs)) + { + if ((axis == Joystick::Axis::PovX) || (axis == Joystick::Axis::PovY)) + { + const unsigned short value = LOWORD(events[i].dwData); + + if (value != 0xFFFF) + { + const float angle = (static_cast(value)) * 3.141592654f / DI_DEGREES / 180.f; + + m_state.axes[Joystick::Axis::PovX] = std::sin(angle) * 100.f; + m_state.axes[Joystick::Axis::PovY] = std::cos(angle) * 100.f; + } + else + { + m_state.axes[Joystick::Axis::PovX] = 0.f; + m_state.axes[Joystick::Axis::PovY] = 0.f; + } + } + else + { + m_state.axes[axis] = (static_cast(static_cast(events[i].dwData)) + 0.5f) * 100.f / 32767.5f; + } + + eventHandled = true; + + break; + } + } + + if (eventHandled) + continue; + + // Get the current state of each button + for (unsigned int j = 0; j < Joystick::ButtonCount; ++j) + { + if (m_buttons[j] == static_cast(events[i].dwOfs)) + m_state.buttons[j] = (events[i].dwData != 0); + } + } + + m_state.connected = true; + + return m_state; +} + + +//////////////////////////////////////////////////////////// +JoystickState JoystickImpl::updateDInputPolled() +{ + JoystickState state; + + if (m_device) + { + // Poll the device + m_device->Poll(); + + DIJOYSTATE2 joystate; + + // Try to get the device state + HRESULT result = m_device->GetDeviceState(sizeof(joystate), &joystate); + + // If we have not acquired or have lost the device, attempt to (re-)acquire it and get the device state again + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Acquire(); + m_device->Poll(); + result = m_device->GetDeviceState(sizeof(joystate), &joystate); + } + + // If we still can't get the device state, assume it has been disconnected + if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST)) + { + m_device->Release(); + m_device = nullptr; + + return state; + } + + if (FAILED(result)) + { + err() << "Failed to get DirectInput device state: " << result << std::endl; + + return state; + } + + // Get the current state of each axis + for (unsigned int i = 0; i < Joystick::AxisCount; ++i) + { + const auto axis = static_cast(i); + if (m_axes[axis] != -1) + { + if ((axis == Joystick::Axis::PovX) || (axis == Joystick::Axis::PovY)) + { + const unsigned short value = LOWORD( + *reinterpret_cast(reinterpret_cast(&joystate) + m_axes[axis])); + + if (value != 0xFFFF) + { + const float angle = (static_cast(value)) * 3.141592654f / DI_DEGREES / 180.f; + + state.axes[Joystick::Axis::PovX] = std::sin(angle) * 100.f; + state.axes[Joystick::Axis::PovY] = std::cos(angle) * 100.f; + } + else + { + state.axes[Joystick::Axis::PovX] = 0.f; + state.axes[Joystick::Axis::PovY] = 0.f; + } + } + else + { + state.axes[axis] = (static_cast(*reinterpret_cast( + reinterpret_cast(&joystate) + m_axes[axis])) + + 0.5f) * + 100.f / 32767.5f; + } + } + else + { + state.axes[axis] = 0.f; + } + } + + // Get the current state of each button + for (unsigned int i = 0; i < Joystick::ButtonCount; ++i) + { + if (m_buttons[i] != -1) + { + const BYTE value = *reinterpret_cast(reinterpret_cast(&joystate) + m_buttons[i]); + + state.buttons[i] = ((value & 0x80) != 0); + } + else + { + state.buttons[i] = false; + } + } + + state.connected = true; + } + + return state; +} + + +//////////////////////////////////////////////////////////// +BOOL CALLBACK JoystickImpl::deviceEnumerationCallback(const DIDEVICEINSTANCE* deviceInstance, void*) +{ + for (JoystickRecord& record : joystickList) + { + if (record.guid == deviceInstance->guidInstance) + { + record.plugged = true; + + return DIENUM_CONTINUE; + } + } + + const JoystickRecord record = {deviceInstance->guidInstance, sf::Joystick::Count, true}; + joystickList.push_back(record); + + return DIENUM_CONTINUE; +} + + +//////////////////////////////////////////////////////////// +BOOL CALLBACK JoystickImpl::deviceObjectEnumerationCallback(const DIDEVICEOBJECTINSTANCE* deviceObjectInstance, void* userData) +{ + JoystickImpl& joystick = *reinterpret_cast(userData); + + if (DIDFT_GETTYPE(deviceObjectInstance->dwType) & DIDFT_AXIS) + { + // Axes + if (deviceObjectInstance->guidType == guids::GUID_XAxis) + joystick.m_axes[Joystick::Axis::X] = DIJOFS_X; + else if (deviceObjectInstance->guidType == guids::GUID_YAxis) + joystick.m_axes[Joystick::Axis::Y] = DIJOFS_Y; + else if (deviceObjectInstance->guidType == guids::GUID_ZAxis) + joystick.m_axes[Joystick::Axis::Z] = DIJOFS_Z; + else if (deviceObjectInstance->guidType == guids::GUID_RzAxis) + joystick.m_axes[Joystick::Axis::R] = DIJOFS_RZ; + else if (deviceObjectInstance->guidType == guids::GUID_RxAxis) + joystick.m_axes[Joystick::Axis::U] = DIJOFS_RX; + else if (deviceObjectInstance->guidType == guids::GUID_RyAxis) + joystick.m_axes[Joystick::Axis::V] = DIJOFS_RY; + else if (deviceObjectInstance->guidType == guids::GUID_Slider) + { + if (joystick.m_axes[Joystick::Axis::U] == -1) + joystick.m_axes[Joystick::Axis::U] = DIJOFS_SLIDER(0); + else + joystick.m_axes[Joystick::Axis::V] = DIJOFS_SLIDER(1); + } + else + return DIENUM_CONTINUE; + + // Set the axis' value range to that of a signed short: [-32768, 32767] + auto propertyRange = DIPROPRANGE(); + propertyRange.diph.dwSize = sizeof(propertyRange); + propertyRange.diph.dwHeaderSize = sizeof(propertyRange.diph); + propertyRange.diph.dwObj = deviceObjectInstance->dwType; + propertyRange.diph.dwHow = DIPH_BYID; + propertyRange.lMin = -32768; + propertyRange.lMax = 32767; + + const HRESULT result = joystick.m_device->SetProperty(DIPROP_RANGE, &propertyRange.diph); + + if (result != DI_OK) + err() << "Failed to set DirectInput device axis property range: " << result << std::endl; + + return DIENUM_CONTINUE; + } + if (DIDFT_GETTYPE(deviceObjectInstance->dwType) & DIDFT_POV) + { + // POVs + if (deviceObjectInstance->guidType == guids::GUID_POV) + { + if (joystick.m_axes[Joystick::Axis::PovX] == -1) + { + joystick.m_axes[Joystick::Axis::PovX] = DIJOFS_POV(0); + joystick.m_axes[Joystick::Axis::PovY] = DIJOFS_POV(0); + } + } + + return DIENUM_CONTINUE; + } + if (DIDFT_GETTYPE(deviceObjectInstance->dwType) & DIDFT_BUTTON) + { + // Buttons + for (unsigned int i = 0; i < Joystick::ButtonCount; ++i) + { + if (joystick.m_buttons[i] == -1) + { + joystick.m_buttons[i] = DIJOFS_BUTTON(static_cast(i)); + break; + } + } + + return DIENUM_CONTINUE; + } + + return DIENUM_CONTINUE; +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.hpp b/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.hpp new file mode 100644 index 0000000..b60ae8d --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/JoystickImpl.hpp @@ -0,0 +1,227 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +/// \brief Windows implementation of joysticks +/// +//////////////////////////////////////////////////////////// +class JoystickImpl +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Perform the global initialization of the joystick module + /// + //////////////////////////////////////////////////////////// + static void initialize(); + + //////////////////////////////////////////////////////////// + /// \brief Perform the global cleanup of the joystick module + /// + //////////////////////////////////////////////////////////// + static void cleanup(); + + //////////////////////////////////////////////////////////// + /// \brief Check if a joystick is currently connected + /// + /// \param index Index of the joystick to check + /// + /// \return `true` if the joystick is connected, `false` otherwise + /// + //////////////////////////////////////////////////////////// + static bool isConnected(unsigned int index); + + //////////////////////////////////////////////////////////// + /// \brief Enable or disable lazy enumeration updates + /// + /// \param status Whether to rely on windows triggering enumeration updates + /// + //////////////////////////////////////////////////////////// + static void setLazyUpdates(bool status); + + //////////////////////////////////////////////////////////// + /// \brief Update the connection status of all joysticks + /// + //////////////////////////////////////////////////////////// + static void updateConnections(); + + //////////////////////////////////////////////////////////// + /// \brief Open the joystick + /// + /// \param index Index assigned to the joystick + /// + /// \return `true` on success, `false` on failure + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool open(unsigned int index); + + //////////////////////////////////////////////////////////// + /// \brief Close the joystick + /// + //////////////////////////////////////////////////////////// + void close(); + + //////////////////////////////////////////////////////////// + /// \brief Get the joystick capabilities + /// + /// \return Joystick capabilities + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] JoystickCaps getCapabilities() const; + + //////////////////////////////////////////////////////////// + /// \brief Get the joystick identification + /// + /// \return Joystick identification + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Joystick::Identification getIdentification() const; + + //////////////////////////////////////////////////////////// + /// \brief Update the joystick and get its new state + /// + /// \return Joystick state + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] JoystickState update(); + + //////////////////////////////////////////////////////////// + /// \brief Perform the global initialization of the joystick module (DInput) + /// + //////////////////////////////////////////////////////////// + static void initializeDInput(); + + //////////////////////////////////////////////////////////// + /// \brief Perform the global cleanup of the joystick module (DInput) + /// + //////////////////////////////////////////////////////////// + static void cleanupDInput(); + + //////////////////////////////////////////////////////////// + /// \brief Check if a joystick is currently connected (DInput) + /// + /// \param index Index of the joystick to check + /// + /// \return `true` if the joystick is connected, `false` otherwise + /// + //////////////////////////////////////////////////////////// + static bool isConnectedDInput(unsigned int index); + + //////////////////////////////////////////////////////////// + /// \brief Update the connection status of all joysticks (DInput) + /// + //////////////////////////////////////////////////////////// + static void updateConnectionsDInput(); + + //////////////////////////////////////////////////////////// + /// \brief Open the joystick (DInput) + /// + /// \param index Index assigned to the joystick + /// + /// \return `true` on success, `false` on failure + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool openDInput(unsigned int index); + + //////////////////////////////////////////////////////////// + /// \brief Close the joystick (DInput) + /// + //////////////////////////////////////////////////////////// + void closeDInput(); + + //////////////////////////////////////////////////////////// + /// \brief Get the joystick capabilities (DInput) + /// + /// \return Joystick capabilities + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] JoystickCaps getCapabilitiesDInput() const; + + //////////////////////////////////////////////////////////// + /// \brief Update the joystick and get its new state (DInput, Buffered) + /// + /// \return Joystick state + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] JoystickState updateDInputBuffered(); + + //////////////////////////////////////////////////////////// + /// \brief Update the joystick and get its new state (DInput, Polled) + /// + /// \return Joystick state + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] JoystickState updateDInputPolled(); + +private: + //////////////////////////////////////////////////////////// + /// \brief Device enumeration callback function passed to EnumDevices in updateConnections + /// + /// \param deviceInstance Device object instance + /// \param userData User data (unused) + /// + /// \return DIENUM_CONTINUE to continue enumerating devices or DIENUM_STOP to stop + /// + //////////////////////////////////////////////////////////// + static BOOL CALLBACK deviceEnumerationCallback(const DIDEVICEINSTANCE* deviceInstance, void* userData); + + //////////////////////////////////////////////////////////// + /// \brief Device object enumeration callback function passed to EnumObjects in open + /// + /// \param deviceObjectInstance Device object instance + /// \param userData User data (pointer to our JoystickImpl object) + /// + /// \return DIENUM_CONTINUE to continue enumerating objects or DIENUM_STOP to stop + /// + //////////////////////////////////////////////////////////// + static BOOL CALLBACK deviceObjectEnumerationCallback(const DIDEVICEOBJECTINSTANCE* deviceObjectInstance, void* userData); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + unsigned int m_index{}; //!< Index of the joystick + JOYCAPS m_caps{}; //!< Joystick capabilities + IDirectInputDevice8W* m_device{}; //!< DirectInput 8.x device + DIDEVCAPS m_deviceCaps{}; //!< DirectInput device capabilities + EnumArray m_axes{}; //!< Offsets to the bytes containing the axes states, -1 if not available + std::array m_buttons{}; //!< Offsets to the bytes containing the button states, -1 if not available + Joystick::Identification m_identification; //!< Joystick identification + JoystickState m_state; //!< Buffered joystick state + bool m_buffered{}; //!< `true` if the device uses buffering, `false` if the device uses polling +}; + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/SensorImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/SensorImpl.cpp new file mode 100644 index 0000000..0e357ca --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/SensorImpl.cpp @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +void SensorImpl::initialize() +{ + // TODO: not implemented +} + + +//////////////////////////////////////////////////////////// +void SensorImpl::cleanup() +{ + // TODO: not implemented +} + + +//////////////////////////////////////////////////////////// +bool SensorImpl::isAvailable(Sensor::Type /*sensor*/) +{ + // TODO: not implemented + return false; +} + + +//////////////////////////////////////////////////////////// +bool SensorImpl::open(Sensor::Type /*sensor*/) +{ + // TODO: not implemented + return false; +} + + +//////////////////////////////////////////////////////////// +void SensorImpl::close() +{ + // TODO: not implemented +} + + +//////////////////////////////////////////////////////////// +Vector3f SensorImpl::update() +{ + // TODO: not implemented + return {}; +} + + +//////////////////////////////////////////////////////////// +void SensorImpl::setEnabled(bool /*enabled*/) +{ + // TODO: not implemented +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/SensorImpl.hpp b/vendor/SFML/src/SFML/Window/Win32/SensorImpl.hpp new file mode 100644 index 0000000..e3baec7 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/SensorImpl.hpp @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +/// \brief Windows implementation of sensors +/// +//////////////////////////////////////////////////////////// +class SensorImpl +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Perform the global initialization of the sensor module + /// + //////////////////////////////////////////////////////////// + static void initialize(); + + //////////////////////////////////////////////////////////// + /// \brief Perform the global cleanup of the sensor module + /// + //////////////////////////////////////////////////////////// + static void cleanup(); + + //////////////////////////////////////////////////////////// + /// \brief Check if a sensor is available + /// + /// \param sensor Sensor to check + /// + /// \return `true` if the sensor is available, `false` otherwise + /// + //////////////////////////////////////////////////////////// + static bool isAvailable(Sensor::Type sensor); + + //////////////////////////////////////////////////////////// + /// \brief Open the sensor + /// + /// \param sensor Type of the sensor + /// + /// \return `true` on success, `false` on failure + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool open(Sensor::Type sensor); + + //////////////////////////////////////////////////////////// + /// \brief Close the sensor + /// + //////////////////////////////////////////////////////////// + void close(); + + //////////////////////////////////////////////////////////// + /// \brief Update the sensor and get its new value + /// + /// \return Sensor value + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Vector3f update(); + + //////////////////////////////////////////////////////////// + /// \brief Enable or disable the sensor + /// + /// \param enabled `true` to enable, `false` to disable + /// + //////////////////////////////////////////////////////////// + void setEnabled(bool enabled); +}; + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/Utils.hpp b/vendor/SFML/src/SFML/Window/Win32/Utils.hpp new file mode 100644 index 0000000..24606ad --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/Utils.hpp @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +namespace sf::priv +{ +inline std::string getErrorString(DWORD error) +{ + PTCHAR buffer = nullptr; + if (FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + error, + 0, + reinterpret_cast(&buffer), + 0, + nullptr) == 0) + { + return "Unknown error."; + } + + const sf::String message = buffer; + LocalFree(buffer); + return message.toAnsiString(); +} +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/VideoModeImpl.cpp b/vendor/SFML/src/SFML/Window/Win32/VideoModeImpl.cpp new file mode 100644 index 0000000..cfbadc7 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/VideoModeImpl.cpp @@ -0,0 +1,71 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +std::vector VideoModeImpl::getFullscreenModes() +{ + std::vector modes; + + // Enumerate all available video modes for the primary display adapter + DEVMODE win32Mode; + win32Mode.dmSize = sizeof(win32Mode); + win32Mode.dmDriverExtra = 0; + for (int count = 0; EnumDisplaySettings(nullptr, static_cast(count), &win32Mode); ++count) + { + // Convert to sf::VideoMode + const VideoMode mode({win32Mode.dmPelsWidth, win32Mode.dmPelsHeight}, win32Mode.dmBitsPerPel); + + // Add it only if it is not already in the array + if (std::find(modes.begin(), modes.end(), mode) == modes.end()) + modes.push_back(mode); + } + + return modes; +} + + +//////////////////////////////////////////////////////////// +VideoMode VideoModeImpl::getDesktopMode() +{ + DEVMODE win32Mode; + win32Mode.dmSize = sizeof(win32Mode); + win32Mode.dmDriverExtra = 0; + EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &win32Mode); + + return VideoMode({win32Mode.dmPelsWidth, win32Mode.dmPelsHeight}, win32Mode.dmBitsPerPel); +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/VulkanImplWin32.cpp b/vendor/SFML/src/SFML/Window/Win32/VulkanImplWin32.cpp new file mode 100644 index 0000000..6e83b2d --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/VulkanImplWin32.cpp @@ -0,0 +1,209 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include + +#include + +#include + +#include + +#define VK_USE_PLATFORM_WIN32_KHR +#define VK_NO_PROTOTYPES +#include + + +namespace +{ +struct VulkanLibraryWrapper +{ + ~VulkanLibraryWrapper() + { + if (library) + FreeLibrary(library); + } + + // Try to load the library and all the required entry points + bool loadLibrary() + { + if (library) + return true; + + library = LoadLibraryA("vulkan-1.dll"); + + if (!library) + return false; + + if (!loadEntryPoint(vkGetInstanceProcAddr, "vkGetInstanceProcAddr")) + { + FreeLibrary(library); + library = nullptr; + return false; + } + + if (!loadEntryPoint(vkEnumerateInstanceLayerProperties, "vkEnumerateInstanceLayerProperties")) + { + FreeLibrary(library); + library = nullptr; + return false; + } + + if (!loadEntryPoint(vkEnumerateInstanceExtensionProperties, "vkEnumerateInstanceExtensionProperties")) + { + FreeLibrary(library); + library = nullptr; + return false; + } + + return true; + } + + template + bool loadEntryPoint(T& entryPoint, const char* name) + { + entryPoint = reinterpret_cast(reinterpret_cast(GetProcAddress(library, name))); + + return entryPoint != nullptr; + } + + HMODULE library{}; + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; + PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties{}; + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties{}; +}; + +VulkanLibraryWrapper wrapper; +} // namespace + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +bool VulkanImpl::isAvailable(bool requireGraphics) +{ + static bool checked = false; + static bool computeAvailable = false; + static bool graphicsAvailable = false; + + if (!checked) + { + checked = true; + + // Check if the library is available + computeAvailable = wrapper.loadLibrary(); + + // To check for instance extensions we don't need to differentiate between graphics and compute + graphicsAvailable = computeAvailable; + + if (graphicsAvailable) + { + // Retrieve the available instance extensions + std::vector extensionProperties; + + std::uint32_t extensionCount = 0; + + wrapper.vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + extensionProperties.resize(extensionCount); + + wrapper.vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionProperties.data()); + + // Check if the necessary extensions are available + bool hasVkKhrSurface = false; + bool hasVkKhrPlatformSurface = false; + + for (const VkExtensionProperties& properties : extensionProperties) + { + if (std::string_view(properties.extensionName) == VK_KHR_SURFACE_EXTENSION_NAME) + { + hasVkKhrSurface = true; + } + else if (std::string_view(properties.extensionName) == VK_KHR_WIN32_SURFACE_EXTENSION_NAME) + { + hasVkKhrPlatformSurface = true; + } + } + + if (!hasVkKhrSurface || !hasVkKhrPlatformSurface) + graphicsAvailable = false; + } + } + + if (requireGraphics) + return graphicsAvailable; + + return computeAvailable; +} + + +//////////////////////////////////////////////////////////// +VulkanFunctionPointer VulkanImpl::getFunction(const char* name) +{ + if (!isAvailable(false)) + return nullptr; + + return reinterpret_cast(GetProcAddress(wrapper.library, name)); +} + + +//////////////////////////////////////////////////////////// +const std::vector& VulkanImpl::getGraphicsRequiredInstanceExtensions() +{ + static const std::vector extensions{VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME}; + return extensions; +} + + +//////////////////////////////////////////////////////////// +bool VulkanImpl::createVulkanSurface(const VkInstance& instance, + WindowHandle windowHandle, + VkSurfaceKHR& surface, + const VkAllocationCallbacks* allocator) +{ + if (!isAvailable()) + return false; + + // Make a copy of the instance handle since we get it passed as a reference + VkInstance inst = instance; + + auto vkCreateWin32SurfaceKHR = reinterpret_cast( + wrapper.vkGetInstanceProcAddr(inst, "vkCreateWin32SurfaceKHR")); + + if (!vkCreateWin32SurfaceKHR) + return false; + + VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR(); + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surfaceCreateInfo.hinstance = GetModuleHandleA(nullptr); + surfaceCreateInfo.hwnd = windowHandle; + + return vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, allocator, &surface) == VK_SUCCESS; +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/WglContext.cpp b/vendor/SFML/src/SFML/Window/Win32/WglContext.cpp new file mode 100644 index 0000000..3549c0d --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/WglContext.cpp @@ -0,0 +1,849 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +// We check for this definition in order to avoid multiple definitions of GLAD +// entities during unity builds of SFML. +#ifndef SF_GLAD_WGL_IMPLEMENTATION_INCLUDED +#define SF_GLAD_WGL_IMPLEMENTATION_INCLUDED +#define SF_GLAD_WGL_IMPLEMENTATION +#include +#endif + +namespace +{ +namespace WglContextImpl +{ +// Some drivers are bugged and don't track the current HDC/HGLRC properly +// In order to deactivate successfully, we need to track it ourselves as well +thread_local sf::priv::WglContext* currentContext(nullptr); + + +// We use a different loader for wgl functions since we load them directly from OpenGL32.dll +sf::GlFunctionPointer getOpenGl32Function(const char* name) +{ + static const HMODULE module = GetModuleHandleA("OpenGL32.dll"); + + if (module) + return reinterpret_cast(GetProcAddress(module, reinterpret_cast(name))); + + return nullptr; +} + + +//////////////////////////////////////////////////////////// +void ensureInit() +{ + static bool initialized = false; + if (!initialized) + { + initialized = true; + + gladLoadWGL(nullptr, getOpenGl32Function); + } +} + + +//////////////////////////////////////////////////////////// +void ensureExtensionsInit(HDC deviceContext) +{ + static bool initialized = false; + if (!initialized) + { + initialized = true; + + // We don't check the return value since the extension + // flags are cleared even if loading fails + gladLoadWGL(deviceContext, sf::priv::WglContext::getFunction); + } +} +} // namespace WglContextImpl +} // namespace + + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +WglContext::WglContext(WglContext* shared) : WglContext(shared, ContextSettings{}, {1u, 1u}) +{ +} + + +//////////////////////////////////////////////////////////// +WglContext::WglContext(WglContext* shared, const ContextSettings& settings, const WindowImpl& owner, unsigned int bitsPerPixel) +{ + WglContextImpl::ensureInit(); + + // Save the creation settings + m_settings = settings; + + // Create the rendering surface from the owner window + createSurface(owner.getNativeHandle(), bitsPerPixel); + + // Create the context + createContext(shared); +} + + +//////////////////////////////////////////////////////////// +WglContext::WglContext(WglContext* shared, const ContextSettings& settings, Vector2u size) +{ + WglContextImpl::ensureInit(); + + // Save the creation settings + m_settings = settings; + + // Create the rendering surface (window or pbuffer if supported) + createSurface(shared, size, VideoMode::getDesktopMode().bitsPerPixel); + + // Create the context + createContext(shared); +} + + +//////////////////////////////////////////////////////////// +WglContext::~WglContext() +{ + // Notify unshared OpenGL resources of context destruction + cleanupUnsharedResources(); + + // Destroy the OpenGL context + if (m_context) + { + if (WglContextImpl::currentContext == this) + { + if (wglMakeCurrent(m_deviceContext, nullptr) == TRUE) + WglContextImpl::currentContext = nullptr; + } + + wglDeleteContext(m_context); + } + + // Destroy the device context + if (m_deviceContext) + { + if (m_pbuffer) + { + wglReleasePbufferDCARB(m_pbuffer, m_deviceContext); + wglDestroyPbufferARB(m_pbuffer); + } + else + { + ReleaseDC(m_window, m_deviceContext); + } + } + + // Destroy the window if we own it + if (m_window && m_ownsWindow) + DestroyWindow(m_window); +} + + +//////////////////////////////////////////////////////////// +GlFunctionPointer WglContext::getFunction(const char* name) +{ + if (WglContextImpl::currentContext == nullptr) + return nullptr; + + // If we are using the generic GDI implementation, skip to loading directly from OpenGL32.dll since it doesn't support extensions + if (!WglContextImpl::currentContext->m_isGeneric) + { + auto address = reinterpret_cast(wglGetProcAddress(reinterpret_cast(name))); + + if (address) + { + // Test whether the returned value is a valid error code + auto errorCode = reinterpret_cast(address); + + if ((errorCode != -1) && (errorCode != 1) && (errorCode != 2) && (errorCode != 3)) + return address; + } + } + + return WglContextImpl::getOpenGl32Function(name); +} + + +//////////////////////////////////////////////////////////// +bool WglContext::makeCurrent(bool current) +{ + if (!m_deviceContext || !m_context) + return false; + + if (wglMakeCurrent(m_deviceContext, current ? m_context : nullptr) == FALSE) + { + err() << "Failed to " << (current ? "activate" : "deactivate") + << " OpenGL context: " << getErrorString(GetLastError()) << std::endl; + return false; + } + + WglContextImpl::currentContext = (current ? this : nullptr); + + return true; +} + + +//////////////////////////////////////////////////////////// +void WglContext::display() +{ + if (m_deviceContext && m_context) + SwapBuffers(m_deviceContext); +} + + +//////////////////////////////////////////////////////////// +void WglContext::setVerticalSyncEnabled(bool enabled) +{ + // Make sure that extensions are initialized + WglContextImpl::ensureExtensionsInit(m_deviceContext); + + if (SF_GLAD_WGL_EXT_swap_control) + { + if (wglSwapIntervalEXT(enabled) == FALSE) + err() << "Setting vertical sync failed: " << getErrorString(GetLastError()) << std::endl; + } + else + { + static bool warned = false; + + if (!warned) + { + // wglSwapIntervalEXT not supported + err() << "Setting vertical sync not supported" << std::endl; + + warned = true; + } + } +} + + +//////////////////////////////////////////////////////////// +int WglContext::selectBestPixelFormat(HDC deviceContext, unsigned int bitsPerPixel, const ContextSettings& settings, bool pbuffer) +{ + // Selecting a pixel format can be an expensive process on some implementations + // Since the same pixel format should always be selected for a specific combination of inputs + // we can cache the result of the lookup instead of having to perform it multiple times for the same inputs + struct PixelFormatCacheEntry + { + unsigned int bitsPerPixel{}; + unsigned int depthBits{}; + unsigned int stencilBits{}; + unsigned int antiAliasingLevel{}; + bool pbuffer{}; + int bestFormat{}; + }; + + static std::mutex cacheMutex; + static std::vector pixelFormatCache; + + // Check if we have already previously found a pixel format for + // the current inputs and return it if one has been previously found + { + const std::lock_guard lock(cacheMutex); + + for (const auto& entry : pixelFormatCache) + { + if (bitsPerPixel == entry.bitsPerPixel && settings.depthBits == entry.depthBits && + settings.stencilBits == entry.stencilBits && settings.antiAliasingLevel == entry.antiAliasingLevel && + pbuffer == entry.pbuffer) + return entry.bestFormat; + } + } + + WglContextImpl::ensureInit(); + + // Find a suitable pixel format -- first try with wglChoosePixelFormatARB + int bestFormat = 0; + if (SF_GLAD_WGL_ARB_pixel_format) + { + // Define the basic attributes we want for our window + static constexpr std::array intAttributes = + {WGL_DRAW_TO_WINDOW_ARB, + GL_TRUE, + WGL_SUPPORT_OPENGL_ARB, + GL_TRUE, + WGL_DOUBLE_BUFFER_ARB, + GL_TRUE, + WGL_PIXEL_TYPE_ARB, + WGL_TYPE_RGBA_ARB, + 0, + 0}; + + // Check how many formats are supporting our requirements + std::array formats{}; + UINT nbFormats = 0; // We must initialize to 0 otherwise broken drivers might fill with garbage in the following call + const bool isValid = wglChoosePixelFormatARB(deviceContext, + intAttributes.data(), + nullptr, + static_cast(formats.size()), + formats.data(), + &nbFormats) != FALSE; + + if (!isValid) + err() << "Failed to enumerate pixel formats: " << getErrorString(GetLastError()) << std::endl; + + // Get the best format among the returned ones + if (isValid && (nbFormats > 0)) + { + int bestScore = 0x7FFFFFFF; + for (UINT i = 0; i < nbFormats; ++i) + { + // Extract the components of the current format + std::array values{}; + static constexpr std::array attributes = + {WGL_RED_BITS_ARB, + WGL_GREEN_BITS_ARB, + WGL_BLUE_BITS_ARB, + WGL_ALPHA_BITS_ARB, + WGL_DEPTH_BITS_ARB, + WGL_STENCIL_BITS_ARB, + WGL_ACCELERATION_ARB}; + + if (wglGetPixelFormatAttribivARB(deviceContext, + formats[i], + PFD_MAIN_PLANE, + static_cast(values.size()), + attributes.data(), + values.data()) == FALSE) + { + err() << "Failed to retrieve pixel format information: " << getErrorString(GetLastError()) << std::endl; + break; + } + + std::array sampleValues = {0, 0}; + if (SF_GLAD_WGL_ARB_multisample) + { + static constexpr std::array sampleAttributes = {WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB}; + + if (wglGetPixelFormatAttribivARB(deviceContext, + formats[i], + PFD_MAIN_PLANE, + static_cast(sampleAttributes.size()), + sampleAttributes.data(), + sampleValues.data()) == FALSE) + { + err() << "Failed to retrieve pixel format multisampling information: " + << getErrorString(GetLastError()) << std::endl; + break; + } + } + + int sRgbCapableValue = 0; + if (SF_GLAD_WGL_ARB_framebuffer_sRGB || SF_GLAD_WGL_EXT_framebuffer_sRGB) + { + const int sRgbCapableAttribute = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; + + if (wglGetPixelFormatAttribivARB(deviceContext, formats[i], PFD_MAIN_PLANE, 1, &sRgbCapableAttribute, &sRgbCapableValue) == + FALSE) + { + err() << "Failed to retrieve pixel format sRGB capability information: " + << getErrorString(GetLastError()) << std::endl; + break; + } + } + + if (pbuffer) + { + static constexpr std::array pbufferAttributes = {WGL_DRAW_TO_PBUFFER_ARB}; + + int pbufferValue = 0; + + if (wglGetPixelFormatAttribivARB(deviceContext, + formats[i], + PFD_MAIN_PLANE, + static_cast(pbufferAttributes.size()), + pbufferAttributes.data(), + &pbufferValue) == FALSE) + { + err() << "Failed to retrieve pixel format pbuffer information: " << getErrorString(GetLastError()) + << std::endl; + break; + } + + if (pbufferValue != GL_TRUE) + continue; + } + + // Evaluate the current configuration + const int color = values[0] + values[1] + values[2] + values[3]; + const int score = evaluateFormat(bitsPerPixel, + settings, + color, + values[4], + values[5], + sampleValues[0] ? sampleValues[1] : 0, + values[6] == WGL_FULL_ACCELERATION_ARB, + sRgbCapableValue == TRUE); + + // Keep it if it's better than the current best + if (score < bestScore) + { + bestScore = score; + bestFormat = formats[i]; + } + } + } + } + + // Find a pixel format with ChoosePixelFormat, if wglChoosePixelFormatARB is not supported + // ChoosePixelFormat doesn't support pbuffers + if ((bestFormat == 0) && !pbuffer) + { + // Setup a pixel format descriptor from the rendering settings + PIXELFORMATDESCRIPTOR descriptor; + ZeroMemory(&descriptor, sizeof(descriptor)); + descriptor.nSize = sizeof(descriptor); + descriptor.nVersion = 1; + descriptor.iLayerType = PFD_MAIN_PLANE; + descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + descriptor.iPixelType = PFD_TYPE_RGBA; + descriptor.cColorBits = static_cast(bitsPerPixel); + descriptor.cDepthBits = static_cast(settings.depthBits); + descriptor.cStencilBits = static_cast(settings.stencilBits); + descriptor.cAlphaBits = bitsPerPixel == 32 ? 8 : 0; + + // Get the pixel format that best matches our requirements + bestFormat = ChoosePixelFormat(deviceContext, &descriptor); + } + + // If we get this far, the format wasn't found in the cache so add it here + { + const std::lock_guard lock(cacheMutex); + + pixelFormatCache.emplace_back( + PixelFormatCacheEntry{bitsPerPixel, settings.depthBits, settings.stencilBits, settings.antiAliasingLevel, pbuffer, bestFormat}); + } + + return bestFormat; +} + + +//////////////////////////////////////////////////////////// +void WglContext::setDevicePixelFormat(unsigned int bitsPerPixel) +{ + const int bestFormat = selectBestPixelFormat(m_deviceContext, bitsPerPixel, m_settings); + + if (bestFormat == 0) + { + err() << "Failed to find a suitable pixel format for device context: " << getErrorString(GetLastError()) << '\n' + << "Cannot create OpenGL context" << std::endl; + return; + } + + // Extract the depth and stencil bits from the chosen format + PIXELFORMATDESCRIPTOR actualFormat; + actualFormat.nSize = sizeof(actualFormat); + actualFormat.nVersion = 1; + DescribePixelFormat(m_deviceContext, bestFormat, sizeof(actualFormat), &actualFormat); + + // Set the chosen pixel format + if (SetPixelFormat(m_deviceContext, bestFormat, &actualFormat) == FALSE) + { + err() << "Failed to set pixel format for device context: " << getErrorString(GetLastError()) << '\n' + << "Cannot create OpenGL context" << std::endl; + return; + } +} + + +//////////////////////////////////////////////////////////// +void WglContext::updateSettingsFromPixelFormat() +{ + const int format = GetPixelFormat(m_deviceContext); + + if (format == 0) + { + err() << "Failed to get selected pixel format: " << getErrorString(GetLastError()) << std::endl; + return; + } + + PIXELFORMATDESCRIPTOR actualFormat; + actualFormat.nSize = sizeof(actualFormat); + actualFormat.nVersion = 1; + + if (DescribePixelFormat(m_deviceContext, format, sizeof(actualFormat), &actualFormat) == 0) + { + err() << "Failed to retrieve pixel format information: " << getErrorString(GetLastError()) << std::endl; + return; + } + + // Detect if we are running using the generic GDI implementation and warn + if (actualFormat.dwFlags & PFD_GENERIC_FORMAT) + { + m_isGeneric = true; + + err() << "Warning: Detected \"Microsoft Corporation GDI Generic\" OpenGL implementation" << std::endl; + + // Detect if the generic GDI implementation is not accelerated + if (!(actualFormat.dwFlags & PFD_GENERIC_ACCELERATED)) + err() << "Warning: The \"Microsoft Corporation GDI Generic\" OpenGL implementation is not " + "hardware-accelerated" + << std::endl; + } + + if (SF_GLAD_WGL_ARB_pixel_format) + { + static constexpr std::array attributes = {WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB}; + std::array values{}; + + if (wglGetPixelFormatAttribivARB(m_deviceContext, + format, + PFD_MAIN_PLANE, + static_cast(attributes.size()), + attributes.data(), + values.data()) == TRUE) + { + m_settings.depthBits = static_cast(values[0]); + m_settings.stencilBits = static_cast(values[1]); + } + else + { + err() << "Failed to retrieve pixel format information: " << getErrorString(GetLastError()) << std::endl; + m_settings.depthBits = actualFormat.cDepthBits; + m_settings.stencilBits = actualFormat.cStencilBits; + } + + if (SF_GLAD_WGL_ARB_multisample) + { + static constexpr std::array sampleAttributes = {WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB}; + std::array sampleValues{}; + + if (wglGetPixelFormatAttribivARB(m_deviceContext, + format, + PFD_MAIN_PLANE, + static_cast(sampleAttributes.size()), + sampleAttributes.data(), + sampleValues.data()) == TRUE) + { + m_settings.antiAliasingLevel = static_cast(sampleValues[0] ? sampleValues[1] : 0); + } + else + { + err() << "Failed to retrieve pixel format multisampling information: " << getErrorString(GetLastError()) + << std::endl; + m_settings.antiAliasingLevel = 0; + } + } + else + { + m_settings.antiAliasingLevel = 0; + } + + if (SF_GLAD_WGL_ARB_framebuffer_sRGB || SF_GLAD_WGL_EXT_framebuffer_sRGB) + { + static constexpr std::array sRgbCapableAttribute = {WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB}; + int sRgbCapableValue = 0; + + if (wglGetPixelFormatAttribivARB(m_deviceContext, + format, + PFD_MAIN_PLANE, + static_cast(sRgbCapableAttribute.size()), + sRgbCapableAttribute.data(), + &sRgbCapableValue) == TRUE) + { + m_settings.sRgbCapable = (sRgbCapableValue == TRUE); + } + else + { + err() << "Failed to retrieve pixel format sRGB capability information: " << getErrorString(GetLastError()) + << std::endl; + m_settings.sRgbCapable = false; + } + } + else + { + m_settings.sRgbCapable = false; + } + } + else + { + m_settings.depthBits = actualFormat.cDepthBits; + m_settings.stencilBits = actualFormat.cStencilBits; + m_settings.antiAliasingLevel = 0; + } +} + + +//////////////////////////////////////////////////////////// +void WglContext::createSurface(WglContext* shared, Vector2u size, unsigned int bitsPerPixel) +{ + // Check if the shared context already exists and pbuffers are supported + if (shared && shared->m_deviceContext && SF_GLAD_WGL_ARB_pbuffer) + { + const int bestFormat = selectBestPixelFormat(shared->m_deviceContext, bitsPerPixel, m_settings, true); + + if (bestFormat > 0) + { + static constexpr std::array attributes = {0, 0}; + + m_pbuffer = wglCreatePbufferARB(shared->m_deviceContext, + bestFormat, + static_cast(size.x), + static_cast(size.y), + attributes.data()); + + if (m_pbuffer) + { + m_window = shared->m_window; + m_deviceContext = wglGetPbufferDCARB(m_pbuffer); + + if (!m_deviceContext) + { + err() << "Failed to retrieve pixel buffer device context: " << getErrorString(GetLastError()) + << std::endl; + + wglDestroyPbufferARB(m_pbuffer); + m_pbuffer = nullptr; + } + } + else + { + err() << "Failed to create pixel buffer: " << getErrorString(GetLastError()) << std::endl; + } + } + } + + // If pbuffers are not available we use a hidden window as the off-screen surface to draw to + if (!m_deviceContext) + { + // We can't create a memory DC, the resulting context wouldn't be compatible + // with other contexts and thus wglShareLists would always fail + + // Create the hidden window + m_window = CreateWindowA("STATIC", + "", + WS_POPUP | WS_DISABLED, + 0, + 0, + static_cast(size.x), + static_cast(size.y), + nullptr, + nullptr, + GetModuleHandle(nullptr), + nullptr); + ShowWindow(m_window, SW_HIDE); + m_deviceContext = GetDC(m_window); + + m_ownsWindow = true; + + // Set the pixel format of the device context + setDevicePixelFormat(bitsPerPixel); + } + + // Update context settings from the selected pixel format + updateSettingsFromPixelFormat(); +} + + +//////////////////////////////////////////////////////////// +void WglContext::createSurface(HWND window, unsigned int bitsPerPixel) +{ + m_window = window; + m_deviceContext = GetDC(window); + + // Set the pixel format of the device context + setDevicePixelFormat(bitsPerPixel); + + // Update context settings from the selected pixel format + updateSettingsFromPixelFormat(); +} + + +//////////////////////////////////////////////////////////// +void WglContext::createContext(WglContext* shared) +{ + // We can't create an OpenGL context if we don't have a DC + if (!m_deviceContext) + return; + + // Get a working copy of the context settings + const ContextSettings settings = m_settings; + + // Get the context to share display lists with + HGLRC sharedContext = shared ? shared->m_context : nullptr; + + // Create the OpenGL context -- first try using wglCreateContextAttribsARB + while (!m_context && m_settings.majorVersion) + { + if (SF_GLAD_WGL_ARB_create_context) + { + std::vector attributes; + + // Check if the user requested a specific context version (anything > 1.1) + if ((m_settings.majorVersion > 1) || ((m_settings.majorVersion == 1) && (m_settings.minorVersion > 1))) + { + attributes.push_back(WGL_CONTEXT_MAJOR_VERSION_ARB); + attributes.push_back(static_cast(m_settings.majorVersion)); + attributes.push_back(WGL_CONTEXT_MINOR_VERSION_ARB); + attributes.push_back(static_cast(m_settings.minorVersion)); + } + + // Check if setting the profile is supported + if (SF_GLAD_WGL_ARB_create_context_profile) + { + const int profile = (m_settings.attributeFlags & ContextSettings::Core) + ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB + : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + const int debug = (m_settings.attributeFlags & ContextSettings::Debug) ? WGL_CONTEXT_DEBUG_BIT_ARB : 0; + + attributes.push_back(WGL_CONTEXT_PROFILE_MASK_ARB); + attributes.push_back(profile); + attributes.push_back(WGL_CONTEXT_FLAGS_ARB); + attributes.push_back(debug); + } + else + { + if ((m_settings.attributeFlags & ContextSettings::Core) || + (m_settings.attributeFlags & ContextSettings::Debug)) + err() << "Selecting a profile during context creation is not supported," + << "disabling compatibility and debug" << std::endl; + + m_settings.attributeFlags = ContextSettings::Default; + } + + // Append the terminating 0 + attributes.push_back(0); + attributes.push_back(0); + + if (sharedContext) + { + static std::recursive_mutex mutex; + const std::lock_guard lock(mutex); + + if (WglContextImpl::currentContext == shared) + { + if (wglMakeCurrent(shared->m_deviceContext, nullptr) == FALSE) + { + err() << "Failed to deactivate shared context before sharing: " << getErrorString(GetLastError()) + << std::endl; + return; + } + + WglContextImpl::currentContext = nullptr; + } + } + + // Create the context + m_context = wglCreateContextAttribsARB(m_deviceContext, sharedContext, attributes.data()); + } + else + { + // If wglCreateContextAttribsARB is not supported, there is no need to keep trying + break; + } + + // If we couldn't create the context, first try disabling flags, + // then lower the version number and try again -- stop at 0.0 + // Invalid version numbers will be generated by this algorithm (like 3.9), but we really don't care + if (!m_context) + { + if (m_settings.attributeFlags != ContextSettings::Default) + { + m_settings.attributeFlags = ContextSettings::Default; + } + else if (m_settings.minorVersion > 0) + { + // If the minor version is not 0, we decrease it and try again + --m_settings.minorVersion; + + m_settings.attributeFlags = settings.attributeFlags; + } + else + { + // If the minor version is 0, we decrease the major version + --m_settings.majorVersion; + m_settings.minorVersion = 9; + + m_settings.attributeFlags = settings.attributeFlags; + } + } + } + + // If wglCreateContextAttribsARB failed, use wglCreateContext + if (!m_context) + { + // set the context version to 1.1 (arbitrary) and disable flags + m_settings.majorVersion = 1; + m_settings.minorVersion = 1; + m_settings.attributeFlags = ContextSettings::Default; + + m_context = wglCreateContext(m_deviceContext); + if (!m_context) + { + err() << "Failed to create an OpenGL context for this window: " << getErrorString(GetLastError()) << std::endl; + return; + } + + // Share this context with others + if (sharedContext) + { + // wglShareLists doesn't seem to be thread-safe + static std::recursive_mutex mutex; + const std::lock_guard lock(mutex); + + if (WglContextImpl::currentContext == shared) + { + if (wglMakeCurrent(shared->m_deviceContext, nullptr) == FALSE) + { + err() << "Failed to deactivate shared context before sharing: " << getErrorString(GetLastError()) + << std::endl; + return; + } + + WglContextImpl::currentContext = nullptr; + } + + if (wglShareLists(sharedContext, m_context) == FALSE) + err() << "Failed to share the OpenGL context: " << getErrorString(GetLastError()) << std::endl; + } + } + + // If we are the shared context, initialize extensions now + // This enables us to re-create the shared context using extensions if we need to + if (!shared && m_context) + { + makeCurrent(true); + WglContextImpl::ensureExtensionsInit(m_deviceContext); + makeCurrent(false); + } +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/WglContext.hpp b/vendor/SFML/src/SFML/Window/Win32/WglContext.hpp new file mode 100644 index 0000000..eb2e715 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/WglContext.hpp @@ -0,0 +1,197 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +#include + +#include + + +namespace sf +{ +struct ContextSettings; + +namespace priv +{ +class WindowImpl; + +//////////////////////////////////////////////////////////// +/// \brief Windows (WGL) implementation of OpenGL contexts +/// +//////////////////////////////////////////////////////////// +class WglContext : public GlContext +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Create a new default context + /// + /// \param shared Context to share the new one with (can be a null pointer) + /// + //////////////////////////////////////////////////////////// + WglContext(WglContext* shared); + + //////////////////////////////////////////////////////////// + /// \brief Create a new context attached to a window + /// + /// \param shared Context to share the new one with + /// \param settings Creation parameters + /// \param owner Pointer to the owner window + /// \param bitsPerPixel Pixel depth, in bits per pixel + /// + //////////////////////////////////////////////////////////// + WglContext(WglContext* shared, const ContextSettings& settings, const WindowImpl& owner, unsigned int bitsPerPixel); + + //////////////////////////////////////////////////////////// + /// \brief Create a new context that embeds its own rendering target + /// + /// \param shared Context to share the new one with + /// \param settings Creation parameters + /// \param size Back buffer width and height, in pixels + /// + //////////////////////////////////////////////////////////// + WglContext(WglContext* shared, const ContextSettings& settings, Vector2u size); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~WglContext() override; + + //////////////////////////////////////////////////////////// + /// \brief Get the address of an OpenGL function + /// + /// \param name Name of the function to get the address of + /// + /// \return Address of the OpenGL function, 0 on failure + /// + //////////////////////////////////////////////////////////// + static GlFunctionPointer getFunction(const char* name); + + //////////////////////////////////////////////////////////// + /// \brief Activate the context as the current target for rendering + /// + /// \param current Whether to make the context current or no longer current + /// + /// \return `true` on success, `false` if any error happened + /// + //////////////////////////////////////////////////////////// + bool makeCurrent(bool current) override; + + //////////////////////////////////////////////////////////// + /// \brief Display what has been rendered to the context so far + /// + //////////////////////////////////////////////////////////// + void display() override; + + //////////////////////////////////////////////////////////// + /// \brief Enable or disable vertical synchronization + /// + /// Activating vertical synchronization will limit the number + /// of frames displayed to the refresh rate of the monitor. + /// This can avoid some visual artifacts, and limit the framerate + /// to a good value (but not constant across different computers). + /// + /// \param enabled: `true` to enable v-sync, `false` to deactivate + /// + //////////////////////////////////////////////////////////// + void setVerticalSyncEnabled(bool enabled) override; + + //////////////////////////////////////////////////////////// + /// \brief Select the best pixel format for a given set of settings + /// + /// \param deviceContext Device context + /// \param bitsPerPixel Pixel depth, in bits per pixel + /// \param settings Requested context settings + /// \param pbuffer Whether the pixel format should support pbuffers + /// + /// \return The best pixel format + /// + //////////////////////////////////////////////////////////// + static int selectBestPixelFormat(HDC deviceContext, + unsigned int bitsPerPixel, + const ContextSettings& settings, + bool pbuffer = false); + +private: + //////////////////////////////////////////////////////////// + /// \brief Set the pixel format of the device context + /// + /// \param bitsPerPixel Pixel depth, in bits per pixel + /// + //////////////////////////////////////////////////////////// + void setDevicePixelFormat(unsigned int bitsPerPixel); + + //////////////////////////////////////////////////////////// + /// \brief Update the context settings from the selected pixel format + /// + //////////////////////////////////////////////////////////// + void updateSettingsFromPixelFormat(); + + //////////////////////////////////////////////////////////// + /// \brief Create the context's drawing surface + /// + /// \param shared Shared context (can be a null pointer) + /// \param size Back buffer width and height, in pixels + /// \param bitsPerPixel Pixel depth, in bits per pixel + /// + //////////////////////////////////////////////////////////// + void createSurface(WglContext* shared, Vector2u size, unsigned int bitsPerPixel); + + //////////////////////////////////////////////////////////// + /// \brief Create the context's drawing surface from an existing window + /// + /// \param window Window handle of the owning window + /// \param bitsPerPixel Pixel depth, in bits per pixel + /// + //////////////////////////////////////////////////////////// + void createSurface(HWND window, unsigned int bitsPerPixel); + + //////////////////////////////////////////////////////////// + /// \brief Create the context + /// + /// \param shared Context to share the new one with (can be a null pointer) + /// + //////////////////////////////////////////////////////////// + void createContext(WglContext* shared); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + HWND m_window{}; //!< Window to which the context is attached + HPBUFFERARB m_pbuffer{}; //!< Handle to a pbuffer if one was created + HDC m_deviceContext{}; //!< Device context associated to the context + HGLRC m_context{}; //!< OpenGL context + bool m_ownsWindow{}; //!< Do we own the target window? + bool m_isGeneric{}; //!< Is this context provided by the generic GDI implementation? +}; + +} // namespace priv +} // namespace sf diff --git a/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.cpp b/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.cpp new file mode 100644 index 0000000..c9a98e3 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.cpp @@ -0,0 +1,1332 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include + +#include +#include +#include + +// dbt.h is lowercase here, as a cross-compile on linux with mingw-w64 +// expects lowercase, and a native compile on windows, whether via msvc +// or mingw-w64 addresses files in a case insensitive manner. +#include +#include +#include +#include + +#include +#include + +// MinGW lacks the definition of some Win32 constants +#ifndef XBUTTON1 +#define XBUTTON1 0x0001 +#endif +#ifndef XBUTTON2 +#define XBUTTON2 0x0002 +#endif +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC (0) +#endif + +namespace +{ +unsigned int windowCount = 0; // Windows owned by SFML +unsigned int handleCount = 0; // All window handles +const wchar_t* className = L"SFML_Window"; +sf::priv::WindowImplWin32* fullscreenWindow = nullptr; + +constexpr GUID guidDevinterfaceHid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30}}; + +void setProcessDpiAware() +{ + // Try SetProcessDpiAwareness first + if (const HINSTANCE shCoreDll = LoadLibrary(L"Shcore.dll")) + { + enum ProcessDpiAwareness + { + ProcessDpiUnaware = 0, + ProcessSystemDpiAware = 1, + ProcessPerMonitorDpiAware = 2 + }; + + using SetProcessDpiAwarenessFuncType = HRESULT(WINAPI*)(ProcessDpiAwareness); + auto setProcessDpiAwarenessFunc = reinterpret_cast( + reinterpret_cast(GetProcAddress(shCoreDll, "SetProcessDpiAwareness"))); + + if (setProcessDpiAwarenessFunc) + { + // We only check for E_INVALIDARG because we would get + // E_ACCESSDENIED if the DPI was already set previously + // and S_OK means the call was successful. + // We intentionally don't use Per Monitor V2 which can be + // enabled with SetProcessDpiAwarenessContext, because that + // would scale the title bar and thus change window size + // by default when moving the window between monitors. + if (setProcessDpiAwarenessFunc(ProcessPerMonitorDpiAware) == E_INVALIDARG) + { + sf::err() << "Failed to set process DPI awareness" << std::endl; + } + else + { + FreeLibrary(shCoreDll); + return; + } + } + + FreeLibrary(shCoreDll); + } + + // Fall back to SetProcessDPIAware if SetProcessDpiAwareness + // is not available on this system + if (const HINSTANCE user32Dll = LoadLibrary(L"user32.dll")) + { + using SetProcessDPIAwareFuncType = BOOL(WINAPI*)(); + auto setProcessDPIAwareFunc = reinterpret_cast( + reinterpret_cast(GetProcAddress(user32Dll, "SetProcessDPIAware"))); + + if (setProcessDPIAwareFunc) + { + if (!setProcessDPIAwareFunc()) + sf::err() << "Failed to set process DPI awareness" << std::endl; + } + + FreeLibrary(user32Dll); + } +} + +// Register a RAWINPUTDEVICE representing the mouse to receive raw +// mouse deltas using WM_INPUT +void initRawMouse() +{ + const RAWINPUTDEVICE rawMouse{0x01, 0x02, 0, nullptr}; // HID usage: mouse device class, no flags, follow keyboard focus + + if (RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse)) != TRUE) + sf::err() << "Failed to initialize raw mouse input" << std::endl; +} +} // namespace + +namespace sf::priv +{ +//////////////////////////////////////////////////////////// +WindowImplWin32::WindowImplWin32(WindowHandle handle) : m_handle(handle) +{ + // Set that this process is DPI aware and can handle DPI scaling + setProcessDpiAware(); + + if (m_handle) + { + // If we're the first window handle, we only need to poll for joysticks when WM_DEVICECHANGE message is received + if (handleCount == 0) + { + JoystickImpl::setLazyUpdates(true); + + initRawMouse(); + } + + ++handleCount; + + // We change the event procedure of the control (it is important to save the old one) + SetWindowLongPtrW(m_handle, GWLP_USERDATA, reinterpret_cast(this)); + m_callback = SetWindowLongPtrW(m_handle, GWLP_WNDPROC, reinterpret_cast(&WindowImplWin32::globalOnEvent)); + } +} + + +//////////////////////////////////////////////////////////// +WindowImplWin32::WindowImplWin32(VideoMode mode, + const String& title, + std::uint32_t style, + State state, + const ContextSettings& /*settings*/) : +m_lastSize(mode.size), +m_fullscreen(state == State::Fullscreen), +m_cursorGrabbed(m_fullscreen) +{ + // Set that this process is DPI aware and can handle DPI scaling + setProcessDpiAware(); + + // Register the window class at first call + if (windowCount == 0) + registerWindowClass(); + + // Compute position and size + HDC screenDC = GetDC(nullptr); + const int left = (GetDeviceCaps(screenDC, HORZRES) - static_cast(mode.size.x)) / 2; + const int top = (GetDeviceCaps(screenDC, VERTRES) - static_cast(mode.size.y)) / 2; + auto [width, height] = Vector2i(mode.size); + ReleaseDC(nullptr, screenDC); + + // Choose the window style according to the Style parameter + DWORD win32Style = WS_VISIBLE; + if (style == Style::None) + { + win32Style |= WS_POPUP; + } + else + { + if (style & Style::Titlebar) + win32Style |= WS_CAPTION | WS_MINIMIZEBOX; + if (style & Style::Resize) + win32Style |= WS_THICKFRAME | WS_MAXIMIZEBOX; + if (style & Style::Close) + win32Style |= WS_SYSMENU; + } + + // In windowed mode, adjust width and height so that window will have the requested client area + if (!m_fullscreen) + { + RECT rectangle = {0, 0, width, height}; + AdjustWindowRect(&rectangle, win32Style, false); + width = rectangle.right - rectangle.left; + height = rectangle.bottom - rectangle.top; + } + + // Create the window + m_handle = CreateWindowW(className, + title.toWideString().c_str(), + win32Style, + left, + top, + width, + height, + nullptr, + nullptr, + GetModuleHandle(nullptr), + this); + + // Register to receive device interface change notifications (used for joystick connection handling) + DEV_BROADCAST_DEVICEINTERFACE deviceInterface = + {sizeof(DEV_BROADCAST_DEVICEINTERFACE), DBT_DEVTYP_DEVICEINTERFACE, 0, guidDevinterfaceHid, {0}}; + RegisterDeviceNotification(m_handle, &deviceInterface, DEVICE_NOTIFY_WINDOW_HANDLE); + + // If we're the first window handle, we only need to poll for joysticks when WM_DEVICECHANGE message is received + if (m_handle) + { + if (handleCount == 0) + { + JoystickImpl::setLazyUpdates(true); + + initRawMouse(); + } + + ++handleCount; + } + + // By default, the OS limits the size of the window the the desktop size, + // we have to resize it after creation to apply the real size + setSize(mode.size); + + // Switch to fullscreen if requested + if (m_fullscreen) + switchToFullscreen(mode); + + // Increment window count + ++windowCount; +} + + +//////////////////////////////////////////////////////////// +WindowImplWin32::~WindowImplWin32() +{ + // TODO should we restore the cursor shape and visibility? + + // Destroy the custom icon, if any + if (m_icon) + DestroyIcon(m_icon); + + // If it's the last window handle we have to poll for joysticks again + if (m_handle) + { + --handleCount; + + if (handleCount == 0) + JoystickImpl::setLazyUpdates(false); + } + + if (!m_callback) + { + // Destroy the window + if (m_handle) + DestroyWindow(m_handle); + + // Decrement the window count + --windowCount; + + // Unregister window class if we were the last window + if (windowCount == 0) + UnregisterClassW(className, GetModuleHandleW(nullptr)); + } + else + { + // The window is external: remove the hook on its message callback + SetWindowLongPtrW(m_handle, GWLP_WNDPROC, m_callback); + } +} + + +//////////////////////////////////////////////////////////// +WindowHandle WindowImplWin32::getNativeHandle() const +{ + return m_handle; +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::processEvents() +{ + // We process the window events only if we own it + if (!m_callback) + { + MSG message; + while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&message); + DispatchMessageW(&message); + } + } +} + + +//////////////////////////////////////////////////////////// +Vector2i WindowImplWin32::getPosition() const +{ + RECT rect; + GetWindowRect(m_handle, &rect); + + return {rect.left, rect.top}; +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setPosition(Vector2i position) +{ + SetWindowPos(m_handle, nullptr, position.x, position.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + if (m_cursorGrabbed) + grabCursor(true); +} + + +//////////////////////////////////////////////////////////// +Vector2u WindowImplWin32::getSize() const +{ + RECT rect; + GetClientRect(m_handle, &rect); + + return Vector2u(Vector2(rect.right - rect.left, rect.bottom - rect.top)); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setSize(Vector2u size) +{ + const auto [width, height] = contentSizeToWindowSize(size); + SetWindowPos(m_handle, nullptr, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setTitle(const String& title) +{ + SetWindowTextW(m_handle, title.toWideString().c_str()); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setIcon(Vector2u size, const std::uint8_t* pixels) +{ + // First destroy the previous one + if (m_icon) + DestroyIcon(m_icon); + + // Windows wants BGRA pixels: swap red and blue channels + std::vector iconPixels(size.x * size.y * 4); + for (std::size_t i = 0; i < iconPixels.size() / 4; ++i) + { + iconPixels[i * 4 + 0] = pixels[i * 4 + 2]; + iconPixels[i * 4 + 1] = pixels[i * 4 + 1]; + iconPixels[i * 4 + 2] = pixels[i * 4 + 0]; + iconPixels[i * 4 + 3] = pixels[i * 4 + 3]; + } + + // Create the icon from the pixel array + m_icon = CreateIcon(GetModuleHandleW(nullptr), + static_cast(size.x), + static_cast(size.y), + 1, + 32, + nullptr, + iconPixels.data()); + + // Set it as both big and small icon of the window + if (m_icon) + { + SendMessageW(m_handle, WM_SETICON, ICON_BIG, reinterpret_cast(m_icon)); + SendMessageW(m_handle, WM_SETICON, ICON_SMALL, reinterpret_cast(m_icon)); + } + else + { + err() << "Failed to set the window's icon" << std::endl; + } +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setVisible(bool visible) +{ + ShowWindow(m_handle, visible ? SW_SHOW : SW_HIDE); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setMouseCursorVisible(bool visible) +{ + m_cursorVisible = visible; + SetCursor(m_cursorVisible ? m_lastCursor : nullptr); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setMouseCursorGrabbed(bool grabbed) +{ + m_cursorGrabbed = grabbed; + grabCursor(m_cursorGrabbed); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setMouseCursor(const CursorImpl& cursor) +{ + m_lastCursor = static_cast(cursor.m_cursor); + SetCursor(m_cursorVisible ? m_lastCursor : nullptr); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setKeyRepeatEnabled(bool enabled) +{ + m_keyRepeatEnabled = enabled; +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::requestFocus() +{ + // Allow focus stealing only within the same process; compare PIDs of current and foreground window + DWORD thisPid = 0; + DWORD foregroundPid = 0; + GetWindowThreadProcessId(m_handle, &thisPid); + GetWindowThreadProcessId(GetForegroundWindow(), &foregroundPid); + + if (thisPid == foregroundPid) + { + // The window requesting focus belongs to the same process as the current window: steal focus + SetForegroundWindow(m_handle); + } + else + { + // Different process: don't steal focus, but create a taskbar notification ("flash") + FLASHWINFO info; + info.cbSize = sizeof(info); + info.hwnd = m_handle; + info.dwFlags = FLASHW_TRAY; + info.dwTimeout = 0; + info.uCount = 3; + + FlashWindowEx(&info); + } +} + + +//////////////////////////////////////////////////////////// +bool WindowImplWin32::hasFocus() const +{ + return m_handle == GetForegroundWindow(); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::registerWindowClass() +{ + WNDCLASSW windowClass; + windowClass.style = 0; + windowClass.lpfnWndProc = &WindowImplWin32::globalOnEvent; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = GetModuleHandleW(nullptr); + windowClass.hIcon = nullptr; + windowClass.hCursor = nullptr; + windowClass.hbrBackground = nullptr; + windowClass.lpszMenuName = nullptr; + windowClass.lpszClassName = className; + RegisterClassW(&windowClass); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::switchToFullscreen(const VideoMode& mode) +{ + DEVMODE devMode; + devMode.dmSize = sizeof(devMode); + devMode.dmPelsWidth = mode.size.x; + devMode.dmPelsHeight = mode.size.y; + devMode.dmBitsPerPel = mode.bitsPerPixel; + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + + // Apply fullscreen mode + if (ChangeDisplaySettingsW(&devMode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) + { + err() << "Failed to change display mode for fullscreen" << std::endl; + return; + } + + // Make the window flags compatible with fullscreen mode + SetWindowLongPtr(m_handle, + GWL_STYLE, + static_cast(WS_POPUP) | static_cast(WS_CLIPCHILDREN) | + static_cast(WS_CLIPSIBLINGS)); + SetWindowLongPtr(m_handle, GWL_EXSTYLE, WS_EX_APPWINDOW); + + // Resize the window so that it fits the entire screen + SetWindowPos(m_handle, HWND_TOP, 0, 0, static_cast(mode.size.x), static_cast(mode.size.y), SWP_FRAMECHANGED); + ShowWindow(m_handle, SW_SHOW); + + // Set "this" as the current fullscreen window + fullscreenWindow = this; +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::cleanup() +{ + // Restore the previous video mode (in case we were running in fullscreen) + if (fullscreenWindow == this) + { + ChangeDisplaySettingsW(nullptr, 0); + fullscreenWindow = nullptr; + } + + // Unhide the mouse cursor (in case it was hidden) + setMouseCursorVisible(true); + + // No longer track the cursor + setTracking(false); + + // No longer capture the cursor + ReleaseCapture(); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::setTracking(bool track) +{ + TRACKMOUSEEVENT mouseEvent; + mouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + mouseEvent.dwFlags = track ? TME_LEAVE : TME_CANCEL; + mouseEvent.hwndTrack = m_handle; + mouseEvent.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&mouseEvent); +} + + +//////////////////////////////////////////////////////////// +void WindowImplWin32::grabCursor(bool grabbed) +{ + if (grabbed) + { + RECT rect; + GetClientRect(m_handle, &rect); + MapWindowPoints(m_handle, nullptr, reinterpret_cast(&rect), 2); + ClipCursor(&rect); + } + else + { + ClipCursor(nullptr); + } +} + + +//////////////////////////////////////////////////////////// +Vector2i WindowImplWin32::contentSizeToWindowSize(Vector2u size) +{ + // SetWindowPos wants the total size of the window (including title bar, borders, and menu) so we have to compute it + const auto style = static_cast(GetWindowLongPtr(m_handle, GWL_STYLE)); + const BOOL hasMenu = ((style & WS_CHILD) == 0) && GetMenu(m_handle) != nullptr; + const auto exStyle = static_cast(GetWindowLongPtr(m_handle, GWL_EXSTYLE)); + + RECT rectangle = {0, 0, static_cast(size.x), static_cast(size.y)}; + AdjustWindowRectEx(&rectangle, style, hasMenu, exStyle); + const auto width = rectangle.right - rectangle.left; + const auto height = rectangle.bottom - rectangle.top; + + return {width, height}; +} + + +//////////////////////////////////////////////////////////// +Keyboard::Scancode WindowImplWin32::toScancode(WPARAM wParam, LPARAM lParam) +{ + int code = (lParam & (0xFF << 16)) >> 16; + + // Retrieve the scancode from the VirtualKey for synthetic key messages + if (code == 0) + { + code = static_cast(MapVirtualKey(static_cast(wParam), MAPVK_VK_TO_VSC)); + } + + // Windows scancodes + // Reference: https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx + // clang-format off + switch (code) + { + case 1: return Keyboard::Scan::Escape; + case 2: return Keyboard::Scan::Num1; + case 3: return Keyboard::Scan::Num2; + case 4: return Keyboard::Scan::Num3; + case 5: return Keyboard::Scan::Num4; + case 6: return Keyboard::Scan::Num5; + case 7: return Keyboard::Scan::Num6; + case 8: return Keyboard::Scan::Num7; + case 9: return Keyboard::Scan::Num8; + case 10: return Keyboard::Scan::Num9; + case 11: return Keyboard::Scan::Num0; + case 12: return Keyboard::Scan::Hyphen; + case 13: return Keyboard::Scan::Equal; + case 14: return Keyboard::Scan::Backspace; + case 15: return Keyboard::Scan::Tab; + case 16: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::MediaPreviousTrack : Keyboard::Scan::Q; + case 17: return Keyboard::Scan::W; + case 18: return Keyboard::Scan::E; + case 19: return Keyboard::Scan::R; + case 20: return Keyboard::Scan::T; + case 21: return Keyboard::Scan::Y; + case 22: return Keyboard::Scan::U; + case 23: return Keyboard::Scan::I; + case 24: return Keyboard::Scan::O; + case 25: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::MediaNextTrack : Keyboard::Scan::P; + case 26: return Keyboard::Scan::LBracket; + case 27: return Keyboard::Scan::RBracket; + case 28: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::NumpadEnter : Keyboard::Scan::Enter; + case 29: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::RControl : Keyboard::Scan::LControl; + case 30: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Select : Keyboard::Scan::A; + case 31: return Keyboard::Scan::S; + case 32: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::VolumeMute : Keyboard::Scan::D; + case 33: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::LaunchApplication1 : Keyboard::Scan::F; + case 34: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::MediaPlayPause : Keyboard::Scan::G; + case 35: return Keyboard::Scan::H; + case 36: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::MediaStop : Keyboard::Scan::J; + case 37: return Keyboard::Scan::K; + case 38: return Keyboard::Scan::L; + case 39: return Keyboard::Scan::Semicolon; + case 40: return Keyboard::Scan::Apostrophe; + case 41: return Keyboard::Scan::Grave; + case 42: return Keyboard::Scan::LShift; + case 43: return Keyboard::Scan::Backslash; + case 44: return Keyboard::Scan::Z; + case 45: return Keyboard::Scan::X; + case 46: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::VolumeDown : Keyboard::Scan::C; + case 47: return Keyboard::Scan::V; + case 48: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::VolumeUp : Keyboard::Scan::B; + case 49: return Keyboard::Scan::N; + case 50: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::HomePage : Keyboard::Scan::M; + case 51: return Keyboard::Scan::Comma; + case 52: return Keyboard::Scan::Period; + case 53: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::NumpadDivide : Keyboard::Scan::Slash; + case 54: return Keyboard::Scan::RShift; + case 55: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::PrintScreen : Keyboard::Scan::NumpadMultiply; + case 56: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::RAlt : Keyboard::Scan::LAlt; + case 57: return Keyboard::Scan::Space; + case 58: return Keyboard::Scan::CapsLock; + case 59: return Keyboard::Scan::F1; + case 60: return Keyboard::Scan::F2; + case 61: return Keyboard::Scan::F3; + case 62: return Keyboard::Scan::F4; + case 63: return Keyboard::Scan::F5; + case 64: return Keyboard::Scan::F6; + case 65: return Keyboard::Scan::F7; + case 66: return Keyboard::Scan::F8; + case 67: return Keyboard::Scan::F9; + case 68: return Keyboard::Scan::F10; + case 69: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::NumLock : Keyboard::Scan::Pause; + case 70: return Keyboard::Scan::ScrollLock; + case 71: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Home : Keyboard::Scan::Numpad7; + case 72: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Up : Keyboard::Scan::Numpad8; + case 73: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::PageUp : Keyboard::Scan::Numpad9; + case 74: return Keyboard::Scan::NumpadMinus; + case 75: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Left : Keyboard::Scan::Numpad4; + case 76: return Keyboard::Scan::Numpad5; + case 77: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Right : Keyboard::Scan::Numpad6; + case 78: return Keyboard::Scan::NumpadPlus; + case 79: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::End : Keyboard::Scan::Numpad1; + case 80: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Down : Keyboard::Scan::Numpad2; + case 81: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::PageDown : Keyboard::Scan::Numpad3; + case 82: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Insert : Keyboard::Scan::Numpad0; + case 83: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Delete : Keyboard::Scan::NumpadDecimal; + + case 86: return Keyboard::Scan::NonUsBackslash; + case 87: return Keyboard::Scan::F11; + case 88: return Keyboard::Scan::F12; + + case 91: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::LSystem : Keyboard::Scan::Unknown; + case 92: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::RSystem : Keyboard::Scan::Unknown; + case 93: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Menu : Keyboard::Scan::Unknown; + + case 99: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Help : Keyboard::Scan::Unknown; + case 100: return Keyboard::Scan::F13; + case 101: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Search : Keyboard::Scan::F14; + case 102: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Favorites : Keyboard::Scan::F15; + case 103: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Refresh : Keyboard::Scan::F16; + case 104: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Stop : Keyboard::Scan::F17; + case 105: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Forward : Keyboard::Scan::F18; + case 106: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::Back : Keyboard::Scan::F19; + case 107: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::LaunchApplication1 : Keyboard::Scan::F20; + case 108: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::LaunchMail : Keyboard::Scan::F21; + case 109: return (HIWORD(lParam) & KF_EXTENDED) ? Keyboard::Scan::LaunchMediaSelect : Keyboard::Scan::F22; + case 110: return Keyboard::Scan::F23; + + case 118: return Keyboard::Scan::F24; + + default: return Keyboard::Scan::Unknown; + } + // clang-format on +} + +//////////////////////////////////////////////////////////// +void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam) +{ + // Don't process any message until window is created + if (m_handle == nullptr) + return; + + switch (message) + { + // Destroy event + case WM_DESTROY: + { + // Here we must cleanup resources ! + cleanup(); + break; + } + + // Set cursor event + case WM_SETCURSOR: + { + // The mouse has moved, if the cursor is in our window we must refresh the cursor + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(m_cursorVisible ? m_lastCursor : nullptr); + } + + break; + } + + // Close event + case WM_CLOSE: + { + pushEvent(Event::Closed{}); + break; + } + + // Resize event + case WM_SIZE: + { + // Consider only events triggered by a maximize or a un-maximize + if (wParam != SIZE_MINIMIZED && !m_resizing && m_lastSize != getSize()) + { + // Update the last handled size + m_lastSize = getSize(); + + // Push a resize event + pushEvent(Event::Resized{m_lastSize}); + + // Restore/update cursor grabbing + grabCursor(m_cursorGrabbed); + } + break; + } + + // Start resizing + case WM_ENTERSIZEMOVE: + { + m_resizing = true; + grabCursor(false); + break; + } + + // Stop resizing + case WM_EXITSIZEMOVE: + { + m_resizing = false; + + // Ignore cases where the window has only been moved + if (m_lastSize != getSize()) + { + // Update the last handled size + m_lastSize = getSize(); + + // Push a resize event + pushEvent(Event::Resized{m_lastSize}); + } + + // Restore/update cursor grabbing + grabCursor(m_cursorGrabbed); + break; + } + + // Fix violations of minimum or maximum size + case WM_GETMINMAXINFO: + { + // We override the returned information to remove the default limit + // (the OS doesn't allow windows bigger than the desktop by default) + + const auto maximumSize = contentSizeToWindowSize(getMaximumSize().value_or(Vector2u(50'000, 50'000))); + + MINMAXINFO& minMaxInfo = *reinterpret_cast(lParam); + minMaxInfo.ptMaxTrackSize.x = maximumSize.x; + minMaxInfo.ptMaxTrackSize.y = maximumSize.y; + if (getMaximumSize().has_value()) + { + minMaxInfo.ptMaxSize.x = maximumSize.x; + minMaxInfo.ptMaxSize.y = maximumSize.y; + } + if (getMinimumSize().has_value()) + { + const auto minimumSize = contentSizeToWindowSize(getMinimumSize().value()); + minMaxInfo.ptMinTrackSize.x = minimumSize.x; + minMaxInfo.ptMinTrackSize.y = minimumSize.y; + } + break; + } + + // Gain focus event + case WM_SETFOCUS: + { + // Restore cursor grabbing + grabCursor(m_cursorGrabbed); + + pushEvent(Event::FocusGained{}); + break; + } + + // Lost focus event + case WM_KILLFOCUS: + { + // Ungrab the cursor + grabCursor(false); + + pushEvent(Event::FocusLost{}); + break; + } + + // Text event + case WM_CHAR: + { + if (m_keyRepeatEnabled || ((lParam & (1 << 30)) == 0)) + { + // Get the code of the typed character + auto character = static_cast(wParam); + + // Check if it is the first part of a surrogate pair, or a regular character + if ((character >= 0xD800) && (character <= 0xDBFF)) + { + // First part of a surrogate pair: store it and wait for the second one + m_surrogate = static_cast(character); + } + else + { + // Check if it is the second part of a surrogate pair, or a regular character + if ((character >= 0xDC00) && (character <= 0xDFFF)) + { + // Convert the UTF-16 surrogate pair to a single UTF-32 value + const std::array utf16 = {m_surrogate, static_cast(character)}; + sf::Utf16::toUtf32(utf16.begin(), utf16.end(), &character); + m_surrogate = 0; + } + + // Send a TextEntered event + pushEvent(Event::TextEntered{character}); + } + } + break; + } + + // Keydown event + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + if (m_keyRepeatEnabled || ((HIWORD(lParam) & KF_REPEAT) == 0)) + { + Event::KeyPressed event; + event.alt = HIWORD(GetKeyState(VK_MENU)) != 0; + event.control = HIWORD(GetKeyState(VK_CONTROL)) != 0; + event.shift = HIWORD(GetKeyState(VK_SHIFT)) != 0; + event.system = HIWORD(GetKeyState(VK_LWIN)) || HIWORD(GetKeyState(VK_RWIN)); + event.code = virtualKeyCodeToSF(wParam, lParam); + event.scancode = toScancode(wParam, lParam); + pushEvent(event); + } + break; + } + + // Keyup event + case WM_KEYUP: + case WM_SYSKEYUP: + { + Event::KeyReleased event; + event.alt = HIWORD(GetKeyState(VK_MENU)) != 0; + event.control = HIWORD(GetKeyState(VK_CONTROL)) != 0; + event.shift = HIWORD(GetKeyState(VK_SHIFT)) != 0; + event.system = HIWORD(GetKeyState(VK_LWIN)) || HIWORD(GetKeyState(VK_RWIN)); + event.code = virtualKeyCodeToSF(wParam, lParam); + event.scancode = toScancode(wParam, lParam); + pushEvent(event); + break; + } + + // Vertical mouse wheel event + case WM_MOUSEWHEEL: + { + // Mouse position is in screen coordinates, convert it to window coordinates + POINT position; + position.x = static_cast(LOWORD(lParam)); + position.y = static_cast(HIWORD(lParam)); + ScreenToClient(m_handle, &position); + + auto delta = static_cast(HIWORD(wParam)); + + Event::MouseWheelScrolled event; + event.wheel = Mouse::Wheel::Vertical; + event.delta = static_cast(delta) / 120.f; + event.position = {position.x, position.y}; + pushEvent(event); + break; + } + + // Horizontal mouse wheel event + case WM_MOUSEHWHEEL: + { + // Mouse position is in screen coordinates, convert it to window coordinates + POINT position; + position.x = static_cast(LOWORD(lParam)); + position.y = static_cast(HIWORD(lParam)); + ScreenToClient(m_handle, &position); + + auto delta = static_cast(HIWORD(wParam)); + + Event::MouseWheelScrolled event; + event.wheel = Mouse::Wheel::Horizontal; + event.delta = -static_cast(delta) / 120.f; + event.position = {position.x, position.y}; + pushEvent(event); + break; + } + + // Mouse left button down event + case WM_LBUTTONDOWN: + { + Event::MouseButtonPressed event; + event.button = Mouse::Button::Left; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse left button up event + case WM_LBUTTONUP: + { + Event::MouseButtonReleased event; + event.button = Mouse::Button::Left; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse right button down event + case WM_RBUTTONDOWN: + { + Event::MouseButtonPressed event; + event.button = Mouse::Button::Right; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse right button up event + case WM_RBUTTONUP: + { + Event::MouseButtonReleased event; + event.button = Mouse::Button::Right; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse wheel button down event + case WM_MBUTTONDOWN: + { + Event::MouseButtonPressed event; + event.button = Mouse::Button::Middle; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse wheel button up event + case WM_MBUTTONUP: + { + Event::MouseButtonReleased event; + event.button = Mouse::Button::Middle; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse X button down event + case WM_XBUTTONDOWN: + { + Event::MouseButtonPressed event; + event.button = HIWORD(wParam) == XBUTTON1 ? Mouse::Button::Extra1 : Mouse::Button::Extra2; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse X button up event + case WM_XBUTTONUP: + { + Event::MouseButtonReleased event; + event.button = HIWORD(wParam) == XBUTTON1 ? Mouse::Button::Extra1 : Mouse::Button::Extra2; + event.position = {static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))}; + pushEvent(event); + break; + } + + // Mouse leave event + case WM_MOUSELEAVE: + { + // Avoid this firing a second time in case the cursor is dragged outside + if (m_mouseInside) + { + m_mouseInside = false; + + // Generate a MouseLeft event + pushEvent(Event::MouseLeft{}); + } + break; + } + + // Mouse move event + case WM_MOUSEMOVE: + { + // Extract the mouse local coordinates + const int x = static_cast(LOWORD(lParam)); + const int y = static_cast(HIWORD(lParam)); + + // Get the client area of the window + RECT area; + GetClientRect(m_handle, &area); + + // Capture the mouse in case the user wants to drag it outside + if ((wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0) + { + // Only release the capture if we really have it + if (GetCapture() == m_handle) + ReleaseCapture(); + } + else if (GetCapture() != m_handle) + { + // Set the capture to continue receiving mouse events + SetCapture(m_handle); + } + + // If the cursor is outside the client area... + if ((x < area.left) || (x > area.right) || (y < area.top) || (y > area.bottom)) + { + // and it used to be inside, the mouse left it. + if (m_mouseInside) + { + m_mouseInside = false; + + // No longer care for the mouse leaving the window + setTracking(false); + + // Generate a MouseLeft event + pushEvent(Event::MouseLeft{}); + } + } + else + { + // and vice-versa + if (!m_mouseInside) + { + m_mouseInside = true; + + // Look for the mouse leaving the window + setTracking(true); + + // Generate a MouseEntered event + pushEvent(Event::MouseEntered{}); + } + } + + // Generate a MouseMove event + pushEvent(Event::MouseMoved{{x, y}}); + break; + } + + // Raw input event + case WM_INPUT: + { + RAWINPUT input; + UINT size = sizeof(input); + + GetRawInputData(reinterpret_cast(lParam), RID_INPUT, &input, &size, sizeof(RAWINPUTHEADER)); + + if (input.header.dwType == RIM_TYPEMOUSE) + { + if (const RAWMOUSE* rawMouse = &input.data.mouse; (rawMouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) + pushEvent(Event::MouseMovedRaw{{rawMouse->lLastX, rawMouse->lLastY}}); + } + + break; + } + + // Hardware configuration change event + case WM_DEVICECHANGE: + { + // Some sort of device change has happened, update joystick connections + if ((wParam == DBT_DEVICEARRIVAL) || (wParam == DBT_DEVICEREMOVECOMPLETE)) + { + // Some sort of device change has happened, update joystick connections if it is a device interface + auto* deviceBroadcastHeader = reinterpret_cast(lParam); + + if (deviceBroadcastHeader && (deviceBroadcastHeader->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)) + JoystickImpl::updateConnections(); + } + + break; + } + + // Work around Windows 10 bug + // When a maximum size is specified and the window is snapped to the edge of the display the window size is subtly too big + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS& pos = *reinterpret_cast(lParam); + if (pos.flags & SWP_NOSIZE) + break; + + if (!getMaximumSize().has_value()) + break; + const auto maximumSize = contentSizeToWindowSize(getMaximumSize().value()); + + bool shouldResize = false; + if (pos.cx > maximumSize.x) + { + pos.cx = maximumSize.x; + shouldResize = true; + } + if (pos.cy > maximumSize.y) + { + pos.cy = maximumSize.y; + shouldResize = true; + } + + if (shouldResize) + SetWindowPos(m_handle, pos.hwndInsertAfter, pos.x, pos.y, pos.cx, pos.cy, 0); + + break; + } + } +} + + +//////////////////////////////////////////////////////////// +Keyboard::Key WindowImplWin32::virtualKeyCodeToSF(WPARAM key, LPARAM flags) +{ + // clang-format off + switch (key) + { + // Check the scancode to distinguish between left and right shift + case VK_SHIFT: + { + static const UINT lShift = MapVirtualKeyW(VK_LSHIFT, MAPVK_VK_TO_VSC); + const UINT scancode = static_cast((flags & (0xFF << 16)) >> 16); + return scancode == lShift ? Keyboard::Key::LShift : Keyboard::Key::RShift; + } + + // Check the "extended" flag to distinguish between left and right alt + case VK_MENU : return (HIWORD(flags) & KF_EXTENDED) ? Keyboard::Key::RAlt : Keyboard::Key::LAlt; + + // Check the "extended" flag to distinguish between left and right control + case VK_CONTROL : return (HIWORD(flags) & KF_EXTENDED) ? Keyboard::Key::RControl : Keyboard::Key::LControl; + + // Other keys are reported properly + case VK_LWIN: return Keyboard::Key::LSystem; + case VK_RWIN: return Keyboard::Key::RSystem; + case VK_APPS: return Keyboard::Key::Menu; + case VK_OEM_1: return Keyboard::Key::Semicolon; + case VK_OEM_2: return Keyboard::Key::Slash; + case VK_OEM_PLUS: return Keyboard::Key::Equal; + case VK_OEM_MINUS: return Keyboard::Key::Hyphen; + case VK_OEM_4: return Keyboard::Key::LBracket; + case VK_OEM_6: return Keyboard::Key::RBracket; + case VK_OEM_COMMA: return Keyboard::Key::Comma; + case VK_OEM_PERIOD: return Keyboard::Key::Period; + case VK_OEM_7: return Keyboard::Key::Apostrophe; + case VK_OEM_5: return Keyboard::Key::Backslash; + case VK_OEM_3: return Keyboard::Key::Grave; + case VK_ESCAPE: return Keyboard::Key::Escape; + case VK_SPACE: return Keyboard::Key::Space; + case VK_RETURN: return Keyboard::Key::Enter; + case VK_BACK: return Keyboard::Key::Backspace; + case VK_TAB: return Keyboard::Key::Tab; + case VK_PRIOR: return Keyboard::Key::PageUp; + case VK_NEXT: return Keyboard::Key::PageDown; + case VK_END: return Keyboard::Key::End; + case VK_HOME: return Keyboard::Key::Home; + case VK_INSERT: return Keyboard::Key::Insert; + case VK_DELETE: return Keyboard::Key::Delete; + case VK_ADD: return Keyboard::Key::Add; + case VK_SUBTRACT: return Keyboard::Key::Subtract; + case VK_MULTIPLY: return Keyboard::Key::Multiply; + case VK_DIVIDE: return Keyboard::Key::Divide; + case VK_PAUSE: return Keyboard::Key::Pause; + case VK_F1: return Keyboard::Key::F1; + case VK_F2: return Keyboard::Key::F2; + case VK_F3: return Keyboard::Key::F3; + case VK_F4: return Keyboard::Key::F4; + case VK_F5: return Keyboard::Key::F5; + case VK_F6: return Keyboard::Key::F6; + case VK_F7: return Keyboard::Key::F7; + case VK_F8: return Keyboard::Key::F8; + case VK_F9: return Keyboard::Key::F9; + case VK_F10: return Keyboard::Key::F10; + case VK_F11: return Keyboard::Key::F11; + case VK_F12: return Keyboard::Key::F12; + case VK_F13: return Keyboard::Key::F13; + case VK_F14: return Keyboard::Key::F14; + case VK_F15: return Keyboard::Key::F15; + case VK_LEFT: return Keyboard::Key::Left; + case VK_RIGHT: return Keyboard::Key::Right; + case VK_UP: return Keyboard::Key::Up; + case VK_DOWN: return Keyboard::Key::Down; + case VK_NUMPAD0: return Keyboard::Key::Numpad0; + case VK_NUMPAD1: return Keyboard::Key::Numpad1; + case VK_NUMPAD2: return Keyboard::Key::Numpad2; + case VK_NUMPAD3: return Keyboard::Key::Numpad3; + case VK_NUMPAD4: return Keyboard::Key::Numpad4; + case VK_NUMPAD5: return Keyboard::Key::Numpad5; + case VK_NUMPAD6: return Keyboard::Key::Numpad6; + case VK_NUMPAD7: return Keyboard::Key::Numpad7; + case VK_NUMPAD8: return Keyboard::Key::Numpad8; + case VK_NUMPAD9: return Keyboard::Key::Numpad9; + case 'A': return Keyboard::Key::A; + case 'Z': return Keyboard::Key::Z; + case 'E': return Keyboard::Key::E; + case 'R': return Keyboard::Key::R; + case 'T': return Keyboard::Key::T; + case 'Y': return Keyboard::Key::Y; + case 'U': return Keyboard::Key::U; + case 'I': return Keyboard::Key::I; + case 'O': return Keyboard::Key::O; + case 'P': return Keyboard::Key::P; + case 'Q': return Keyboard::Key::Q; + case 'S': return Keyboard::Key::S; + case 'D': return Keyboard::Key::D; + case 'F': return Keyboard::Key::F; + case 'G': return Keyboard::Key::G; + case 'H': return Keyboard::Key::H; + case 'J': return Keyboard::Key::J; + case 'K': return Keyboard::Key::K; + case 'L': return Keyboard::Key::L; + case 'M': return Keyboard::Key::M; + case 'W': return Keyboard::Key::W; + case 'X': return Keyboard::Key::X; + case 'C': return Keyboard::Key::C; + case 'V': return Keyboard::Key::V; + case 'B': return Keyboard::Key::B; + case 'N': return Keyboard::Key::N; + case '0': return Keyboard::Key::Num0; + case '1': return Keyboard::Key::Num1; + case '2': return Keyboard::Key::Num2; + case '3': return Keyboard::Key::Num3; + case '4': return Keyboard::Key::Num4; + case '5': return Keyboard::Key::Num5; + case '6': return Keyboard::Key::Num6; + case '7': return Keyboard::Key::Num7; + case '8': return Keyboard::Key::Num8; + case '9': return Keyboard::Key::Num9; + } + // clang-format on + + return Keyboard::Key::Unknown; +} + + +//////////////////////////////////////////////////////////// +LRESULT CALLBACK WindowImplWin32::globalOnEvent(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) +{ + // Associate handle and Window instance when the creation message is received + if (message == WM_CREATE) + { + // Get WindowImplWin32 instance (it was passed as the last argument of CreateWindow) + auto window = reinterpret_cast(reinterpret_cast(lParam)->lpCreateParams); + + // Set as the "user data" parameter of the window + SetWindowLongPtrW(handle, GWLP_USERDATA, window); + } + + // Get the WindowImpl instance corresponding to the window handle + WindowImplWin32* window = handle ? reinterpret_cast(GetWindowLongPtr(handle, GWLP_USERDATA)) : nullptr; + + // Forward the event to the appropriate function + if (window) + { + window->processEvent(message, wParam, lParam); + + if (window->m_callback) + return CallWindowProcW(reinterpret_cast(window->m_callback), handle, message, wParam, lParam); + } + + // We don't forward the WM_CLOSE message to prevent the OS from automatically destroying the window + if (message == WM_CLOSE) + return 0; + + // Don't forward the menu system command, so that pressing ALT or F10 doesn't steal the focus + if ((message == WM_SYSCOMMAND) && (wParam == SC_KEYMENU)) + return 0; + + return DefWindowProcW(handle, message, wParam, lParam); +} + +} // namespace sf::priv diff --git a/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.hpp b/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.hpp new file mode 100644 index 0000000..d15f6f0 --- /dev/null +++ b/vendor/SFML/src/SFML/Window/Win32/WindowImplWin32.hpp @@ -0,0 +1,315 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2025 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include + +#include +#include + +#include + + +namespace sf +{ +class String; + +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Windows implementation of WindowImpl +/// +//////////////////////////////////////////////////////////// +class WindowImplWin32 : public WindowImpl +{ +public: + //////////////////////////////////////////////////////////// + /// \brief Construct the window implementation from an existing control + /// + /// \param handle Platform-specific handle of the control + /// + //////////////////////////////////////////////////////////// + WindowImplWin32(WindowHandle handle); + + //////////////////////////////////////////////////////////// + /// \brief Create the window implementation + /// + /// \param mode Video mode to use + /// \param title Title of the window + /// \param style Window style + /// \param state Window state + /// \param settings Additional settings for the underlying OpenGL context + /// + //////////////////////////////////////////////////////////// + WindowImplWin32(VideoMode mode, const String& title, std::uint32_t style, State state, const ContextSettings& settings); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~WindowImplWin32() override; + + //////////////////////////////////////////////////////////// + /// \brief Get the OS-specific handle of the window + /// + /// \return Handle of the window + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] WindowHandle getNativeHandle() const override; + + //////////////////////////////////////////////////////////// + /// \brief Get the position of the window + /// + /// \return Position of the window, in pixels + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Vector2i getPosition() const override; + + //////////////////////////////////////////////////////////// + /// \brief Change the position of the window on screen + /// + /// \param position New position of the window, in pixels + /// + //////////////////////////////////////////////////////////// + void setPosition(Vector2i position) override; + + //////////////////////////////////////////////////////////// + /// \brief Get the client size of the window + /// + /// \return Size of the window, in pixels + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] Vector2u getSize() const override; + + //////////////////////////////////////////////////////////// + /// \brief Change the size of the rendering region of the window + /// + /// \param size New size, in pixels + /// + //////////////////////////////////////////////////////////// + void setSize(Vector2u size) override; + + //////////////////////////////////////////////////////////// + /// \brief Change the title of the window + /// + /// \param title New title + /// + //////////////////////////////////////////////////////////// + void setTitle(const String& title) override; + + //////////////////////////////////////////////////////////// + /// \brief Change the window's icon + /// + /// \param size Icon's width and height, in pixels + /// \param pixels Pointer to the pixels in memory, format must be RGBA 32 bits + /// + //////////////////////////////////////////////////////////// + void setIcon(Vector2u size, const std::uint8_t* pixels) override; + + //////////////////////////////////////////////////////////// + /// \brief Show or hide the window + /// + /// \param visible `true` to show, `false` to hide + /// + //////////////////////////////////////////////////////////// + void setVisible(bool visible) override; + + //////////////////////////////////////////////////////////// + /// \brief Show or hide the mouse cursor + /// + /// \param visible `true` to show, `false` to hide + /// + //////////////////////////////////////////////////////////// + void setMouseCursorVisible(bool visible) override; + + //////////////////////////////////////////////////////////// + /// \brief Grab or release the mouse cursor + /// + /// \param grabbed `true` to enable, `false` to disable + /// + //////////////////////////////////////////////////////////// + void setMouseCursorGrabbed(bool grabbed) override; + + //////////////////////////////////////////////////////////// + /// \brief Set the displayed cursor to a native system cursor + /// + /// \param cursor Native system cursor type to display + /// + //////////////////////////////////////////////////////////// + void setMouseCursor(const CursorImpl& cursor) override; + + //////////////////////////////////////////////////////////// + /// \brief Enable or disable automatic key-repeat + /// + /// \param enabled `true` to enable, `false` to disable + /// + //////////////////////////////////////////////////////////// + void setKeyRepeatEnabled(bool enabled) override; + + //////////////////////////////////////////////////////////// + /// \brief Request the current window to be made the active + /// foreground window + /// + //////////////////////////////////////////////////////////// + void requestFocus() override; + + //////////////////////////////////////////////////////////// + /// \brief Check whether the window has the input focus + /// + /// \return `true` if window has focus, `false` otherwise + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] bool hasFocus() const override; + +protected: + //////////////////////////////////////////////////////////// + /// \brief Process incoming events from the operating system + /// + //////////////////////////////////////////////////////////// + void processEvents() override; + +private: + //////////////////////////////////////////////////////////// + /// Register the window class + /// + //////////////////////////////////////////////////////////// + void registerWindowClass(); + + //////////////////////////////////////////////////////////// + /// \brief Switch to fullscreen mode + /// + /// \param mode Video mode to switch to + /// + //////////////////////////////////////////////////////////// + void switchToFullscreen(const VideoMode& mode); + + //////////////////////////////////////////////////////////// + /// \brief Free all the graphical resources attached to the window + /// + //////////////////////////////////////////////////////////// + void cleanup(); + + //////////////////////////////////////////////////////////// + /// \brief Process a Win32 event + /// + /// \param message Message to process + /// \param wParam First parameter of the event + /// \param lParam Second parameter of the event + /// + //////////////////////////////////////////////////////////// + void processEvent(UINT message, WPARAM wParam, LPARAM lParam); + + //////////////////////////////////////////////////////////// + /// \brief Enables or disables tracking for the mouse cursor leaving the window + /// + /// \param track `true` to enable, `false` to disable + /// + //////////////////////////////////////////////////////////// + void setTracking(bool track); + + //////////////////////////////////////////////////////////// + /// \brief Grab or release the mouse cursor + /// + /// This is not to be confused with setMouseCursorGrabbed. + /// Here m_cursorGrabbed is not modified; it is used, + /// for example, to release the cursor when switching to + /// another application. + /// + /// \param grabbed `true` to enable, `false` to disable + /// + //////////////////////////////////////////////////////////// + void grabCursor(bool grabbed); + + //////////////////////////////////////////////////////////// + /// \brief Convert content size to window size including window chrome + /// + /// \param size Size to convert + /// + /// \return Converted size including window chrome + /// + //////////////////////////////////////////////////////////// + Vector2i contentSizeToWindowSize(Vector2u size); + + //////////////////////////////////////////////////////////// + /// \brief Convert a Win32 virtual key code to a SFML key code + /// + /// \param key Virtual key code to convert + /// \param flags Additional flags + /// + /// \return SFML key code corresponding to the key + /// + //////////////////////////////////////////////////////////// + static Keyboard::Key virtualKeyCodeToSF(WPARAM key, LPARAM flags); + + //////////////////////////////////////////////////////////// + /// \brief Function called whenever one of our windows receives a message + /// + /// \param handle Win32 handle of the window + /// \param message Message received + /// \param wParam First parameter of the message + /// \param lParam Second parameter of the message + /// + /// \return `true` to discard the event after it has been processed + /// + //////////////////////////////////////////////////////////// + static LRESULT CALLBACK globalOnEvent(HWND handle, UINT message, WPARAM wParam, LPARAM lParam); + + //////////////////////////////////////////////////////////// + /// \brief Convert a Win32 scancode to an sfml scancode + /// + /// \param flags input flags + /// + /// \return SFML scancode corresponding to the key + /// + //////////////////////////////////////////////////////////// + static Keyboard::Scancode toScancode(WPARAM wParam, LPARAM lParam); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + HWND m_handle{}; //!< Win32 handle of the window + LONG_PTR m_callback{}; //!< Stores the original event callback function of the control + bool m_cursorVisible{true}; //!< Is the cursor visible or hidden? + HCURSOR m_lastCursor{ + LoadCursor(nullptr, IDC_ARROW)}; //!< Last cursor used -- this data is not owned by the window and is required to be always valid + HICON m_icon{}; //!< Custom icon assigned to the window + bool m_keyRepeatEnabled{true}; //!< Automatic key-repeat state for keydown events + Vector2u m_lastSize; //!< The last handled size of the window + bool m_resizing{}; //!< Is the window being resized? + char16_t m_surrogate{}; //!< First half of the surrogate pair, in case we're receiving a Unicode character in two events + bool m_mouseInside{}; //!< Mouse is inside the window? + bool m_fullscreen{}; //!< Is the window fullscreen? + bool m_cursorGrabbed{}; //!< Is the mouse cursor trapped? +}; + +} // namespace priv + +} // namespace sf