#ifdef _MSC_VER // Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" // generated by SetClassLongPtr(). #pragma warning(disable:4244) #endif // HEADER FILES ------------------------------------------------------------ #define WIN32_LEAN_AND_MEAN #define DIRECTINPUT_VERSION 0x800 #define _WIN32_WINNT 0x0501 #include #include #define USE_WINDOWS_DWORD #include "i_input.h" #include "i_system.h" #include "d_event.h" #include "d_gui.h" #include "c_cvars.h" #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" #include "rawinput.h" // MACROS ------------------------------------------------------------------ #define DINPUT_BUFFERSIZE 32 // TYPES ------------------------------------------------------------------- class FRawMouse : public FMouse { public: FRawMouse(); ~FRawMouse(); bool GetDevice(); void ProcessInput(); bool WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result); void Grab(); void Ungrab(); protected: bool Grabbed; POINT UngrabbedPointerPos; }; class FDInputMouse : public FMouse { public: FDInputMouse(); ~FDInputMouse(); bool GetDevice(); void ProcessInput(); bool WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result); void Grab(); void Ungrab(); protected: LPDIRECTINPUTDEVICE8 Device; bool Grabbed; }; class FWin32Mouse : public FMouse { public: FWin32Mouse(); ~FWin32Mouse(); bool GetDevice(); void ProcessInput(); bool WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result); void Grab(); void Ungrab(); protected: void CenterMouse(int x, int y); POINT UngrabbedPointerPos; LONG PrevX, PrevY; bool Grabbed; }; enum EMouseMode { MM_None, MM_Win32, MM_DInput, MM_RawInput }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- static void SetCursorState(bool visible); static FMouse *CreateWin32Mouse(); static FMouse *CreateDInputMouse(); static FMouse *CreateRawMouse(); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern HWND Window; extern LPDIRECTINPUT8 g_pdi; extern LPDIRECTINPUT g_pdi3; extern bool GUICapture; extern bool HaveFocus; // PRIVATE DATA DEFINITIONS ------------------------------------------------ static bool NativeMouse; static EMouseMode MouseMode = MM_None; static FMouse *(*MouseFactory[])() = { CreateWin32Mouse, CreateDInputMouse, CreateRawMouse }; // PUBLIC DATA DEFINITIONS ------------------------------------------------- FMouse *Mouse; HCURSOR TheArrowCursor; HCURSOR TheInvisibleCursor; CVAR (Bool, use_mouse, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, m_noprescale, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, m_filter, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CUSTOM_CVAR (Int, in_mouse, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) { if (self < 0) { self = 0; } else if (self > 3) { self = 3; } else { I_StartupMouse(); } } CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) { if (self < 0) { self = 0; } else if (self > 2) { self = 2; } } // CODE -------------------------------------------------------------------- //========================================================================== // // SetCursorState // // Ensures the cursor is either visible or invisible. // //========================================================================== static void SetCursorState(bool visible) { HCURSOR usingCursor = visible ? TheArrowCursor : TheInvisibleCursor; SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)usingCursor); //if (HaveFocus) { SetCursor(usingCursor); if (visible) { ShowCursor(TRUE); } else { while (ShowCursor(FALSE) >= 0) { } } } } //========================================================================== // // CaptureMode_InGame // //========================================================================== static bool CaptureMode_InGame() { if (mouse_capturemode == 2) { return true; } else if (mouse_capturemode == 1) { return gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_FINALE; } else { return gamestate == GS_LEVEL; } } //========================================================================== // // I_CheckNativeMouse // // Should we be capturing mouse input, or should we let the OS have normal // control of it (i.e. native mouse)? // //========================================================================== void I_CheckNativeMouse(bool preferNative) { bool wantNative = (GetFocus() != Window) || ((!screen || !screen->IsFullscreen()) && (!CaptureMode_InGame() || GUICapture || paused || preferNative || !use_mouse || demoplayback)); //Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse); if (wantNative != NativeMouse) { if (Mouse != NULL) { NativeMouse = wantNative; if (wantNative) { Mouse->Ungrab(); } else { Mouse->Grab(); } } } } //========================================================================== // // FMouse - Constructor // //========================================================================== FMouse::FMouse() { LastX = LastY = 0; ButtonState = 0; } //========================================================================== // // FMouse :: PostMouseMove // // Posts a mouse movement event, potentially averaging it with the previous // movement. If there is no movement to post, then no event is generated. // //========================================================================== void FMouse::PostMouseMove(int x, int y) { event_t ev = { 0 }; if (m_filter) { ev.x = (x + LastX) / 2; ev.y = (y + LastY) / 2; } else { ev.x = x; ev.y = y; } LastX = x; LastY = y; if (ev.x | ev.y) { ev.type = EV_Mouse; D_PostEvent(&ev); } } //========================================================================== // // FMouse :: WheelMoved // // Generates events for a wheel move. Events are generated for every // WHEEL_DELTA units that the wheel has moved. In normal mode, each move // generates both a key down and a key up event. In GUI mode, only one // event is generated for each unit of movement. // //========================================================================== void FMouse::WheelMoved(int wheelmove) { event_t ev = { 0 }; int dir; if (GUICapture) { ev.type = EV_GUI_Event; if (wheelmove < 0) { dir = WHEEL_DELTA; ev.subtype = EV_GUI_WheelDown; } else { dir = -WHEEL_DELTA; ev.subtype = EV_GUI_WheelUp; } /* FIXME ev.data3 = ((KeyState[VK_SHIFT]&128) ? GKM_SHIFT : 0) | ((KeyState[VK_CONTROL]&128) ? GKM_CTRL : 0) | ((KeyState[VK_MENU]&128) ? GKM_ALT : 0); */ while (abs(wheelmove) >= WHEEL_DELTA) { D_PostEvent(&ev); wheelmove += dir; } } else { if (wheelmove < 0) { dir = WHEEL_DELTA; ev.data1 = KEY_MWHEELDOWN; } else { dir = -WHEEL_DELTA; ev.data1 = KEY_MWHEELUP; } while (abs(wheelmove) >= WHEEL_DELTA) { ev.type = EV_KeyDown; D_PostEvent(&ev); ev.type = EV_KeyUp; D_PostEvent(&ev); wheelmove += dir; } } } //========================================================================== // // FMouse :: PostButtonEvent // // Posts a mouse button up/down event. Down events are always posted. Up // events will only be sent if the button is currently marked as down. // //========================================================================== void FMouse::PostButtonEvent(int button, bool down) { event_t ev = { 0 }; int mask = 1 << button; ev.data1 = KEY_MOUSE1 + button; if (down) { ButtonState |= mask; ev.type = EV_KeyDown; D_PostEvent(&ev); } else if (ButtonState & mask) { ButtonState &= ~mask; ev.type = EV_KeyUp; D_PostEvent(&ev); } } //========================================================================== // // FMouse :: ClearButtonState // // Sends key up events for all buttons that are currently down and marks // them as up. Used when focus is lost and we can no longer track up events, // so get them marked up right away. // //========================================================================== void FMouse::ClearButtonState() { if (ButtonState != 0) { int i, mask; event_t ev = { 0 }; ev.type = EV_KeyUp; for (i = sizeof(ButtonState) * 8, mask = 1; i > 0; --i, mask <<= 1) { if (ButtonState & mask) { ev.data1 = KEY_MOUSE1 + (int)sizeof(ButtonState) * 8 - i; D_PostEvent(&ev); } } ButtonState = 0; } } //========================================================================== // // CreateRawMouse // //========================================================================== static FMouse *CreateRawMouse() { return new FRawMouse; } //========================================================================== // // FRawMouse - Constructor // //========================================================================== FRawMouse::FRawMouse() { Grabbed = false; } //========================================================================== // // FRawMouse - Destructor // //========================================================================== FRawMouse::~FRawMouse() { Ungrab(); } //========================================================================== // // FRawMouse :: GetDevice // // Ensure the API is present and we can listen for mouse input. // //========================================================================== bool FRawMouse::GetDevice() { RAWINPUTDEVICE rid; if (MyRegisterRawInputDevices == NULL) { return false; } rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE; rid.usUsage = HID_GDP_MOUSE; rid.dwFlags = 0; rid.hwndTarget = Window; if (!MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) { return false; } rid.dwFlags = RIDEV_REMOVE; rid.hwndTarget = NULL; // Must be NULL for RIDEV_REMOVE. MyRegisterRawInputDevices(&rid, 1, sizeof(rid)); return true; } //========================================================================== // // FRawMouse :: ProcessInput // // All input comes through WM_INPUT messages, so nothing to do here. // //========================================================================== void FRawMouse::ProcessInput() { } //========================================================================== // // FRawMouse :: Grab // //========================================================================== void FRawMouse::Grab() { if (!Grabbed) { RAWINPUTDEVICE rid; rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE; rid.usUsage = HID_GDP_MOUSE; rid.dwFlags = RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY; rid.hwndTarget = Window; if (MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) { GetCursorPos(&UngrabbedPointerPos); Grabbed = true; SetCursorState(false); // By setting the cursor position, we force the pointer image // to change right away instead of having it delayed until // some time in the future. SetCursorPos(0, 0); } } } //========================================================================== // // FRawMouse :: Ungrab // //========================================================================== void FRawMouse::Ungrab() { if (Grabbed) { RAWINPUTDEVICE rid; rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE; rid.usUsage = HID_GDP_MOUSE; rid.dwFlags = RIDEV_REMOVE; rid.hwndTarget = NULL; if (MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) { Grabbed = false; ClearButtonState(); } SetCursorState(true); SetCursorPos(UngrabbedPointerPos.x, UngrabbedPointerPos.y); } } //========================================================================== // // FRawMouse :: WndProcHook // //========================================================================== bool FRawMouse::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) { if (!Grabbed) { return false; } if (message == WM_INPUT) { BYTE buffer[40]; UINT size = sizeof(buffer); int i; USHORT mask; if (MyGetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &size, sizeof(RAWINPUTHEADER)) > 0) { RAWINPUT *raw = (RAWINPUT *)buffer; if (raw->header.dwType != RIM_TYPEMOUSE) { return false; } // Check buttons. The up and down motions are stored in the usButtonFlags field. // The ulRawButtons field, unfortunately, is device-dependant, and may well // not contain any data at all. This means it is apparently impossible // to read more than five mouse buttons with Windows, because RI_MOUSE_WHEEL // gets in the way when trying to extrapolate to more than five. for (i = 0, mask = 1; i < 5; ++i) { if (raw->data.mouse.usButtonFlags & mask) // button down { PostButtonEvent(i, true); } mask <<= 1; if (raw->data.mouse.usButtonFlags & mask) // button up { PostButtonEvent(i, false); } mask <<= 1; } if (raw->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { WheelMoved(raw->data.mouse.usButtonData); } PostMouseMove(m_noprescale ? raw->data.mouse.lLastX : raw->data.mouse.lLastX<<2, -raw->data.mouse.lLastY); } return false; } else if (message == WM_SYSCOMMAND) { wParam &= 0xFFF0; if (wParam == SC_MOVE || wParam == SC_SIZE) { return true; } } return false; } //========================================================================== // // CreateDInputMouse // //========================================================================== static FMouse *CreateDInputMouse() { return new FDInputMouse; } //========================================================================== // // FDInputMouse - Constructor // //========================================================================== FDInputMouse::FDInputMouse() { Device = NULL; Grabbed = false; } //========================================================================== // // FDInputMouse - Destructor // //========================================================================== FDInputMouse::~FDInputMouse() { if (Device != NULL) { Device->Release(); Device = NULL; } } //========================================================================== // // FDInputMouse :: GetDevice // // Create the device interface and initialize it. // //========================================================================== bool FDInputMouse::GetDevice() { HRESULT hr; if (g_pdi3 != NULL) { // DirectInput3 interface hr = g_pdi3->CreateDevice(GUID_SysMouse, (LPDIRECTINPUTDEVICE*)&Device, NULL); } else if (g_pdi != NULL) { // DirectInput8 interface hr = g_pdi->CreateDevice(GUID_SysMouse, &Device, NULL); } else { hr = -1; } if (FAILED(hr)) { return false; } // How many buttons does this mouse have? DIDEVCAPS_DX3 caps = { sizeof(caps) }; hr = Device->GetCapabilities((DIDEVCAPS *)&caps); // If that failed, just assume four buttons. if (FAILED(hr)) { caps.dwButtons = 4; } // Now select the data format with enough buttons for this mouse. // (Can we use c_dfDIMouse2 with DirectInput3? If so, then we could just set // that unconditionally.) hr = Device->SetDataFormat(caps.dwButtons <= 4 ? &c_dfDIMouse : &c_dfDIMouse2); if (FAILED(hr)) { ufailit: Device->Release(); Device = NULL; return false; } // Set cooperative level. hr = Device->SetCooperativeLevel(Window, DISCL_EXCLUSIVE | DISCL_FOREGROUND); if (FAILED(hr)) { goto ufailit; } // Set buffer size so we can use buffered input. DIPROPDWORD prop; prop.diph.dwSize = sizeof(prop); prop.diph.dwHeaderSize = sizeof(prop.diph); prop.diph.dwObj = 0; prop.diph.dwHow = DIPH_DEVICE; prop.dwData = DINPUT_BUFFERSIZE; hr = Device->SetProperty(DIPROP_BUFFERSIZE, &prop.diph); if (FAILED(hr)) { goto ufailit; } return true; } //========================================================================== // // FDInputMouse::WndProcHook // //========================================================================== bool FDInputMouse::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) { // Do not allow window sizing or moving activity while the mouse is // grabbed, because they will never be able to complete, causing mouse // input to hang until the mouse is ungrabbed (e.g. by alt-tabbing away). if (Grabbed && message == WM_SYSCOMMAND) { wParam &= 0xFFF0; if (wParam == SC_MOVE || wParam == SC_SIZE) { return true; } } return false; } //========================================================================== // // FDInputMouse :: ProcessInput // // Posts any events that have accumulated since the previous call. // //========================================================================== void FDInputMouse::ProcessInput() { DIDEVICEOBJECTDATA od; DWORD dwElements; HRESULT hr; int count = 0; int dx, dy; dx = 0; dy = 0; if (!Grabbed) return; event_t ev = { 0 }; for (;;) { DWORD cbObjectData = g_pdi3 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA); dwElements = 1; hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0); if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) { Grab(); hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0); } /* Unable to read data or no data available */ if (FAILED(hr) || !dwElements) break; count++; /* Look at the element to see what happened */ // GCC does not like putting the DIMOFS_ macros in case statements, // so use ifs instead. if (od.dwOfs == (DWORD)DIMOFS_X) { dx += od.dwData; } else if (od.dwOfs == (DWORD)DIMOFS_Y) { dy += od.dwData; } else if (od.dwOfs == (DWORD)DIMOFS_Z) { WheelMoved(od.dwData); } else if (od.dwOfs >= (DWORD)DIMOFS_BUTTON0 && od.dwOfs <= (DWORD)DIMOFS_BUTTON7) { /* [RH] Mouse button events mimic keydown/up events */ if (!GUICapture) { PostButtonEvent(od.dwOfs - DIMOFS_BUTTON0, !!(od.dwData & 0x80)); } } } PostMouseMove(m_noprescale ? dx : dx<<2, -dy); } //========================================================================== // // FDInputMouse :: Grab // //========================================================================== void FDInputMouse::Grab() { if (SUCCEEDED(Device->Acquire())) { Grabbed = true; SetCursorState(false); } } //========================================================================== // // FDInputMouse :: Ungrab // //========================================================================== void FDInputMouse::Ungrab() { Device->Unacquire(); Grabbed = false; SetCursorState(true); ClearButtonState(); } /**************************************************************************/ /**************************************************************************/ //========================================================================== // // CreateWin32Mouse // //========================================================================== static FMouse *CreateWin32Mouse() { return new FWin32Mouse; } //========================================================================== // // FWin32Mouse - Constructor // //========================================================================== FWin32Mouse::FWin32Mouse() { GetCursorPos(&UngrabbedPointerPos); Grabbed = false; } //========================================================================== // // FWin32Mouse - Destructor // //========================================================================== FWin32Mouse::~FWin32Mouse() { Ungrab(); } //========================================================================== // // FWin32Mouse :: GetDevice // // The Win32 mouse is always available, since it is the lowest common // denominator. (Even if it's not connected, it is still considered as // "available"; it just won't generate any events.) // //========================================================================== bool FWin32Mouse::GetDevice() { return true; } //========================================================================== // // FWin32Mouse :: ProcessInput // // Get current mouse position and post events if the mouse has moved from // last time. // //========================================================================== void FWin32Mouse::ProcessInput() { POINT pt; int x, y; if (!Grabbed || !GetCursorPos(&pt)) { return; } x = pt.x - PrevX; y = PrevY - pt.y; if (!m_noprescale) { x *= 3; y *= 2; } if (x | y) { CenterMouse(pt.x, pt.y); } PostMouseMove(x, y); } //========================================================================== // // FWin32Mouse :: WndProcHook // // Intercepts mouse-related window messages if the mouse is grabbed. // //========================================================================== bool FWin32Mouse::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) { if (!Grabbed) { return false; } if (message == WM_SIZE) { if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) { CenterMouse(-1, -1); return true; } } else if (message == WM_MOVE) { CenterMouse(-1, -1); return true; } else if (message == WM_SYSCOMMAND) { // Do not enter the window moving and sizing modes while grabbed, // because those require the mouse. wParam &= 0xFFF0; if (wParam == SC_MOVE || wParam == SC_SIZE) { return true; } } else if (message == WM_MOUSEWHEEL) { WheelMoved(HIWORD(wParam)); return true; } else if (message >= WM_LBUTTONDOWN && message <= WM_MBUTTONUP) { int action = (message - WM_LBUTTONDOWN) % 3; int button = (message - WM_LBUTTONDOWN) / 3; if (action == 2) { // double clicking we care not about. return false; } event_t ev = { 0 }; ev.type = action ? EV_KeyUp : EV_KeyDown; ev.data1 = KEY_MOUSE1 + button; if (action) { ButtonState &= ~(1 << button); } else { ButtonState |= 1 << button; } D_PostEvent(&ev); return true; } else if (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONUP) { // Microsoft's (lack of) documentation for the X buttons is unclear on whether // or not simultaneous pressing of multiple X buttons will ever be merged into // a single message. Winuser.h describes the button field as being filled with // flags, which suggests that it could merge them. My testing // indicates it does not, but I will assume it might in the future. WORD xbuttons = GET_XBUTTON_WPARAM(wParam); event_t ev = { 0 }; ev.type = (message == WM_XBUTTONDOWN) ? EV_KeyDown : EV_KeyUp; // There are only two X buttons defined presently, so I extrapolate from // the current winuser.h values to support up to 8 mouse buttons. for (int i = 0; i < 5; ++i, xbuttons >>= 1) { if (xbuttons & 1) { ev.data1 = KEY_MOUSE4 + i; if (ev.type == EV_KeyDown) { ButtonState |= 1 << (i + 4); } else { ButtonState &= ~(1 << (i + 4)); } D_PostEvent(&ev); } } *result = TRUE; return true; } return false; } //========================================================================== // // FWin32Mouse :: Grab // // Hides the mouse and locks it inside the window boundaries. // //========================================================================== void FWin32Mouse::Grab() { RECT rect; if (Grabbed) { return; } GetCursorPos(&UngrabbedPointerPos); ClipCursor(NULL); // helps with Win95? GetClientRect(Window, &rect); // Reposition the rect so that it only covers the client area. ClientToScreen(Window, (LPPOINT)&rect.left); ClientToScreen(Window, (LPPOINT)&rect.right); ClipCursor(&rect); SetCursorState(false); CenterMouse(-1, -1); Grabbed = true; } //========================================================================== // // FWin32Mouse :: Ungrab // // Shows the mouse and lets it roam free. // //========================================================================== void FWin32Mouse::Ungrab() { if (!Grabbed) { return; } ClipCursor(NULL); SetCursorPos(UngrabbedPointerPos.x, UngrabbedPointerPos.y); SetCursorState(true); Grabbed = false; ClearButtonState(); } //========================================================================== // // FWin32Mouse :: CenterMouse // // Moves the mouse to the center of the window, but only if the current // position isn't already in the center. // //========================================================================== void FWin32Mouse::CenterMouse(int curx, int cury) { RECT rect; GetWindowRect (Window, &rect); int centx = (rect.left + rect.right) >> 1; int centy = (rect.top + rect.bottom) >> 1; // Reduce the number of WM_MOUSEMOVE messages that get sent // by only calling SetCursorPos when we really need to. if (centx != curx || centy != cury) { PrevX = centx; PrevY = centy; SetCursorPos (centx, centy); } } //========================================================================== // // I_StartupMouse // // Called during game init and whenever in_mouse changes. // //========================================================================== void I_StartupMouse () { EMouseMode new_mousemode; switch(in_mouse) { case 0: default: if (OSPlatform == os_WinNT4) { new_mousemode = MM_Win32; } else if (MyRegisterRawInputDevices != NULL) { new_mousemode = MM_RawInput; } else { new_mousemode = MM_DInput; } break; case 1: new_mousemode = MM_Win32; break; case 2: new_mousemode = MM_DInput; break; case 3: new_mousemode = MM_RawInput; break; } if (new_mousemode != MouseMode) { if (Mouse != NULL) { delete Mouse; } do { Mouse = MouseFactory[new_mousemode - MM_Win32](); if (Mouse != NULL) { if (Mouse->GetDevice()) { break; } delete Mouse; Mouse = NULL; } new_mousemode = (EMouseMode)(new_mousemode - 1); } while (new_mousemode != MM_None); MouseMode = new_mousemode; NativeMouse = true; } HaveFocus = (GetFocus() == Window); }