Add support for raw mouse input

This commit is contained in:
Paul Meffle 2018-01-27 14:26:07 +01:00 committed by Chris Thrasher
parent bfd65989e9
commit 92bba1ed6f
6 changed files with 151 additions and 6 deletions

View File

@ -133,7 +133,7 @@ jobs:
run: | run: |
CLANG_VERSION=$(clang++ --version | sed -n 's/.*version \([0-9]\+\)\..*/\1/p') CLANG_VERSION=$(clang++ --version | sed -n 's/.*version \([0-9]\+\)\..*/\1/p')
echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV 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 - name: Remove ALSA Library
if: runner.os == 'Linux' && matrix.platform.name != 'Android' if: runner.os == 'Linux' && matrix.platform.name != 'Android'
@ -367,7 +367,7 @@ jobs:
- name: Install Linux Dependencies - name: Install Linux Dependencies
if: runner.os == 'Linux' 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 - name: Install macOS Dependencies
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -406,7 +406,7 @@ jobs:
- name: Install Linux Dependencies - name: Install Linux Dependencies
if: runner.os == 'Linux' 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 - 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}} run: cmake --preset dev -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DSFML_BUILD_EXAMPLES=OFF -DSFML_ENABLE_SANITIZERS=ON ${{matrix.platform.flags}}

View File

@ -154,6 +154,39 @@ public:
Vector2i position; //!< Position of the mouse pointer, relative to the top left of the owner window 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 /// \brief Mouse entered event
/// ///
@ -303,6 +336,7 @@ private:
MouseButtonPressed, MouseButtonPressed,
MouseButtonReleased, MouseButtonReleased,
MouseMoved, MouseMoved,
MouseMovedRaw,
MouseEntered, MouseEntered,
MouseLeft, MouseLeft,
JoystickButtonPressed, JoystickButtonPressed,

View File

@ -273,8 +273,8 @@ if(SFML_OS_LINUX OR SFML_OS_FREEBSD OR SFML_OS_OPENBSD OR SFML_OS_NETBSD)
find_package(GBM REQUIRED) find_package(GBM REQUIRED)
target_link_libraries(sfml-window PRIVATE DRM::DRM GBM::GBM) target_link_libraries(sfml-window PRIVATE DRM::DRM GBM::GBM)
else() else()
find_package(X11 REQUIRED COMPONENTS Xrandr Xcursor) find_package(X11 REQUIRED COMPONENTS Xrandr Xcursor Xi)
target_link_libraries(sfml-window PRIVATE X11::X11 X11::Xrandr X11::Xcursor) target_link_libraries(sfml-window PRIVATE X11::X11 X11::Xrandr X11::Xcursor X11::Xi)
endif() endif()
endif() endif()
target_link_libraries(sfml-window PUBLIC SFML::System) target_link_libraries(sfml-window PUBLIC SFML::System)

View File

@ -45,10 +45,12 @@
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <algorithm> #include <algorithm>
#include <array>
#include <bitset> #include <bitset>
#include <fcntl.h> #include <fcntl.h>
#include <filesystem> #include <filesystem>
@ -98,7 +100,7 @@ constexpr unsigned int maxTrialsCount = 5;
// NOLINTNEXTLINE(readability-non-const-parameter) // NOLINTNEXTLINE(readability-non-const-parameter)
Bool checkEvent(::Display*, XEvent* event, XPointer userData) 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 // The event matches the current window so pick it up
return true; return true;
@ -367,6 +369,36 @@ bool isWMAbsolutePositionGood()
std::end(wmAbsPosGood), std::end(wmAbsPosGood),
[&](const sf::String& name) { return name == windowManagerName; }); [&](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<unsigned char, XIMaskLen(XI_LASTEVENT)> 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 WindowImplX11Impl
} // namespace } // namespace
@ -1561,6 +1593,13 @@ void WindowImplX11::initialize()
1); 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 // Show the window
setVisible(true); setVisible(true);
@ -2011,6 +2050,33 @@ bool WindowImplX11::processEvent(XEvent& windowEvent)
break; break;
} }
// Raw input
case GenericEvent:
{
if (XGetEventData(m_display.get(), &windowEvent.xcookie))
{
if (windowEvent.xcookie.evtype == XI_RawMotion)
{
const auto* rawEvent = static_cast<const XIRawEvent*>(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<int>(rawEvent->raw_values[0]);
if ((rawEvent->valuators.mask_len > 1) && XIMaskIsSet(rawEvent->valuators.mask, 1))
relativeValueY = static_cast<int>(rawEvent->raw_values[1]);
pushEvent(Event::MouseMovedRaw{{relativeValueX, relativeValueY}});
}
XFreeEventData(m_display.get(), &windowEvent.xcookie);
}
break;
}
} }
return true; return true;

