//-------------------------------------------------------------------------------------- // File: Mouse.cpp // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248929 //-------------------------------------------------------------------------------------- #include "pch.h" #include "Mouse.h" #include "PlatformHelpers.h" using namespace DirectX; using Microsoft::WRL::ComPtr; #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) //====================================================================================== // Windows Store or Universal Windows Platform (UWP) app implementation //====================================================================================== // // For a Windows Store app or Universal Windows Platform (UWP) app, add the following to your existing // application methods: // // void App::SetWindow(CoreWindow^ window ) // { // m_mouse->SetWindow(window); // } // // void App::OnDpiChanged(DisplayInformation^ sender, Object^ args) // { // m_mouse->SetDpi(sender->LogicalDpi); // } // #include class Mouse::Impl { public: Impl(Mouse* owner) : mOwner(owner), mDPI(96.f), mMode(MODE_ABSOLUTE) { mPointerPressedToken.value = 0; mPointerReleasedToken.value = 0; mPointerMovedToken.value = 0; mPointerWheelToken.value = 0; mPointerMouseMovedToken.value = 0; if ( s_mouse ) { throw std::exception( "Mouse is a singleton" ); } s_mouse = this; memset( &mState, 0, sizeof(State) ); mScrollWheelValue.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) ); mRelativeRead.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) ); if ( !mScrollWheelValue || !mRelativeRead ) { throw std::exception( "CreateEventEx" ); } } ~Impl() { s_mouse = nullptr; RemoveHandlers(); } void GetState(State& state) const { memcpy( &state, &mState, sizeof(State) ); DWORD result = WaitForSingleObjectEx( mScrollWheelValue.get(), 0, FALSE ); if ( result == WAIT_FAILED ) throw std::exception( "WaitForSingleObjectEx" ); if ( result == WAIT_OBJECT_0 ) { state.scrollWheelValue = 0; } if (mMode == MODE_RELATIVE) { result = WaitForSingleObjectEx( mRelativeRead.get(), 0, FALSE ); if (result == WAIT_FAILED) throw std::exception("WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { state.x = 0; state.y = 0; } else { SetEvent(mRelativeRead.get()); } } state.positionMode = mMode; } void ResetScrollWheelValue() { SetEvent(mScrollWheelValue.get()); } void SetMode(Mode mode) { using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::UI::Core; using namespace ABI::Windows::Foundation; if (mMode == mode) return; ComPtr statics; HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_UI_Core_CoreWindow).Get(), statics.GetAddressOf() ); ThrowIfFailed( hr ); ComPtr window; hr = statics->GetForCurrentThread( window.GetAddressOf() ); ThrowIfFailed( hr ); if (mode == MODE_RELATIVE) { hr = window->get_PointerCursor( mCursor.ReleaseAndGetAddressOf() ); ThrowIfFailed(hr); hr = window->put_PointerCursor(nullptr); ThrowIfFailed(hr); SetEvent(mRelativeRead.get()); mMode = MODE_RELATIVE; } else { if (!mCursor) { ComPtr factory; hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_UI_Core_CoreCursor).Get(), factory.GetAddressOf() ); ThrowIfFailed( hr ); hr = factory->CreateCursor( CoreCursorType_Arrow, 0, mCursor.GetAddressOf() ); ThrowIfFailed( hr ); } hr = window->put_PointerCursor( mCursor.Get() ); ThrowIfFailed(hr); mCursor.Reset(); mMode = MODE_ABSOLUTE; } } void SetWindow(ABI::Windows::UI::Core::ICoreWindow* window) { using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Devices::Input; if (mWindow.Get() == window) return; RemoveHandlers(); mWindow = window; if (!window) { mCursor.Reset(); mMouse.Reset(); return; } ComPtr mouseStatics; HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Devices_Input_MouseDevice).Get(), mouseStatics.GetAddressOf() ); ThrowIfFailed( hr ); hr = mouseStatics->GetForCurrentView(mMouse.ReleaseAndGetAddressOf()); ThrowIfFailed(hr); typedef __FITypedEventHandler_2_Windows__CDevices__CInput__CMouseDevice_Windows__CDevices__CInput__CMouseEventArgs MouseMovedHandler; hr = mMouse->add_MouseMoved( Callback(MouseMovedEvent).Get(), &mPointerMouseMovedToken ); ThrowIfFailed(hr); typedef __FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CPointerEventArgs PointerHandler; auto cb = Callback(PointerEvent); hr = window->add_PointerPressed(cb.Get(), &mPointerPressedToken); ThrowIfFailed(hr); hr = window->add_PointerReleased(cb.Get(), &mPointerReleasedToken); ThrowIfFailed(hr); hr = window->add_PointerMoved(cb.Get(), &mPointerMovedToken); ThrowIfFailed(hr); hr = window->add_PointerWheelChanged(Callback(PointerWheel).Get(), &mPointerWheelToken); ThrowIfFailed(hr); } State mState; Mouse* mOwner; float mDPI; static Mouse::Impl* s_mouse; private: Mode mMode; ComPtr mWindow; ComPtr mMouse; ComPtr mCursor; ScopedHandle mScrollWheelValue; ScopedHandle mRelativeRead; EventRegistrationToken mPointerPressedToken; EventRegistrationToken mPointerReleasedToken; EventRegistrationToken mPointerMovedToken; EventRegistrationToken mPointerWheelToken; EventRegistrationToken mPointerMouseMovedToken; void RemoveHandlers() { if (mWindow) { (void)mWindow->remove_PointerPressed(mPointerPressedToken); mPointerPressedToken.value = 0; (void)mWindow->remove_PointerReleased(mPointerReleasedToken); mPointerReleasedToken.value = 0; (void)mWindow->remove_PointerMoved(mPointerMovedToken); mPointerMovedToken.value = 0; (void)mWindow->remove_PointerWheelChanged(mPointerWheelToken); mPointerWheelToken.value = 0; } if (mMouse) { (void)mMouse->remove_MouseMoved(mPointerMouseMovedToken); mPointerMouseMovedToken.value = 0; } } static HRESULT PointerEvent( IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args ) { using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Input; using namespace ABI::Windows::Devices::Input; if (!s_mouse) return S_OK; ComPtr currentPoint; HRESULT hr = args->get_CurrentPoint( currentPoint.GetAddressOf() ); ThrowIfFailed(hr); ComPtr pointerDevice; hr = currentPoint->get_PointerDevice( pointerDevice.GetAddressOf() ); ThrowIfFailed(hr); PointerDeviceType devType; hr = pointerDevice->get_PointerDeviceType( &devType ); ThrowIfFailed(hr); if (devType == PointerDeviceType::PointerDeviceType_Mouse) { ComPtr props; hr = currentPoint->get_Properties( props.GetAddressOf() ); ThrowIfFailed(hr); boolean value; hr = props->get_IsLeftButtonPressed(&value); ThrowIfFailed(hr); s_mouse->mState.leftButton = value != 0; hr = props->get_IsRightButtonPressed(&value); ThrowIfFailed(hr); s_mouse->mState.rightButton = value != 0; hr = props->get_IsMiddleButtonPressed(&value); ThrowIfFailed(hr); s_mouse->mState.middleButton = value != 0; hr = props->get_IsXButton1Pressed(&value); ThrowIfFailed(hr); s_mouse->mState.xButton1 = value != 0; hr = props->get_IsXButton2Pressed(&value); ThrowIfFailed(hr); s_mouse->mState.xButton2 = value != 0; } if (s_mouse->mMode == MODE_ABSOLUTE) { Point pos; hr = currentPoint->get_Position( &pos ); ThrowIfFailed(hr); float dpi = s_mouse->mDPI; s_mouse->mState.x = static_cast( pos.X * dpi / 96.f + 0.5f ); s_mouse->mState.y = static_cast( pos.Y * dpi / 96.f + 0.5f ); } return S_OK; } static HRESULT PointerWheel( IInspectable *, ABI::Windows::UI::Core::IPointerEventArgs*args ) { using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Input; using namespace ABI::Windows::Devices::Input; if (!s_mouse) return S_OK; ComPtr currentPoint; HRESULT hr = args->get_CurrentPoint( currentPoint.GetAddressOf() ); ThrowIfFailed(hr); ComPtr pointerDevice; hr = currentPoint->get_PointerDevice( pointerDevice.GetAddressOf() ); ThrowIfFailed(hr); PointerDeviceType devType; hr = pointerDevice->get_PointerDeviceType( &devType ); ThrowIfFailed(hr); if (devType == PointerDeviceType::PointerDeviceType_Mouse) { ComPtr props; hr = currentPoint->get_Properties( props.GetAddressOf() ); ThrowIfFailed(hr); INT32 value; hr = props->get_MouseWheelDelta(&value); ThrowIfFailed(hr); HANDLE evt = s_mouse->mScrollWheelValue.get(); if (WaitForSingleObjectEx(evt, 0, FALSE) == WAIT_OBJECT_0) { s_mouse->mState.scrollWheelValue = 0; ResetEvent(evt); } s_mouse->mState.scrollWheelValue += value; if (s_mouse->mMode == MODE_ABSOLUTE) { Point pos; hr = currentPoint->get_Position( &pos ); ThrowIfFailed(hr); float dpi = s_mouse->mDPI; s_mouse->mState.x = static_cast( pos.X * dpi / 96.f + 0.5f ); s_mouse->mState.y = static_cast( pos.Y * dpi / 96.f + 0.5f ); } } return S_OK; } static HRESULT MouseMovedEvent( IInspectable *, ABI::Windows::Devices::Input::IMouseEventArgs* args ) { using namespace ABI::Windows::Devices::Input; if (!s_mouse) return S_OK; if (s_mouse->mMode == MODE_RELATIVE) { MouseDelta delta; HRESULT hr = args->get_MouseDelta(&delta); ThrowIfFailed(hr); s_mouse->mState.x = delta.X; s_mouse->mState.y = delta.Y; ResetEvent( s_mouse->mRelativeRead.get() ); } return S_OK; } }; Mouse::Impl* Mouse::Impl::s_mouse = nullptr; void Mouse::SetWindow(ABI::Windows::UI::Core::ICoreWindow* window) { pImpl->SetWindow(window); } void Mouse::SetDpi(float dpi) { auto pImpl = Impl::s_mouse; if (!pImpl) return; pImpl->mDPI = dpi; } #elif defined(_XBOX_ONE) || ( defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) ) //====================================================================================== // Null device for Windows Phone and Xbox One //====================================================================================== class Mouse::Impl { public: Impl(Mouse* owner) : mOwner(owner) { if ( s_mouse ) { throw std::exception( "Mouse is a singleton" ); } s_mouse = this; } ~Impl() { s_mouse = nullptr; } void GetState(State& state) const { memset( &state, 0, sizeof(State) ); } void ResetScrollWheelValue() { } void SetMode(Mode mode) { UNREFERENCED_PARAMETER(mode); } Mouse* mOwner; static Mouse::Impl* s_mouse; }; Mouse::Impl* Mouse::Impl::s_mouse = nullptr; #else //====================================================================================== // Win32 desktop implementation //====================================================================================== // // For a Win32 desktop application, in your window setup be sure to call this method: // // m_mouse->SetWindow(hwnd); // // And call this static function from your Window Message Procedure // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) // { // switch (message) // { // case WM_ACTIVATEAPP: // case WM_INPUT: // case WM_MOUSEMOVE: // case WM_LBUTTONDOWN: // case WM_LBUTTONUP: // case WM_RBUTTONDOWN: // case WM_RBUTTONUP: // case WM_MBUTTONDOWN: // case WM_MBUTTONUP: // case WM_MOUSEWHEEL: // case WM_XBUTTONDOWN: // case WM_XBUTTONUP: // case WM_MOUSEHOVER: // Mouse::ProcessMessage(message, wParam, lParam); // break; // // } // } // class Mouse::Impl { public: Impl(Mouse* owner) : mOwner(owner), mWindow(nullptr), mMode(MODE_ABSOLUTE), mLastX(0), mLastY(0), mInFocus(true) { if ( s_mouse ) { throw std::exception( "Mouse is a singleton" ); } s_mouse = this; memset( &mState, 0, sizeof(State) ); mScrollWheelValue.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) ); mRelativeRead.reset( CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE) ); mAbsoluteMode.reset( CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE) ); mRelativeMode.reset( CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE) ); if ( !mScrollWheelValue || !mRelativeRead || !mAbsoluteMode || !mRelativeMode ) { throw std::exception( "CreateEventEx" ); } } ~Impl() { s_mouse = nullptr; } void GetState(State& state) const { memcpy( &state, &mState, sizeof(State) ); state.positionMode = mMode; DWORD result = WaitForSingleObjectEx( mScrollWheelValue.get(), 0, FALSE ); if ( result == WAIT_FAILED ) throw std::exception( "WaitForSingleObjectEx" ); if ( result == WAIT_OBJECT_0 ) { state.scrollWheelValue = 0; } if (state.positionMode == MODE_RELATIVE) { result = WaitForSingleObjectEx( mRelativeRead.get(), 0, FALSE ); if (result == WAIT_FAILED) throw std::exception("WaitForSingleObjectEx"); if (result == WAIT_OBJECT_0) { state.x = 0; state.y = 0; } else { SetEvent(mRelativeRead.get()); } } } void ResetScrollWheelValue() { SetEvent(mScrollWheelValue.get()); } void SetMode(Mode mode) { if (mMode == mode) return; SetEvent((mode == MODE_ABSOLUTE) ? mAbsoluteMode.get() : mRelativeMode.get()); assert(mWindow != 0); TRACKMOUSEEVENT tme; tme.cbSize = sizeof(tme); tme.dwFlags = TME_HOVER; tme.hwndTrack = mWindow; tme.dwHoverTime = 1; if (!TrackMouseEvent(&tme)) { throw std::exception("TrackMouseEvent"); } } void SetWindow(HWND window) { if (mWindow == window) return; assert(window != 0); RAWINPUTDEVICE Rid; Rid.usUsagePage = 0x1 /* HID_USAGE_PAGE_GENERIC */; Rid.usUsage = 0x2 /* HID_USAGE_GENERIC_MOUSE */; Rid.dwFlags = RIDEV_INPUTSINK; Rid.hwndTarget = window; if ( !RegisterRawInputDevices(&Rid, 1, sizeof(RAWINPUTDEVICE)) ) { throw std::exception("RegisterRawInputDevices"); } mWindow = window; } State mState; Mouse* mOwner; static Mouse::Impl* s_mouse; private: HWND mWindow; Mode mMode; ScopedHandle mScrollWheelValue; ScopedHandle mRelativeRead; ScopedHandle mAbsoluteMode; ScopedHandle mRelativeMode; int mLastX; int mLastY; bool mInFocus; friend void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam); void ClipToWindow() { assert(mWindow != 0); RECT rect; GetClientRect(mWindow, &rect); POINT ul; ul.x = rect.left; ul.y = rect.top; POINT lr; lr.x = rect.right; lr.y = rect.bottom; MapWindowPoints(mWindow, nullptr, &ul, 1); MapWindowPoints(mWindow, nullptr, &lr, 1); rect.left = ul.x; rect.top = ul.y; rect.right = lr.x; rect.bottom = lr.y; ClipCursor(&rect); } }; Mouse::Impl* Mouse::Impl::s_mouse = nullptr; void Mouse::SetWindow( HWND window ) { pImpl->SetWindow(window); } void Mouse::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam) { auto pImpl = Impl::s_mouse; if (!pImpl) return; HANDLE evts[3]; evts[0] = pImpl->mScrollWheelValue.get(); evts[1] = pImpl->mAbsoluteMode.get(); evts[2] = pImpl->mRelativeMode.get(); switch (WaitForMultipleObjectsEx(_countof(evts), evts, FALSE, 0, FALSE)) { case WAIT_OBJECT_0: pImpl->mState.scrollWheelValue = 0; ResetEvent(evts[0]); break; case (WAIT_OBJECT_0 + 1) : { pImpl->mMode = MODE_ABSOLUTE; ClipCursor(nullptr); POINT point; point.x = pImpl->mLastX; point.y = pImpl->mLastY; if (MapWindowPoints(pImpl->mWindow, nullptr, &point, 1)) { SetCursorPos(point.x, point.y); } ShowCursor(TRUE); pImpl->mState.x = pImpl->mLastX; pImpl->mState.y = pImpl->mLastY; } break; case (WAIT_OBJECT_0 + 2) : { ResetEvent( pImpl->mRelativeRead.get() ); pImpl->mMode = MODE_RELATIVE; pImpl->mState.x = pImpl->mState.y = 0; ShowCursor(FALSE); pImpl->ClipToWindow(); } break; case WAIT_FAILED: throw std::exception("WaitForMultipleObjectsEx"); } switch (message) { case WM_ACTIVATEAPP: if (wParam) { pImpl->mInFocus = true; if (pImpl->mMode == MODE_RELATIVE) { pImpl->mState.x = pImpl->mState.y = 0; ShowCursor(FALSE); pImpl->ClipToWindow(); } } else { int scrollWheel = pImpl->mState.scrollWheelValue; memset(&pImpl->mState, 0, sizeof(State)); pImpl->mState.scrollWheelValue = scrollWheel; pImpl->mInFocus = false; } return; case WM_INPUT: if (pImpl->mInFocus && pImpl->mMode == MODE_RELATIVE) { RAWINPUT raw; UINT rawSize = sizeof(raw); UINT resultData = GetRawInputData(reinterpret_cast(lParam), RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); if (resultData == UINT(-1)) { throw std::exception("GetRawInputData"); } if (raw.header.dwType == RIM_TYPEMOUSE) { if ( !(raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) ) { pImpl->mState.x = raw.data.mouse.lLastX; pImpl->mState.y = raw.data.mouse.lLastY; ResetEvent( pImpl->mRelativeRead.get() ); } // Note that with Remote Desktop input comes through as MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP, // so this imlementation doesn't suport relative mode via remote desktop. } } return; case WM_MOUSEMOVE: break; case WM_LBUTTONDOWN: pImpl->mState.leftButton = true; break; case WM_LBUTTONUP: pImpl->mState.leftButton = false; break; case WM_RBUTTONDOWN: pImpl->mState.rightButton = true; break; case WM_RBUTTONUP: pImpl->mState.rightButton = false; break; case WM_MBUTTONDOWN: pImpl->mState.middleButton = true; break; case WM_MBUTTONUP: pImpl->mState.middleButton = false; break; case WM_MOUSEWHEEL: pImpl->mState.scrollWheelValue += GET_WHEEL_DELTA_WPARAM(wParam); return; case WM_XBUTTONDOWN: switch (GET_XBUTTON_WPARAM(wParam)) { case XBUTTON1: pImpl->mState.xButton1 = true; break; case XBUTTON2: pImpl->mState.xButton2 = true; break; } break; case WM_XBUTTONUP: switch (GET_XBUTTON_WPARAM(wParam)) { case XBUTTON1: pImpl->mState.xButton1 = false; break; case XBUTTON2: pImpl->mState.xButton2 = false; break; } break; case WM_MOUSEHOVER: break; default: // Not a mouse message, so exit return; } if (pImpl->mMode == MODE_ABSOLUTE) { // All mouse messages provide a new pointer position int xPos = static_cast(LOWORD(lParam)); // GET_X_LPARAM(lParam); int yPos = static_cast(HIWORD(lParam)); // GET_Y_LPARAM(lParam); pImpl->mState.x = pImpl->mLastX = xPos; pImpl->mState.y = pImpl->mLastY = yPos; } } #endif #pragma warning( disable : 4355 ) // Public constructor. Mouse::Mouse() : pImpl( new Impl(this) ) { } // Move constructor. Mouse::Mouse(Mouse&& moveFrom) : pImpl(std::move(moveFrom.pImpl)) { pImpl->mOwner = this; } // Move assignment. Mouse& Mouse::operator= (Mouse&& moveFrom) { pImpl = std::move(moveFrom.pImpl); pImpl->mOwner = this; return *this; } // Public destructor. Mouse::~Mouse() { } Mouse::State Mouse::GetState() const { State state; pImpl->GetState(state); return state; } void Mouse::ResetScrollWheelValue() { pImpl->ResetScrollWheelValue(); } void Mouse::SetMode(Mode mode) { pImpl->SetMode(mode); } Mouse& Mouse::Get() { if ( !Impl::s_mouse || !Impl::s_mouse->mOwner ) throw std::exception( "Mouse is a singleton" ); return *Impl::s_mouse->mOwner; } //====================================================================================== // ButtonStateTracker //====================================================================================== #define UPDATE_BUTTON_STATE(field) field = static_cast( ( !!state.field ) | ( ( !!state.field ^ !!lastState.field ) << 1 ) ); void Mouse::ButtonStateTracker::Update( const Mouse::State& state ) { UPDATE_BUTTON_STATE(leftButton); assert( ( !state.leftButton && !lastState.leftButton ) == ( leftButton == UP ) ); assert( ( state.leftButton && lastState.leftButton ) == ( leftButton == HELD ) ); assert( ( !state.leftButton && lastState.leftButton ) == ( leftButton == RELEASED ) ); assert( ( state.leftButton && !lastState.leftButton ) == ( leftButton == PRESSED ) ); UPDATE_BUTTON_STATE(middleButton); UPDATE_BUTTON_STATE(rightButton); UPDATE_BUTTON_STATE(xButton1); UPDATE_BUTTON_STATE(xButton2); lastState = state; } #undef UPDATE_BUTTON_STATE void Mouse::ButtonStateTracker::Reset() { memset( this, 0, sizeof(ButtonStateTracker) ); }