#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_INPUT_SOURCE 1 #include "Nuclex/Input/Config.h" #if defined(NUCLEX_INPUT_WIN32) #include "WindowMessageMouse.h" #include namespace Nuclex { namespace Input { namespace Devices { // ------------------------------------------------------------------------------------------- // WindowMessageMouse::WindowMessageMouse(HWND windowHandle) : windowHandle(windowHandle), isLocked(false), hasFocus(false), isInsideMenuLoop(false), isTrackingMouse(false), isMouseInsideClientArea(false), lastKnownMouseX(-1), lastKnownMouseY(-1) { this->trackMouseEvent.cbSize = sizeof(this->trackMouseEvent); this->trackMouseEvent.dwFlags = TME_LEAVE; this->trackMouseEvent.dwHoverTime = 0; this->trackMouseEvent.hwndTrack = windowHandle; HWND focusedWindow = ::GetFocus(); this->hasFocus = (focusedWindow == windowHandle); updateClientRectangleInScreenCoordinates(); registerForRawInput(); } // ------------------------------------------------------------------------------------------- // WindowMessageMouse::~WindowMessageMouse() { if(this->isTrackingMouse) { stopTrackingMouseLeave(false); } if(this->isLocked) { freeMouse(); } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::SetLocked(bool locked /* = true */) { // Unlock? if(this->isLocked && !locked) { this->isLocked = false; freeMouse(); } // Lock? if(!this->isLocked && locked) { this->isLocked = true; updateClientRectangleInScreenCoordinates(); restrictMouse(); // Because the first message will be WM_SETCURSOR and WM_MOUSEMOVE will only trigger // after it, not hiding the mouse cursor unless isMouseInsideClientArea is set. if(isMouseRestricted()) { this->isMouseInsideClientArea = true; } } } // ------------------------------------------------------------------------------------------- // bool WindowMessageMouse::ProcessWindowMessage( UINT message, WPARAM parameter1, LPARAM parameter2 ) { switch(message) { case WM_INPUT: { HRAWINPUT rawInputHandle = reinterpret_cast(parameter2); processRawInput(rawInputHandle); return true; // handled } case WM_SETCURSOR: { bool shouldCursorBeHidden = this->isLocked && this->isMouseInsideClientArea && this->hasFocus && !this->isInsideMenuLoop; if(shouldCursorBeHidden) { ::SetCursor(nullptr); return true; // handled - we picked the mouse cursor } return false; // unhandled - let DefWindowProc pick the mouse cursor } case WM_MOUSELEAVE: { this->isTrackingMouse = false; this->isMouseInsideClientArea = false; if(this->isLocked) { freeMouse(); } callMouseMovedIfPositionChanged(static_cast(-1)); return true; // handled; } case WM_MOUSEMOVE: { if(!this->isTrackingMouse) { startTrackingMouseLeave(); this->isTrackingMouse = true; } this->isMouseInsideClientArea = true; if(this->isLocked && this->hasFocus && !this->isInsideMenuLoop) { if(!isMouseRestricted()) { restrictMouse(); } } callMouseMovedIfPositionChanged(parameter2); return true; // handled } case WM_LBUTTONDOWN: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonPress(MouseButtons::Left); return true; // handled } case WM_LBUTTONUP: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonRelease(MouseButtons::Left); return true; // handled } case WM_RBUTTONDOWN: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonPress(MouseButtons::Right); return true; // handled } case WM_RBUTTONUP: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonRelease(MouseButtons::Right); return true; // handled } case WM_MBUTTONDOWN: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonPress(MouseButtons::Middle); return true; // handled } case WM_MBUTTONUP: { callMouseMovedIfPositionChanged(parameter2); BufferedMouse::BufferButtonRelease(MouseButtons::Middle); return true; // handled } case WM_XBUTTONDOWN: { callMouseMovedIfPositionChanged(parameter2); WORD button = GET_XBUTTON_WPARAM(parameter1); if(button == XBUTTON1) { BufferedMouse::BufferButtonPress(MouseButtons::X1); } else if(button == XBUTTON2) { BufferedMouse::BufferButtonPress(MouseButtons::X2); } return true; // handled } case WM_XBUTTONUP: { callMouseMovedIfPositionChanged(parameter2); WORD button = GET_XBUTTON_WPARAM(parameter1); if(button == XBUTTON1) { BufferedMouse::BufferButtonRelease(MouseButtons::X1); } else if(button == XBUTTON2) { BufferedMouse::BufferButtonRelease(MouseButtons::X2); } return true; // handled } case WM_ACTIVATE: { HWND affectedWindowHandle = reinterpret_cast(parameter2); if((affectedWindowHandle == this->windowHandle) || (affectedWindowHandle == nullptr)) { WORD action = LOWORD(parameter1); windowActivationStateChanged(action); } return false; // unhandled - we still want DefWindowProc to do its stuff } case WM_ENTERMENULOOP: { this->isInsideMenuLoop = true; if(this->isLocked) { freeMouse(); } return false; // unhandled - let DefWindowProc take care of the menu stuff } case WM_EXITMENULOOP: { this->isInsideMenuLoop = false; return false; // unhandled - let DefWindowProc exit the menu loop itself } case WM_MOVING: case WM_SIZING: case WM_WINDOWPOSCHANGING: case WM_MOVE: case WM_SIZE: case WM_WINDOWPOSCHANGED: { updateClientRectangleInScreenCoordinates(); return false; // unhandled - let DefWindowProc handle the window move/resize } default: { return false; // unhandled } } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::windowActivationStateChanged(WORD activationAction) { switch(activationAction) { case WA_CLICKACTIVE: { this->hasFocus = true; break; } case WA_ACTIVE: { this->hasFocus = true; break; } case WA_INACTIVE: { this->hasFocus = false; break; } } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::processRawInput(HRAWINPUT rawInputHandle) { RAWINPUT rawInput; UINT rawInputSize = sizeof(rawInput); UINT result = ::GetRawInputData( rawInputHandle, RID_INPUT, &rawInput, &rawInputSize, sizeof(rawInput.header) ); if(result == static_cast(-1)) { throw std::runtime_error("Could not retrieve raw mouse input"); } if(!this->isMouseInsideClientArea) { return; } if(rawInput.header.dwType == RIM_TYPEMOUSE) { if(rawInput.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { // Incredibly, there seems to be no way of telling the DPI of the mouse. // I'd like to provide the game with more accurate input, not faster input. BufferedMouse::BufferMovement( static_cast(rawInput.data.mouse.lLastX) / 4.0f, static_cast(rawInput.data.mouse.lLastY) / 4.0f ); } } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::registerForRawInput() { const static USHORT HID_USAGE_PAGE_GENERIC = 0x01; const static USHORT HID_USAGE_GENERIC_MOUSE = 0x02; RAWINPUTDEVICE mouseRawInputDevice; mouseRawInputDevice.hwndTarget = this->windowHandle; mouseRawInputDevice.usUsagePage = HID_USAGE_PAGE_GENERIC; mouseRawInputDevice.usUsage = HID_USAGE_GENERIC_MOUSE; mouseRawInputDevice.dwFlags = 0; BOOL result = ::RegisterRawInputDevices( &mouseRawInputDevice, 1, sizeof(mouseRawInputDevice) ); if(result == FALSE) { throw std::runtime_error("Could not register for raw mouse input"); } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::callMouseMovedIfPositionChanged(LPARAM parameter) { int x = GET_X_LPARAM(parameter); int y = GET_Y_LPARAM(parameter); if((x != this->lastKnownMouseX) || (y != this->lastKnownMouseY)) { BufferedMouse::BufferCursorMovement(x, y); } } // ------------------------------------------------------------------------------------------- // const std::string &WindowMessageMouse::GetName() const { static std::string name("Default Windows Mouse"); return name; } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::restrictMouse() { BOOL result = ::ClipCursor(&this->clientRectangleInScreenCoordinates); if(result == FALSE) { throw std::runtime_error("Could not limit mouse cursor to the game's window"); } } // ------------------------------------------------------------------------------------------- // bool WindowMessageMouse::isMouseRestricted() const { RECT clippingRectangle; BOOL result = ::GetClipCursor(&clippingRectangle); if(result == FALSE) { throw std::runtime_error("Could not obtain the area the mouse cursor is restricted to"); } return (clippingRectangle.left == this->clientRectangleInScreenCoordinates.left) && (clippingRectangle.right == this->clientRectangleInScreenCoordinates.right) && (clippingRectangle.top == this->clientRectangleInScreenCoordinates.top) && (clippingRectangle.bottom == this->clientRectangleInScreenCoordinates.bottom); } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::freeMouse() { BOOL result = ::ClipCursor(nullptr); if(result == FALSE) { throw std::runtime_error("Could not release movement restriction on the mouse cursor"); } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::startTrackingMouseLeave() { this->trackMouseEvent.dwFlags = TME_LEAVE; BOOL result = ::TrackMouseEvent(&this->trackMouseEvent); if(result == FALSE) { throw std::runtime_error("Could not begin tracking mouse events"); } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::stopTrackingMouseLeave(bool throwOnError /* = true */) { this->trackMouseEvent.dwFlags = TME_CANCEL | TME_LEAVE; BOOL result = ::TrackMouseEvent(&this->trackMouseEvent); if(throwOnError) { if(result == FALSE) { throw std::runtime_error("Could not stop tracking mouse events"); } } } // ------------------------------------------------------------------------------------------- // void WindowMessageMouse::updateClientRectangleInScreenCoordinates() { // Obtain the client rectangle of the window in window coordinates BOOL result = ::GetClientRect( this->windowHandle, &this->clientRectangleInScreenCoordinates ); if(result == FALSE) { throw std::runtime_error("Unable to obtain client rectangle of the game window"); } // Now find out where the window is on the screen. We're assuming the window message // loop is called synchronously, that is, it is not possible for the window to be moved // between the call to GetClientRect() and ClientToScreen(). POINT clientRectangleScreenCoordinates = {0}; result = ::ClientToScreen(windowHandle, &clientRectangleScreenCoordinates); if(result == FALSE) { throw std::runtime_error("Could not convert window client area to screen coordinates"); } // Add the window position to the rectangle so it is translated into screen coordinates this->clientRectangleInScreenCoordinates.left += clientRectangleScreenCoordinates.x; this->clientRectangleInScreenCoordinates.top += clientRectangleScreenCoordinates.y; this->clientRectangleInScreenCoordinates.right += clientRectangleScreenCoordinates.x; this->clientRectangleInScreenCoordinates.bottom += clientRectangleScreenCoordinates.y; } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Input::Devices #endif // defined(NUCLEX_INPUT_WIN32)