View File

@ -126,6 +126,16 @@ void setProcessDpiAware()
FreeLibrary(user32Dll); 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
namespace sf::priv 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 we're the first window handle, we only need to poll for joysticks when WM_DEVICECHANGE message is received
if (handleCount == 0) if (handleCount == 0)
{
JoystickImpl::setLazyUpdates(true); JoystickImpl::setLazyUpdates(true);
initRawMouse();
}
++handleCount; ++handleCount;
// We change the event procedure of the control (it is important to save the old one) // 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 (m_handle)
{ {
if (handleCount == 0) if (handleCount == 0)
{
JoystickImpl::setLazyUpdates(true); JoystickImpl::setLazyUpdates(true);
initRawMouse();
}
++handleCount; ++handleCount;
} }
@ -1085,6 +1103,23 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam)
break; break;
} }
// Raw input event
case WM_INPUT:
{
RAWINPUT input;
UINT size = sizeof(input);
GetRawInputData(reinterpret_cast<HRAWINPUT>(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 // Hardware configuration change event
case WM_DEVICECHANGE: case WM_DEVICECHANGE:
{ {

View File

@ -123,6 +123,13 @@ TEST_CASE("[Window] sf::Event")
const auto& mouseMoved = *event.getIf<sf::Event::MouseMoved>(); const auto& mouseMoved = *event.getIf<sf::Event::MouseMoved>();
CHECK(mouseMoved.position == sf::Vector2i(4, 2)); CHECK(mouseMoved.position == sf::Vector2i(4, 2));
event = sf::Event::MouseMovedRaw{{3, 7}};
CHECK(event);
CHECK(event.is<sf::Event::MouseMovedRaw>());
CHECK(event.getIf<sf::Event::MouseMovedRaw>());
const auto& mouseMovedRaw = *event.getIf<sf::Event::MouseMovedRaw>();
CHECK(mouseMovedRaw.delta == sf::Vector2i(3, 7));
event = sf::Event::MouseEntered{}; event = sf::Event::MouseEntered{};
CHECK(event); CHECK(event);
CHECK(event.is<sf::Event::MouseEntered>()); CHECK(event.is<sf::Event::MouseEntered>());
@ -254,6 +261,9 @@ TEST_CASE("[Window] sf::Event")
const sf::Event::MouseMoved mouseMoved; const sf::Event::MouseMoved mouseMoved;
CHECK(mouseMoved.position == sf::Vector2i()); CHECK(mouseMoved.position == sf::Vector2i());
const sf::Event::MouseMovedRaw mouseMovedRaw;
CHECK(mouseMovedRaw.delta == sf::Vector2i());
const sf::Event::JoystickButtonPressed joystickButtonPressed; const sf::Event::JoystickButtonPressed joystickButtonPressed;
CHECK(joystickButtonPressed.joystickId == 0); CHECK(joystickButtonPressed.joystickId == 0);
CHECK(joystickButtonPressed.button == 0); CHECK(joystickButtonPressed.button == 0);