From 92bba1ed6f388956e047c5a6eff80437ed816172 Mon Sep 17 00:00:00 2001 From: Paul Meffle Date: Sat, 27 Jan 2018 14:26:07 +0100 Subject: [PATCH] Add support for raw mouse input --- .github/workflows/ci.yml | 6 +- include/SFML/Window/Event.hpp | 34 ++++++++++++ src/SFML/Window/CMakeLists.txt | 4 +- src/SFML/Window/Unix/WindowImplX11.cpp | 68 ++++++++++++++++++++++- src/SFML/Window/Win32/WindowImplWin32.cpp | 35 ++++++++++++ test/Window/Event.test.cpp | 10 ++++ 6 files changed, 151 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a7094bd9..2753c815c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,7 +133,7 @@ jobs: run: | CLANG_VERSION=$(clang++ --version | sed -n 's/.*version \([0-9]\+\)\..*/\1/p') echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV - sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }} + sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }} - name: Remove ALSA Library if: runner.os == 'Linux' && matrix.platform.name != 'Android' @@ -367,7 +367,7 @@ jobs: - name: Install Linux Dependencies if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install libfreetype-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev + run: sudo apt-get update && sudo apt-get install libfreetype-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev - name: Install macOS Dependencies if: runner.os == 'macOS' @@ -406,7 +406,7 @@ jobs: - name: Install Linux Dependencies if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox && sudo apt-get remove -y libasound2 + run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox && sudo apt-get remove -y libasound2 - name: Configure run: cmake --preset dev -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DSFML_BUILD_EXAMPLES=OFF -DSFML_ENABLE_SANITIZERS=ON ${{matrix.platform.flags}} diff --git a/include/SFML/Window/Event.hpp b/include/SFML/Window/Event.hpp index 627144915..a2c292fd1 100644 --- a/include/SFML/Window/Event.hpp +++ b/include/SFML/Window/Event.hpp @@ -154,6 +154,39 @@ public: Vector2i position; //!< Position of the mouse pointer, relative to the top left of the owner window }; + //////////////////////////////////////////////////////////// + /// \brief Mouse move raw event + /// + /// Raw mouse input data comes unprocessed from the + /// operating system hence "raw". While the MouseMoved + /// position value is dependent on the screen resolution, + /// raw data is not. If the physical mouse is moved too + /// little to cause the screen cursor to move at least a + /// single pixel, no MouseMoved event will be generated. In + /// contrast, any movement information generated by the + /// mouse independent of its sensor resolution will always + /// generate a MouseMovedRaw event. + /// + /// In addition to screen resolution independence, raw + /// mouse data also does not have mouse acceleration or + /// smoothing applied to it as MouseMoved does. + /// + /// Raw mouse movement data is intended for controlling + /// non-cursor movement, e.g. controlling the camera + /// orientation in a first person view, whereas MouseMoved + /// is intended primarily for controlling things related to + /// the screen cursor hence the additional processing + /// applied to it. + /// + /// Currently, raw mouse input events will only be generated + /// on Windows and Linux. + /// + //////////////////////////////////////////////////////////// + struct MouseMovedRaw + { + Vector2i delta; ///< Delta movement of the mouse since the last event + }; + //////////////////////////////////////////////////////////// /// \brief Mouse entered event /// @@ -303,6 +336,7 @@ private: MouseButtonPressed, MouseButtonReleased, MouseMoved, + MouseMovedRaw, MouseEntered, MouseLeft, JoystickButtonPressed, diff --git a/src/SFML/Window/CMakeLists.txt b/src/SFML/Window/CMakeLists.txt index 7aa502e3d..ab59e10bb 100644 --- a/src/SFML/Window/CMakeLists.txt +++ b/src/SFML/Window/CMakeLists.txt @@ -273,8 +273,8 @@ if(SFML_OS_LINUX OR SFML_OS_FREEBSD OR SFML_OS_OPENBSD OR SFML_OS_NETBSD) find_package(GBM REQUIRED) target_link_libraries(sfml-window PRIVATE DRM::DRM GBM::GBM) else() - find_package(X11 REQUIRED COMPONENTS Xrandr Xcursor) - target_link_libraries(sfml-window PRIVATE X11::X11 X11::Xrandr X11::Xcursor) + find_package(X11 REQUIRED COMPONENTS Xrandr Xcursor Xi) + target_link_libraries(sfml-window PRIVATE X11::X11 X11::Xrandr X11::Xcursor X11::Xi) endif() endif() target_link_libraries(sfml-window PUBLIC SFML::System) diff --git a/src/SFML/Window/Unix/WindowImplX11.cpp b/src/SFML/Window/Unix/WindowImplX11.cpp index d6c19b746..e9bac176f 100644 --- a/src/SFML/Window/Unix/WindowImplX11.cpp +++ b/src/SFML/Window/Unix/WindowImplX11.cpp @@ -45,10 +45,12 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -98,7 +100,7 @@ constexpr unsigned int maxTrialsCount = 5; // NOLINTNEXTLINE(readability-non-const-parameter) Bool checkEvent(::Display*, XEvent* event, XPointer userData) { - if (event->xany.window == reinterpret_cast<::Window>(userData)) + if (event->xany.window == reinterpret_cast<::Window>(userData) || event->type == GenericEvent) { // The event matches the current window so pick it up return true; @@ -367,6 +369,36 @@ bool isWMAbsolutePositionGood() std::end(wmAbsPosGood), [&](const sf::String& name) { return name == windowManagerName; }); } + +// Initialize raw mouse input +bool initRawMouse(::Display* disp) +{ + int opcode = 0; + int event = 0; + int error = 0; + + if (XQueryExtension(disp, "XInputExtension", &opcode, &event, &error)) + { + int major = 2; + int minor = 0; + + if (XIQueryVersion(disp, &major, &minor) != BadRequest) + { + std::array mask{}; + XISetMask(mask.data(), XI_RawMotion); + + XIEventMask xiEventMask; + xiEventMask.deviceid = XIAllDevices; + xiEventMask.mask_len = mask.size(); + xiEventMask.mask = mask.data(); + + if (XISelectEvents(disp, DefaultRootWindow(disp), &xiEventMask, 1) == Success) + return true; + } + } + + return false; +} } // namespace WindowImplX11Impl } // namespace @@ -1561,6 +1593,13 @@ void WindowImplX11::initialize() 1); } + // Enable raw input in first window + if (allWindows.empty()) + { + if (!initRawMouse(m_display.get())) + sf::err() << "Failed to initialize raw mouse input" << std::endl; + } + // Show the window setVisible(true); @@ -2011,6 +2050,33 @@ bool WindowImplX11::processEvent(XEvent& windowEvent) break; } + + // Raw input + case GenericEvent: + { + if (XGetEventData(m_display.get(), &windowEvent.xcookie)) + { + if (windowEvent.xcookie.evtype == XI_RawMotion) + { + const auto* rawEvent = static_cast(windowEvent.xcookie.data); + int relativeValueX = 0; + int relativeValueY = 0; + + // Get relative input values + if ((rawEvent->valuators.mask_len > 0) && XIMaskIsSet(rawEvent->valuators.mask, 0)) + relativeValueX = static_cast(rawEvent->raw_values[0]); + + if ((rawEvent->valuators.mask_len > 1) && XIMaskIsSet(rawEvent->valuators.mask, 1)) + relativeValueY = static_cast(rawEvent->raw_values[1]); + + pushEvent(Event::MouseMovedRaw{{relativeValueX, relativeValueY}}); + } + + XFreeEventData(m_display.get(), &windowEvent.xcookie); + } + + break; + } } return true; diff --git a/src/SFML/Window/Win32/WindowImplWin32.cpp b/src/SFML/Window/Win32/WindowImplWin32.cpp index 059b39dba..98b69e7e5 100644 --- a/src/SFML/Window/Win32/WindowImplWin32.cpp +++ b/src/SFML/Window/Win32/WindowImplWin32.cpp @@ -126,6 +126,16 @@ void setProcessDpiAware() 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 @@ -140,8 +150,12 @@ WindowImplWin32::WindowImplWin32(WindowHandle handle) : m_handle(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) @@ -222,8 +236,12 @@ m_cursorGrabbed(m_fullscreen) if (m_handle) { if (handleCount == 0) + { JoystickImpl::setLazyUpdates(true); + initRawMouse(); + } + ++handleCount; } @@ -1085,6 +1103,23 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam) 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: { diff --git a/test/Window/Event.test.cpp b/test/Window/Event.test.cpp index acedebc7b..9155a6ed6 100644 --- a/test/Window/Event.test.cpp +++ b/test/Window/Event.test.cpp @@ -123,6 +123,13 @@ TEST_CASE("[Window] sf::Event") const auto& mouseMoved = *event.getIf(); CHECK(mouseMoved.position == sf::Vector2i(4, 2)); + event = sf::Event::MouseMovedRaw{{3, 7}}; + CHECK(event); + CHECK(event.is()); + CHECK(event.getIf()); + const auto& mouseMovedRaw = *event.getIf(); + CHECK(mouseMovedRaw.delta == sf::Vector2i(3, 7)); + event = sf::Event::MouseEntered{}; CHECK(event); CHECK(event.is()); @@ -254,6 +261,9 @@ TEST_CASE("[Window] sf::Event") const sf::Event::MouseMoved mouseMoved; CHECK(mouseMoved.position == sf::Vector2i()); + const sf::Event::MouseMovedRaw mouseMovedRaw; + CHECK(mouseMovedRaw.delta == sf::Vector2i()); + const sf::Event::JoystickButtonPressed joystickButtonPressed; CHECK(joystickButtonPressed.joystickId == 0); CHECK(joystickButtonPressed.button == 0);