From 86023c23cab221dda622a15be3c471ea642ce1d5 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 23 May 2009 02:22:30 +0000 Subject: [PATCH] - Fixed: When using Win32 mouse, windowed mode, alt-tabbing away and then clicking on the window's title bar moved it practically off the screen. - Beginnings of i_input.cpp rewrite: Win32 and DirectInput mouse handling has been moved into classes. SVN r1597 (trunk) --- docs/rh-log.txt | 8 +- src/win32/i_input.cpp | 1224 +++++++++++++++++++++++++---------------- 2 files changed, 757 insertions(+), 475 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 24d9fa026..dc4435804 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,10 @@ -May 20, 2009 (Changes by Graf Zahl) +May 22, 2009 +- Fixed: When using Win32 mouse, windowed mode, alt-tabbing away and then + clicking on the window's title bar moved it practically off the screen. +- Beginnings of i_input.cpp rewrite: Win32 and DirectInput mouse handling has + been moved into classes. + +May 20, 2009 (Changes by Graf Zahl) - Fixed: The SBARINFO parser compared an FString in the GAMEINFO with a NULL pointer and tried to load a lump with an empty name as statusbar script for non-Doom games. diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index 04028e758..65502c278 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -105,6 +105,33 @@ #define DINPUT_BUFFERSIZE 32 + +class FInputDevice +{ +public: + virtual bool GetDevice() = 0; + virtual void ProcessInput() = 0; + virtual bool WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result); +}; + +class FMouse : public FInputDevice +{ +public: + FMouse(); + + virtual void Grab() = 0; + virtual void Ungrab() = 0; + +protected: + void PostMouseMove(int x, int y); + void WheelMoved(int wheelmove); + void PostButtonEvent(int button, bool down); + void ClearButtonState(); + + int LastX, LastY; // for m_filter + WORD ButtonState; // bit mask of current button states (1=down, 0=up) +}; + #ifdef _DEBUG #define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS #else @@ -125,23 +152,14 @@ static HMODULE DInputDLL; static void KeyRead (); static BOOL I_StartupKeyboard (); static void I_StartupMouse (); -static void MouseRead_DI (); -static void MouseRead_Win32 (); -static void GrabMouse_Win32 (); -static void UngrabMouse_Win32 (); -static BOOL I_GetDIMouse (); -static void I_GetWin32Mouse (); static void CenterMouse_Win32 (LONG curx, LONG cury); -static void WheelMoved (); static void DI_Acquire (LPDIRECTINPUTDEVICE8 mouse); static void DI_Unacquire (LPDIRECTINPUTDEVICE8 mouse); -static void SetCursorState (int visible); +static void SetCursorState (bool visible); static HRESULT InitJoystick (); static bool GUICapture; -static int NativeMouse; -static bool MakeMouseEvents; -static POINT UngrabbedPointerPos; +static bool NativeMouse; bool VidResizing; @@ -157,13 +175,12 @@ EXTERN_CVAR (String, language) EXTERN_CVAR (Bool, lookstrafe) -typedef enum { win32, dinput } mousemode_t; -static mousemode_t mousemode; +typedef enum { none, win32, dinput } mousemode_t; +static mousemode_t mousemode = none; extern BOOL paused; static bool HaveFocus = false; static bool noidle = false; -static int WheelMove; static LPDIRECTINPUT8 g_pdi; static LPDIRECTINPUT g_pdi3; @@ -176,7 +193,8 @@ static LPDIRECTINPUTDEVICE8 g_pJoy; // pointer for each device instead of two. static LPDIRECTINPUTDEVICE8 g_pKey; -static LPDIRECTINPUTDEVICE8 g_pMouse; + +FMouse *Mouse; HCURSOR TheArrowCursor, TheInvisibleCursor; @@ -442,7 +460,7 @@ static bool inGame() } } -void I_CheckNativeMouse (bool preferNative) +void I_CheckNativeMouse(bool preferNative) { bool wantNative = !HaveFocus || ((!screen || !screen->IsFullscreen()) && @@ -450,35 +468,15 @@ void I_CheckNativeMouse (bool preferNative) //Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse); - if (int(wantNative) != NativeMouse) + if (wantNative != NativeMouse) { if (wantNative) { - if (mousemode == dinput) - { - DI_Unacquire (g_pMouse); - } - else - { - UngrabMouse_Win32 (); - SetCursorPos (UngrabbedPointerPos.x, UngrabbedPointerPos.y); - } - FlushDIKState (KEY_MOUSE1, KEY_MOUSE8); + Mouse->Ungrab(); } else { - if (mousemode == win32) - { - if (NativeMouse >= 0) - { - GetCursorPos (&UngrabbedPointerPos); - } - GrabMouse_Win32 (); - } - else - { - DI_Acquire (g_pMouse); - } + Mouse->Grab(); } NativeMouse = wantNative; } @@ -486,6 +484,15 @@ void I_CheckNativeMouse (bool preferNative) LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (Mouse != NULL) + { + LRESULT result; + + if (Mouse->WndProcHook(hWnd, message, wParam, lParam, &result)) + { + return result; + } + } event_t event; memset (&event, 0, sizeof(event)); @@ -534,23 +541,9 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_SIZE: - if (mousemode == win32 && !NativeMouse && - (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)) - { - CenterMouse_Win32 (-1, -1); - return 0; - } InvalidateRect (Window, NULL, FALSE); break; - case WM_MOVE: - if (mousemode == win32 && !NativeMouse) - { - CenterMouse_Win32 (-1, -1); - return 0; - } - break; - // Being forced to separate my keyboard input handler into // two pieces like this really stinks. (IMHO) case WM_KEYDOWN: @@ -697,14 +690,7 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: - if (MakeMouseEvents && mousemode == win32) - { - event.type = ((message - WM_LBUTTONDOWN) % 3) ? EV_KeyUp : EV_KeyDown; - event.data1 = KEY_MOUSE1 + (message - WM_LBUTTONDOWN) / 3; - DIKState[ActiveDIKState][event.data1] = (event.type == EV_KeyDown); - D_PostEvent (&event); - } - else if (GUICapture) + if (GUICapture) { event.type = EV_GUI_Event; if (message >= WM_LBUTTONDOWN && message <= WM_LBUTTONDBLCLK) @@ -723,41 +709,6 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } break; - case WM_XBUTTONDOWN: - case 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. - if (MakeMouseEvents && mousemode == win32) - { - WORD xbuttons = GET_XBUTTON_WPARAM (wParam); - - event.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) - { - event.data1 = KEY_MOUSE4 + i; - DIKState[ActiveDIKState][event.data1] = (event.type == EV_KeyDown); - D_PostEvent (&event); - } - } - } - return TRUE; - - case WM_MOUSEWHEEL: - if ((MakeMouseEvents || NativeMouse) && mousemode == win32) - { - WheelMove += (short) HIWORD(wParam); - WheelMoved (); - } - break; - case WM_DISPLAYCHANGE: if (SpawnEAXWindow) { @@ -1307,18 +1258,6 @@ static HRESULT InitJoystick () return S_OK; } -static void DI_Acquire (LPDIRECTINPUTDEVICE8 mouse) -{ - if (mouse) mouse->Acquire (); - SetCursorState (NativeMouse); -} - -static void DI_Unacquire (LPDIRECTINPUTDEVICE8 mouse) -{ - if (mouse) mouse->Unacquire (); - SetCursorState (TRUE); -} - /****** Stuff from Andy Bay's mymouse.c ******/ /**************************************************************************** @@ -1329,116 +1268,9 @@ static void DI_Unacquire (LPDIRECTINPUTDEVICE8 mouse) * ****************************************************************************/ -// [RH] Obtain the mouse using standard Win32 calls. Should always work. -static void I_GetWin32Mouse () -{ - mousemode = win32; - - if (g_pMouse) - { - DI_Unacquire (g_pMouse); - g_pMouse->Release (); - g_pMouse = NULL; - } - GrabMouse_Win32 (); -} // [RH] Used to obtain DirectInput access to the mouse. // (Preferred for Win95, but buggy under NT 4.) -static BOOL I_GetDIMouse () -{ - HRESULT hr; - DIPROPDWORD dipdw = - { - { - sizeof(DIPROPDWORD), // diph.dwSize - sizeof(DIPROPHEADER), // diph.dwHeaderSize - 0, // diph.dwObj - DIPH_DEVICE, // diph.dwHow - }, - DINPUT_BUFFERSIZE, // dwData - }; - - if (mousemode == dinput) - return FALSE; - - mousemode = win32; // Assume failure - UngrabMouse_Win32 (); - - if (in_mouse == 1 || (in_mouse == 0 && OSPlatform == os_WinNT4)) - return FALSE; - - // Obtain an interface to the system mouse device. - if (g_pdi3) - { - hr = g_pdi3->CreateDevice (GUID_SysMouse, (LPDIRECTINPUTDEVICE*)&g_pMouse, NULL); - } - else - { - hr = g_pdi->CreateDevice (GUID_SysMouse, &g_pMouse, NULL); - } - - if (FAILED(hr)) - return FALSE; - - DIDEVCAPS_DX3 mouseCaps = { sizeof(mouseCaps), }; - hr = g_pMouse->GetCapabilities ((DIDEVCAPS *)&mouseCaps); - - // Set the data format to "mouse format". - if (SUCCEEDED(hr)) - { - // Select the data format with enough buttons for this mouse - if (mouseCaps.dwButtons <= 4) - { - hr = g_pMouse->SetDataFormat (&c_dfDIMouse); - } - else - { - hr = g_pMouse->SetDataFormat (&c_dfDIMouse2); - } - } - else - { - // Assume the mouse has no more than 4 buttons if we can't check it - hr = g_pMouse->SetDataFormat (&c_dfDIMouse); - } - - if (FAILED(hr)) - { - g_pMouse->Release (); - g_pMouse = NULL; - return FALSE; - } - - // Set the cooperative level. - hr = g_pMouse->SetCooperativeLevel ((HWND)Window, - DISCL_EXCLUSIVE | DISCL_FOREGROUND); - - if (FAILED(hr)) - { - g_pMouse->Release (); - g_pMouse = NULL; - return FALSE; - } - - - // Set the buffer size to DINPUT_BUFFERSIZE elements. - // The buffer size is a DWORD property associated with the device. - hr = g_pMouse->SetProperty (DIPROP_BUFFERSIZE, &dipdw.diph); - - if (FAILED(hr)) - { - Printf ("Could not set mouse buffer size"); - g_pMouse->Release (); - g_pMouse = NULL; - return FALSE; - } - - DI_Acquire (g_pMouse); - - mousemode = dinput; - return TRUE; -} bool I_InitInput (void *hwnd) { @@ -1447,8 +1279,7 @@ bool I_InitInput (void *hwnd) Printf ("I_InitInput\n"); atterm (I_ShutdownInput); - NativeMouse = -1; - GetCursorPos (&UngrabbedPointerPos); + NativeMouse = true; noidle = !!Args->CheckParm ("-noidle"); g_pdi = NULL; @@ -1526,11 +1357,9 @@ void I_ShutdownInput () g_pKey->Release (); g_pKey = NULL; } - if (g_pMouse) + if (Mouse != NULL) { - DI_Unacquire (g_pMouse); - g_pMouse->Release (); - g_pMouse = NULL; + delete Mouse; } if (g_pJoy) { @@ -1538,7 +1367,6 @@ void I_ShutdownInput () g_pJoy->Release (); g_pJoy = NULL; } - UngrabMouse_Win32 (); if (g_pdi) { g_pdi->Release (); @@ -1548,11 +1376,11 @@ void I_ShutdownInput () { g_pdi3->Release (); g_pdi3 = NULL; - if (DInputDLL != NULL) - { - FreeLibrary (DInputDLL); - DInputDLL = NULL; - } + } + if (DInputDLL != NULL) + { + FreeLibrary (DInputDLL); + DInputDLL = NULL; } } @@ -1600,255 +1428,16 @@ static void SetSoundPaused (int state) static LONG PrevX, PrevY; -static void I_StartupMouse () -{ - int new_mousemode; - - if (in_mouse == 1 || (in_mouse == 0 && OSPlatform == os_WinNT4)) - new_mousemode = win32; - else - new_mousemode = dinput; - - if (new_mousemode != mousemode) - { - if (new_mousemode == win32 || !I_GetDIMouse()) - I_GetWin32Mouse (); - NativeMouse = false; - } - HaveFocus = GetFocus() == Window; -} - -static void CenterMouse_Win32 (LONG curx, LONG cury) -{ - RECT rect; - - GetWindowRect (Window, &rect); - - const LONG centx = (rect.left + rect.right) >> 1; - const LONG 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); - } -} - -static void SetCursorState (int visible) +static void SetCursorState(bool visible) { HCURSOR usingCursor = visible ? TheArrowCursor : TheInvisibleCursor; - SetClassLongPtr (Window, GCLP_HCURSOR, (LONG_PTR)usingCursor); + SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)usingCursor); if (HaveFocus) { - SetCursor (usingCursor); + SetCursor(usingCursor); } } -static void GrabMouse_Win32 () -{ - RECT rect; - - 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_Win32 (-1, -1); - MakeMouseEvents = true; -} - -static void UngrabMouse_Win32 () -{ - ClipCursor (NULL); - SetCursorState (TRUE); - MakeMouseEvents = false; -} - -static void WheelMoved () -{ - event_t event; - int dir; - - memset (&event, 0, sizeof(event)); - if (GUICapture) - { - event.type = EV_GUI_Event; - if (WheelMove < 0) - { - dir = WHEEL_DELTA; - event.subtype = EV_GUI_WheelDown; - } - else - { - dir = -WHEEL_DELTA; - event.subtype = EV_GUI_WheelUp; - } - event.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 (&event); - WheelMove += dir; - } - } - else - { - if (WheelMove < 0) - { - dir = WHEEL_DELTA; - event.data1 = KEY_MWHEELDOWN; - } - else - { - dir = -WHEEL_DELTA; - event.data1 = KEY_MWHEELUP; - } - while (abs (WheelMove) >= WHEEL_DELTA) - { - event.type = EV_KeyDown; - D_PostEvent (&event); - event.type = EV_KeyUp; - D_PostEvent (&event); - WheelMove += dir; - } - } -} - -static void PostMouseMove (int x, int y) -{ - static int lastx = 0, lasty = 0; - 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); - } -} - -static void MouseRead_Win32 () -{ - POINT pt; - int x, y; - - if (KeysReadCount == 0) - { - UngrabMouse_Win32(); - } - else if (KeysReadCount == 1 && HaveFocus && !MakeMouseEvents) - { - GrabMouse_Win32(); - } - if (!HaveFocus || !MakeMouseEvents || !GetCursorPos (&pt)) - { - return; - } - - x = pt.x - PrevX; - y = PrevY - pt.y; - - if (!m_noprescale) - { - x *= 3; - y *= 2; - } - - if (x | y) - { - CenterMouse_Win32 (pt.x, pt.y); - } - - PostMouseMove (x, y); -} - -static void MouseRead_DI () -{ - DIDEVICEOBJECTDATA od; - DWORD dwElements; - HRESULT hr; - int count = 0; - int dx, dy; - event_t event; - - dx = 0; - dy = 0; - - if (!HaveFocus || NativeMouse || !g_pMouse) - return; - - memset (&event, 0, sizeof(event)); - for (;;) - { - dwElements = 1; - hr = g_pMouse->GetDeviceData ( - g_pdi3 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA), - &od, &dwElements, 0); - if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) - { - DI_Acquire (g_pMouse); - hr = g_pMouse->GetDeviceData ( - g_pdi3 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA), - (LPDIDEVICEOBJECTDATA)&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) - { - WheelMove += od.dwData; - WheelMoved (); - } - else if (od.dwOfs >= (DWORD)DIMOFS_BUTTON0 && od.dwOfs <= (DWORD)DIMOFS_BUTTON7) - { - /* [RH] Mouse button events mimic keydown/up events */ - if (!GUICapture) - { - event.type = (od.dwData & 0x80) ? EV_KeyDown : EV_KeyUp; - event.data1 = KEY_MOUSE1 + (od.dwOfs - DIMOFS_BUTTON0); - DIKState[ActiveDIKState][event.data1] = (event.type == EV_KeyDown); - D_PostEvent (&event); - } - } - } - - PostMouseMove (m_noprescale ? dx : dx<<2, -dy); -} - // Initialize the keyboard static BOOL I_StartupKeyboard (void) { @@ -1984,12 +1573,9 @@ void I_GetEvent () KeyRead (); - if (use_mouse) + if (Mouse != NULL) { - if (mousemode == dinput) - MouseRead_DI (); - else - MouseRead_Win32 (); + Mouse->ProcessInput(); } } @@ -2077,3 +1663,693 @@ CCMD (playmovie) } I_PlayMovie (argv[1]); } + +//========================================================================== +// +// FInputDevice :: WndProcHook +// +// Gives subclasses a chance to intercept window messages. +// +//========================================================================== + +bool FInputDevice::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) +{ + return false; +} + +//========================================================================== +// +// 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; + } + 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; + } +} + +class FDInputMouse : public FMouse +{ +public: + FDInputMouse(); + ~FDInputMouse(); + + bool GetDevice(); + void ProcessInput(); + void Grab(); + void Ungrab(); + +protected: + LPDIRECTINPUTDEVICE8 Device; +}; + +//========================================================================== +// +// FDInputMouse - Constructor +// +//========================================================================== + +FDInputMouse::FDInputMouse() +{ + Device = NULL; +} + +//========================================================================== +// +// 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) + { // DirectInput3 interface + hr = g_pdi3->CreateDevice(GUID_SysMouse, (LPDIRECTINPUTDEVICE*)&Device, NULL); + } + else + { // DirectInput8 interface + hr = g_pdi->CreateDevice(GUID_SysMouse, &Device, NULL); + } + 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 :: 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 (!HaveFocus || NativeMouse) + 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() +{ + Device->Acquire(); + SetCursorState(NativeMouse); +} + +//========================================================================== +// +// FDInputMouse :: Ungrab +// +//========================================================================== + +void FDInputMouse::Ungrab() +{ + Device->Unacquire(); + SetCursorState(true); + ClearButtonState(); +} + +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; + bool Grabbed; +}; + +//========================================================================== +// +// 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 (!HaveFocus || !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); + *result = 0; + return true; + } + } + else if (message == WM_MOVE) + { + CenterMouse(-1, -1); + *result = 0; + 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) + { + *result = 0; + 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); + } +} + +static void I_StartupMouse () +{ + mousemode_t new_mousemode; + + if (in_mouse == 1 || (in_mouse == 0 && OSPlatform == os_WinNT4)) + new_mousemode = win32; + else + new_mousemode = dinput; + + if (new_mousemode != mousemode) + { + if (new_mousemode == dinput) + { + Mouse = new FDInputMouse(); + } + else + { + Mouse = new FWin32Mouse(); + } + if (!Mouse->GetDevice()) + { + delete Mouse; + if (new_mousemode == dinput) + { + new_mousemode = win32; + Mouse = new FWin32Mouse(); + if (!Mouse->GetDevice()) + { + Mouse = NULL; + new_mousemode = none; + } + } + } + mousemode = new_mousemode; + } + HaveFocus = GetFocus() == Window; +}