/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IceTech Source Code 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 GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IceTech Source Code. If not, see . If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #ifndef DIRECTINPUT_VERSION #define DIRECTINPUT_VERSION 0x0800 #endif #include "DirectInputShim.h" #include #include #include #include #include #include #include #include #include #include #ifndef ICE_DI_DEFAULT_BUFFER_SIZE #define ICE_DI_DEFAULT_BUFFER_SIZE 256 #endif #ifndef ICE_DI_CENTER_MOUSE_WHEN_ACTIVE #define ICE_DI_CENTER_MOUSE_WHEN_ACTIVE 1 #endif #ifndef ICE_DI_CENTER_WARP_IGNORE_MS #define ICE_DI_CENTER_WARP_IGNORE_MS 120 #endif #ifndef ICE_DI_CENTER_PIXEL_TOLERANCE #define ICE_DI_CENTER_PIXEL_TOLERANCE 1 #endif // Forward declarations class IceDirectInputDevice8; class IceDirectInput8; bool IceDirectInputShim_RegisterRawInput(HWND hwnd); bool IceDirectInputShim_ForceMouseCenter(); void IceDirectInputShim_SetMouseTargetWindow(HWND hwnd); void IceDirectInputShim_SetMouseCenterLock(bool enabled); // Globals static HWND g_rawInputHwnd = NULL; static HWND g_keyboardTargetHwnd = NULL; static HWND g_mouseTargetHwnd = NULL; static IceDirectInputDevice8* g_keyboardDevice = NULL; static IceDirectInputDevice8* g_mouseDevice = NULL; static std::mutex g_deviceMutex; static std::mutex g_windowMutex; static std::atomic g_iceDI_GameMouseActive(true); static std::atomic g_iceDI_MouseAcquired(false); static std::atomic g_iceDI_CenterMouseWhenActive(ICE_DI_CENTER_MOUSE_WHEN_ACTIVE != 0); // The shim owns the clip only while the game mouse is active. static bool g_mouseClipOwned = false; static RECT g_lastClipRect = { 0, 0, 0, 0 }; // Used only to ignore absolute raw-mouse packets generated by our own recentering. static POINT g_lastCenterWarpPoint = { 0, 0 }; static DWORD g_lastCenterWarpTick = 0; // ------------------------------------------------------------ // Helpers // ------------------------------------------------------------ static DWORD IceDI_GetTimeStamp() { return GetTickCount(); } static LONG IceDI_AbsLong(LONG v) { return v < 0 ? -v : v; } static bool IceDI_IsDInputProp(REFGUID rguidProp, DWORD propId) { // DirectInput DIPROP_* values are not real GUID objects. // They are encoded as invalid pointer-sized constants: // #define MAKEDIPROP(prop) (*(const GUID *)(prop)) // // So do NOT compare DIPROP_* with operator== / IsEqualGUID / memcmp. return (DWORD)(uintptr_t)&rguidProp == propId; } static bool IceDI_IsPressedRawKeyboard(const RAWKEYBOARD& kb) { return (kb.Flags & RI_KEY_BREAK) == 0; } static bool IceDI_IsUsableWindow(HWND hwnd) { return hwnd != NULL && ::IsWindow(hwnd) != FALSE; } static bool IceDI_IsVisibleUsableWindow(HWND hwnd) { return IceDI_IsUsableWindow(hwnd) && ::IsWindowVisible(hwnd) != FALSE; } static bool IceDI_ForegroundOwnsWindow(HWND hwnd) { if (!IceDI_IsUsableWindow(hwnd)) { return false; } HWND foreground = ::GetForegroundWindow(); if (!IceDI_IsUsableWindow(foreground)) { return false; } if (foreground == hwnd) { return true; } // Embedded case: the Doom/game HWND can be a child of the editor's // foreground top-level HWND. This is the case that needs live client-to- // screen conversion instead of stale cached x/y/resolution. if (::IsChild(foreground, hwnd)) { return true; } HWND root = ::GetAncestor(hwnd, GA_ROOT); if (root != NULL && root == foreground) { return true; } // Rare, but allow it when a child of our target became foreground. if (::IsChild(hwnd, foreground)) { return true; } return false; } static bool IceDI_GetClientScreenRect(HWND hwnd, RECT* outRect) { if (outRect == NULL || !IceDI_IsUsableWindow(hwnd)) { return false; } RECT client; if (!::GetClientRect(hwnd, &client)) { return false; } if ((client.right - client.left) <= 0 || (client.bottom - client.top) <= 0) { return false; } POINT pts[2]; pts[0].x = client.left; pts[0].y = client.top; pts[1].x = client.right; pts[1].y = client.bottom; if (!::ClientToScreen(hwnd, &pts[0])) { return false; } if (!::ClientToScreen(hwnd, &pts[1])) { return false; } outRect->left = std::min(pts[0].x, pts[1].x); outRect->top = std::min(pts[0].y, pts[1].y); outRect->right = std::max(pts[0].x, pts[1].x); outRect->bottom = std::max(pts[0].y, pts[1].y); return (outRect->right > outRect->left) && (outRect->bottom > outRect->top); } static bool IceDI_RectEquals(const RECT& a, const RECT& b) { return a.left == b.left && a.top == b.top && a.right == b.right && a.bottom == b.bottom; } static HWND IceDI_GetFallbackRawInputWindow_NoLock() { if (IceDI_IsUsableWindow(g_rawInputHwnd)) { return g_rawInputHwnd; } if (IceDI_IsUsableWindow(g_mouseTargetHwnd)) { return g_mouseTargetHwnd; } if (IceDI_IsUsableWindow(g_keyboardTargetHwnd)) { return g_keyboardTargetHwnd; } return NULL; } static HWND IceDI_GetMouseTargetWindow() { HWND target = NULL; HWND fallback = NULL; { std::lock_guard lock(g_windowMutex); target = g_mouseTargetHwnd; fallback = IceDI_GetFallbackRawInputWindow_NoLock(); } if (IceDI_IsUsableWindow(target)) { return target; } if (IceDI_IsUsableWindow(fallback)) { return fallback; } // Last-ditch fallback for code paths that acquire before setting // cooperative level. GetFocus only works reliably inside this thread, // but it is better than retaining a stale hwnd. HWND focus = ::GetFocus(); if (IceDI_IsUsableWindow(focus)) { return focus; } return NULL; } static bool IceDI_ShouldLockMouseToWindow(HWND hwnd) { if (!g_iceDI_CenterMouseWhenActive.load()) { return false; } if (!g_iceDI_GameMouseActive.load()) { return false; } if (!g_iceDI_MouseAcquired.load()) { return false; } if (!IceDI_IsVisibleUsableWindow(hwnd)) { return false; } if (!IceDI_ForegroundOwnsWindow(hwnd)) { return false; } return true; } static void IceDI_RememberCenterWarp(const POINT& center) { std::lock_guard lock(g_windowMutex); g_lastCenterWarpPoint = center; g_lastCenterWarpTick = IceDI_GetTimeStamp(); } static bool IceDI_IsRecentCenterWarpPoint(const POINT& pt) { POINT lastPoint; DWORD lastTick; { std::lock_guard lock(g_windowMutex); lastPoint = g_lastCenterWarpPoint; lastTick = g_lastCenterWarpTick; } if (lastTick == 0) { return false; } DWORD now = IceDI_GetTimeStamp(); if ((DWORD)(now - lastTick) > ICE_DI_CENTER_WARP_IGNORE_MS) { return false; } return IceDI_AbsLong(pt.x - lastPoint.x) <= 2 && IceDI_AbsLong(pt.y - lastPoint.y) <= 2; } static void IceDI_ClearMouseClipIfOwned() { bool shouldClear = false; { std::lock_guard lock(g_windowMutex); shouldClear = g_mouseClipOwned; g_mouseClipOwned = false; g_lastClipRect.left = 0; g_lastClipRect.top = 0; g_lastClipRect.right = 0; g_lastClipRect.bottom = 0; } if (shouldClear) { ::ClipCursor(NULL); } } static bool IceDI_ApplyMouseClip(const RECT& rect) { bool needsClip = true; { std::lock_guard lock(g_windowMutex); needsClip = !g_mouseClipOwned || !IceDI_RectEquals(g_lastClipRect, rect); g_mouseClipOwned = true; g_lastClipRect = rect; } // Calling ClipCursor repeatedly is okay, but avoid doing it every poll when // the rect has not changed. If the editor moves/reparents/resizes the child // window, the live rect changes and this is applied immediately. if (needsClip) { if (!::ClipCursor(&rect)) { return false; } } return true; } static bool IceDI_SkipCenter = false; void IceDI_ForceOverrideSkipCenter(bool shouldSkip) { IceDI_SkipCenter = shouldSkip; } static bool IceDI_ForceMouseCenterInternal(bool forceWarp) { HWND hwnd = IceDI_GetMouseTargetWindow(); if (!IceDI_ShouldLockMouseToWindow(hwnd) || IceDI_SkipCenter) { IceDI_ClearMouseClipIfOwned(); return false; } RECT clientScreenRect; if (!IceDI_GetClientScreenRect(hwnd, &clientScreenRect)) { IceDI_ClearMouseClipIfOwned(); return false; } POINT center; center.x = clientScreenRect.left + ((clientScreenRect.right - clientScreenRect.left) / 2); center.y = clientScreenRect.top + ((clientScreenRect.bottom - clientScreenRect.top) / 2); IceDI_ApplyMouseClip(clientScreenRect); POINT cursor; bool haveCursor = (::GetCursorPos(&cursor) != FALSE); bool outside = !haveCursor || !::PtInRect(&clientScreenRect, cursor); bool offCenter = !haveCursor || IceDI_AbsLong(cursor.x - center.x) > ICE_DI_CENTER_PIXEL_TOLERANCE || IceDI_AbsLong(cursor.y - center.y) > ICE_DI_CENTER_PIXEL_TOLERANCE; if (forceWarp || outside || offCenter) { IceDI_RememberCenterWarp(center); ::SetCursorPos(center.x, center.y); } return true; } static bool IceDI_RawAbsoluteMouseToScreen(const RAWMOUSE& rm, POINT* outPoint) { if (outPoint == NULL) { return false; } int left = 0; int top = 0; int width = ::GetSystemMetrics(SM_CXSCREEN); int height = ::GetSystemMetrics(SM_CYSCREEN); if (rm.usFlags & MOUSE_VIRTUAL_DESKTOP) { left = ::GetSystemMetrics(SM_XVIRTUALSCREEN); top = ::GetSystemMetrics(SM_YVIRTUALSCREEN); width = ::GetSystemMetrics(SM_CXVIRTUALSCREEN); height = ::GetSystemMetrics(SM_CYVIRTUALSCREEN); } if (width <= 0 || height <= 0) { return false; } // RAWMOUSE absolute packets are normalized to 0..65535 over the selected // desktop rectangle. Treating these values as relative deltas is a classic // cause of huge mouse jumps/skips. outPoint->x = left + ::MulDiv(rm.lLastX, width - 1, 65535); outPoint->y = top + ::MulDiv(rm.lLastY, height - 1, 65535); return true; } static DWORD IceDI_RawKeyboardToDIK(const RAWKEYBOARD& kb) { USHORT makeCode = kb.MakeCode; USHORT flags = kb.Flags; USHORT vkey = kb.VKey; if (makeCode == 0) { if (vkey == VK_SNAPSHOT) { return DIK_SYSRQ; } if (vkey == VK_PAUSE) { return DIK_PAUSE; } return 0; } if (vkey == VK_PAUSE) { return DIK_PAUSE; } if (vkey == VK_SNAPSHOT) { return DIK_SYSRQ; } DWORD dik = makeCode & 0x7F; if (flags & RI_KEY_E0) { dik |= 0x80; } switch (vkey) { case VK_RCONTROL: return DIK_RCONTROL; case VK_RMENU: return DIK_RMENU; case VK_LWIN: return DIK_LWIN; case VK_RWIN: return DIK_RWIN; case VK_APPS: return DIK_APPS; default: break; } return dik; } enum iceDIShimDeviceType { ICE_DI_DEVICE_KEYBOARD, ICE_DI_DEVICE_MOUSE }; static bool IceDI_RegisterRawInputForCurrentTargets(HWND fallbackHwnd) { HWND keyboardTarget = NULL; HWND mouseTarget = NULL; { std::lock_guard lock(g_windowMutex); if (IceDI_IsUsableWindow(g_keyboardTargetHwnd)) { keyboardTarget = g_keyboardTargetHwnd; } if (IceDI_IsUsableWindow(g_mouseTargetHwnd)) { mouseTarget = g_mouseTargetHwnd; } if (!IceDI_IsUsableWindow(keyboardTarget) && IceDI_IsUsableWindow(fallbackHwnd)) { keyboardTarget = fallbackHwnd; g_keyboardTargetHwnd = fallbackHwnd; } if (!IceDI_IsUsableWindow(mouseTarget) && IceDI_IsUsableWindow(fallbackHwnd)) { mouseTarget = fallbackHwnd; g_mouseTargetHwnd = fallbackHwnd; } if (IceDI_IsUsableWindow(fallbackHwnd)) { g_rawInputHwnd = fallbackHwnd; } else { g_rawInputHwnd = IceDI_GetFallbackRawInputWindow_NoLock(); } } if (!IceDI_IsUsableWindow(keyboardTarget) && !IceDI_IsUsableWindow(mouseTarget)) { return false; } RAWINPUTDEVICE rid[2]; UINT count = 0; memset(rid, 0, sizeof(rid)); if (IceDI_IsUsableWindow(keyboardTarget)) { rid[count].usUsagePage = 0x01; rid[count].usUsage = 0x06; rid[count].dwFlags = 0; rid[count].hwndTarget = keyboardTarget; count++; } if (IceDI_IsUsableWindow(mouseTarget)) { rid[count].usUsagePage = 0x01; rid[count].usUsage = 0x02; rid[count].dwFlags = 0; rid[count].hwndTarget = mouseTarget; count++; } if (count == 0) { return false; } if (!::RegisterRawInputDevices(rid, count, sizeof(RAWINPUTDEVICE))) { OutputDebugStringA("IceDirectInputShim: RegisterRawInputDevices failed\n"); return false; } return true; } static void IceDI_SetDeviceTargetWindow(iceDIShimDeviceType type, HWND hwnd) { if (!IceDI_IsUsableWindow(hwnd)) { return; } { std::lock_guard lock(g_windowMutex); if (type == ICE_DI_DEVICE_KEYBOARD) { g_keyboardTargetHwnd = hwnd; } else { g_mouseTargetHwnd = hwnd; } g_rawInputHwnd = hwnd; } IceDI_RegisterRawInputForCurrentTargets(hwnd); } // ------------------------------------------------------------ // IceDirectInputDevice8 // ------------------------------------------------------------ class IceDirectInputDevice8 : public IDirectInputDevice8A { public: IceDirectInputDevice8(iceDIShimDeviceType type) : refCount(1), deviceType(type), acquired(false), bufferSize(ICE_DI_DEFAULT_BUFFER_SIZE), coopFlags(0), hwnd(NULL), haveLastAbsoluteMouse(false) { memset(keyboardState, 0, sizeof(keyboardState)); memset(&mouseState, 0, sizeof(mouseState)); lastAbsoluteMouse.x = 0; lastAbsoluteMouse.y = 0; } virtual ~IceDirectInputDevice8() { std::lock_guard lock(g_deviceMutex); if (g_keyboardDevice == this) { g_keyboardDevice = NULL; } if (g_mouseDevice == this) { g_mouseDevice = NULL; g_iceDI_MouseAcquired.store(false); } } // -------------------------------------------------------- // IUnknown // -------------------------------------------------------- STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) override { if (ppvObj == NULL) { return E_POINTER; } *ppvObj = NULL; if (riid == IID_IUnknown || riid == IID_IDirectInputDevice8A) { *ppvObj = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHOD_(ULONG, AddRef)() override { return ++refCount; } STDMETHOD_(ULONG, Release)() override { ULONG r = --refCount; if (r == 0) { delete this; } return r; } // -------------------------------------------------------- // IDirectInputDevice8A // -------------------------------------------------------- STDMETHOD(GetCapabilities)(LPDIDEVCAPS lpDIDevCaps) override { if (lpDIDevCaps == NULL) { return E_POINTER; } if (lpDIDevCaps->dwSize < sizeof(DIDEVCAPS)) { return DIERR_INVALIDPARAM; } DWORD oldSize = lpDIDevCaps->dwSize; memset(lpDIDevCaps, 0, oldSize); lpDIDevCaps->dwSize = oldSize; if (deviceType == ICE_DI_DEVICE_KEYBOARD) { lpDIDevCaps->dwDevType = DI8DEVTYPE_KEYBOARD; lpDIDevCaps->dwButtons = 256; } else { lpDIDevCaps->dwDevType = DI8DEVTYPE_MOUSE; lpDIDevCaps->dwAxes = 3; lpDIDevCaps->dwButtons = 8; } return DI_OK; } STDMETHOD(EnumObjects)(LPDIENUMDEVICEOBJECTSCALLBACKA lpCallback, LPVOID pvRef, DWORD dwFlags) override { if (lpCallback == NULL) { return DIERR_INVALIDPARAM; } if (deviceType == ICE_DI_DEVICE_KEYBOARD) { for (DWORD i = 0; i < 256; i++) { DIDEVICEOBJECTINSTANCEA inst; memset(&inst, 0, sizeof(inst)); inst.dwSize = sizeof(inst); inst.dwType = DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i); inst.dwOfs = i; sprintf_s(inst.tszName, "Key %u", i); if (lpCallback(&inst, pvRef) == DIENUM_STOP) { break; } } } else { static const DWORD mouseObjects[] = { DIMOFS_X, DIMOFS_Y, DIMOFS_Z, DIMOFS_BUTTON0, DIMOFS_BUTTON1, DIMOFS_BUTTON2, DIMOFS_BUTTON3, DIMOFS_BUTTON4, DIMOFS_BUTTON5, DIMOFS_BUTTON6, DIMOFS_BUTTON7 }; for (int i = 0; i < (int)(sizeof(mouseObjects) / sizeof(mouseObjects[0])); i++) { DIDEVICEOBJECTINSTANCEA inst; memset(&inst, 0, sizeof(inst)); inst.dwSize = sizeof(inst); inst.dwOfs = mouseObjects[i]; if (mouseObjects[i] == DIMOFS_X || mouseObjects[i] == DIMOFS_Y || mouseObjects[i] == DIMOFS_Z) { inst.dwType = DIDFT_AXIS | DIDFT_MAKEINSTANCE(i); } else { inst.dwType = DIDFT_BUTTON | DIDFT_MAKEINSTANCE(i - 3); } sprintf_s(inst.tszName, "Mouse Object %d", i); if (lpCallback(&inst, pvRef) == DIENUM_STOP) { break; } } } return DI_OK; } STDMETHOD(GetProperty)(REFGUID rguidProp, LPDIPROPHEADER pdiph) override { if (pdiph == NULL) { return E_POINTER; } if (IceDI_IsDInputProp(rguidProp, 1)) { // DIPROP_BUFFERSIZE if (pdiph->dwSize < sizeof(DIPROPDWORD)) { return DIERR_INVALIDPARAM; } DIPROPDWORD* prop = reinterpret_cast(pdiph); prop->dwData = bufferSize; return DI_OK; } return DIERR_UNSUPPORTED; } STDMETHOD(SetProperty)(REFGUID rguidProp, LPCDIPROPHEADER pdiph) override { if (pdiph == NULL) { return E_POINTER; } if (IceDI_IsDInputProp(rguidProp, 1)) { // DIPROP_BUFFERSIZE if (pdiph->dwSize < sizeof(DIPROPDWORD)) { return DIERR_INVALIDPARAM; } const DIPROPDWORD* prop = reinterpret_cast(pdiph); std::lock_guard lock(mutex); bufferSize = prop->dwData > 0 ? prop->dwData : ICE_DI_DEFAULT_BUFFER_SIZE; TrimQueue_NoLock(); return DI_OK; } // Ignore unsupported DirectInput properties instead of failing old code. return DI_OK; } STDMETHOD(Acquire)() override { acquired = true; if (hwnd != NULL && ::IsWindow(hwnd)) { IceDI_SetDeviceTargetWindow(deviceType, hwnd); } else { HWND fallback = NULL; { std::lock_guard lock(g_windowMutex); fallback = IceDI_GetFallbackRawInputWindow_NoLock(); } if (fallback != NULL && ::IsWindow(fallback)) { IceDI_RegisterRawInputForCurrentTargets(fallback); } } if (deviceType == ICE_DI_DEVICE_MOUSE) { g_iceDI_MouseAcquired.store(true); ResetMouseMotion(); IceDI_ForceMouseCenterInternal(true); } return DI_OK; } STDMETHOD(Unacquire)() override { acquired = false; if (deviceType == ICE_DI_DEVICE_MOUSE) { g_iceDI_MouseAcquired.store(false); ResetMouseMotion(); IceDI_ClearMouseClipIfOwned(); } return DI_OK; } STDMETHOD(GetDeviceState)(DWORD cbData, LPVOID lpvData) override { if (lpvData == NULL) { return E_POINTER; } if (!acquired) { return DIERR_NOTACQUIRED; } if (deviceType == ICE_DI_DEVICE_MOUSE) { IceDI_ForceMouseCenterInternal(false); } std::lock_guard lock(mutex); if (deviceType == ICE_DI_DEVICE_KEYBOARD) { if (cbData < 256) { return DIERR_INVALIDPARAM; } memcpy(lpvData, keyboardState, 256); return DI_OK; } if (cbData >= sizeof(DIMOUSESTATE2)) { memcpy(lpvData, &mouseState, sizeof(DIMOUSESTATE2)); // Relative mouse state is consumed per poll. mouseState.lX = 0; mouseState.lY = 0; mouseState.lZ = 0; return DI_OK; } if (cbData >= sizeof(DIMOUSESTATE)) { DIMOUSESTATE out; memset(&out, 0, sizeof(out)); out.lX = mouseState.lX; out.lY = mouseState.lY; out.lZ = mouseState.lZ; memcpy(out.rgbButtons, mouseState.rgbButtons, 4); memcpy(lpvData, &out, sizeof(out)); mouseState.lX = 0; mouseState.lY = 0; mouseState.lZ = 0; return DI_OK; } return DIERR_INVALIDPARAM; } STDMETHOD(GetDeviceData)( DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags ) override { if (pdwInOut == NULL) { return E_POINTER; } if (cbObjectData < sizeof(DIDEVICEOBJECTDATA)) { return DIERR_INVALIDPARAM; } if (!acquired) { return DIERR_NOTACQUIRED; } if (deviceType == ICE_DI_DEVICE_MOUSE) { IceDI_ForceMouseCenterInternal(false); } std::lock_guard lock(mutex); DWORD requested = *pdwInOut; // Doom/idTech uses rgdod == NULL to clear buffered garbage. if (rgdod == NULL) { DWORD count = 0; if (requested == 0) { count = (DWORD)queue.size(); queue.clear(); } else { count = std::min(requested, (DWORD)queue.size()); for (DWORD i = 0; i < count; i++) { queue.pop_front(); } } *pdwInOut = count; return DI_OK; } DWORD count = std::min(requested, (DWORD)queue.size()); for (DWORD i = 0; i < count; i++) { rgdod[i] = queue.front(); queue.pop_front(); } *pdwInOut = count; return DI_OK; } STDMETHOD(SetDataFormat)(LPCDIDATAFORMAT lpdf) override { if (lpdf == NULL) { return DIERR_INVALIDPARAM; } // Existing Doom path passes c_dfDIKeyboard / c_dfDIMouse2. // The shim always emits standard DirectInput offsets. return DI_OK; } STDMETHOD(SetEventNotification)(HANDLE hEvent) override { return DI_OK; } STDMETHOD(SetCooperativeLevel)(HWND hwndOwner, DWORD dwFlags) override { hwnd = hwndOwner; coopFlags = dwFlags; if (hwndOwner != NULL && ::IsWindow(hwndOwner)) { IceDI_SetDeviceTargetWindow(deviceType, hwndOwner); if (deviceType == ICE_DI_DEVICE_MOUSE) { IceDI_ForceMouseCenterInternal(true); } } return DI_OK; } STDMETHOD(GetObjectInfo)(LPDIDEVICEOBJECTINSTANCEA pdidoi, DWORD dwObj, DWORD dwHow) override { if (pdidoi == NULL) { return E_POINTER; } DWORD oldSize = pdidoi->dwSize; memset(pdidoi, 0, oldSize); pdidoi->dwSize = oldSize; pdidoi->dwOfs = dwObj; sprintf_s(pdidoi->tszName, "Ice RawInput Object %u", dwObj); return DI_OK; } STDMETHOD(GetDeviceInfo)(LPDIDEVICEINSTANCEA pdidi) override { if (pdidi == NULL) { return E_POINTER; } DWORD oldSize = pdidi->dwSize; memset(pdidi, 0, oldSize); pdidi->dwSize = oldSize; if (deviceType == ICE_DI_DEVICE_KEYBOARD) { pdidi->guidInstance = GUID_SysKeyboard; pdidi->guidProduct = GUID_SysKeyboard; pdidi->dwDevType = DI8DEVTYPE_KEYBOARD; strcpy_s(pdidi->tszInstanceName, "Ice RawInput Keyboard"); strcpy_s(pdidi->tszProductName, "Ice RawInput Keyboard"); } else { pdidi->guidInstance = GUID_SysMouse; pdidi->guidProduct = GUID_SysMouse; pdidi->dwDevType = DI8DEVTYPE_MOUSE; strcpy_s(pdidi->tszInstanceName, "Ice RawInput Mouse"); strcpy_s(pdidi->tszProductName, "Ice RawInput Mouse"); } return DI_OK; } STDMETHOD(RunControlPanel)(HWND hwndOwner, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(Initialize)(HINSTANCE hinst, DWORD dwVersion, REFGUID rguid) override { return DI_OK; } STDMETHOD(CreateEffect)(REFGUID rguid, LPCDIEFFECT lpeff, LPDIRECTINPUTEFFECT* ppdeff, LPUNKNOWN punkOuter) override { if (ppdeff != NULL) { *ppdeff = NULL; } return DIERR_UNSUPPORTED; } STDMETHOD(EnumEffects)(LPDIENUMEFFECTSCALLBACKA lpCallback, LPVOID pvRef, DWORD dwEffType) override { return DIERR_UNSUPPORTED; } STDMETHOD(GetEffectInfo)(LPDIEFFECTINFOA pdei, REFGUID rguid) override { return DIERR_UNSUPPORTED; } STDMETHOD(GetForceFeedbackState)(LPDWORD pdwOut) override { if (pdwOut != NULL) { *pdwOut = 0; } return DIERR_UNSUPPORTED; } STDMETHOD(SendForceFeedbackCommand)(DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(EnumCreatedEffectObjects)(LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback, LPVOID pvRef, DWORD fl) override { return DIERR_UNSUPPORTED; } STDMETHOD(Escape)(LPDIEFFESCAPE pesc) override { return DIERR_UNSUPPORTED; } STDMETHOD(Poll)() override { if (deviceType == ICE_DI_DEVICE_MOUSE && acquired) { IceDI_ForceMouseCenterInternal(false); } return DI_OK; } STDMETHOD(SendDeviceData)(DWORD cbObjectData, LPCDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD fl) override { return DIERR_UNSUPPORTED; } STDMETHOD(EnumEffectsInFile)(LPCSTR lpszFileName, LPDIENUMEFFECTSINFILECALLBACK pec, LPVOID pvRef, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(WriteEffectToFile)(LPCSTR lpszFileName, DWORD dwEntries, LPDIFILEEFFECT rgDiFileEft, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(BuildActionMap)(LPDIACTIONFORMATA lpdiaf, LPCSTR lpszUserName, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(SetActionMap)(LPDIACTIONFORMATA lpdiActionFormat, LPCSTR lptszUserName, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(GetImageInfo)(LPDIDEVICEIMAGEINFOHEADERA lpdiDevImageInfoHeader) override { return DIERR_UNSUPPORTED; } // -------------------------------------------------------- // Raw Input entry points // -------------------------------------------------------- void PushKeyboardRaw(const RAWKEYBOARD& kb) { DWORD dik = IceDI_RawKeyboardToDIK(kb); if (dik == 0 || dik >= 256) { return; } const bool down = IceDI_IsPressedRawKeyboard(kb); const BYTE newState = down ? 0x80 : 0x00; std::lock_guard lock(mutex); keyboardState[dik] = newState; DIDEVICEOBJECTDATA od; memset(&od, 0, sizeof(od)); od.dwOfs = dik; od.dwData = newState; od.dwTimeStamp = IceDI_GetTimeStamp(); od.dwSequence = 0; od.uAppData = 0; PushEvent_NoLock(od); } void PushMouseRaw(const RAWMOUSE& rm) { std::lock_guard lock(mutex); const DWORD time = IceDI_GetTimeStamp(); LONG dx = 0; LONG dy = 0; if (rm.usFlags & MOUSE_MOVE_ABSOLUTE) { POINT absolutePoint; if (IceDI_RawAbsoluteMouseToScreen(rm, &absolutePoint)) { // SetCursorPos can surface as an absolute packet on some mouse // stacks / remote-desktop / wrapped-window setups. Reset the // baseline instead of generating a giant negative/positive delta. if (IceDI_IsRecentCenterWarpPoint(absolutePoint)) { lastAbsoluteMouse = absolutePoint; haveLastAbsoluteMouse = true; } else if (haveLastAbsoluteMouse) { dx = absolutePoint.x - lastAbsoluteMouse.x; dy = absolutePoint.y - lastAbsoluteMouse.y; lastAbsoluteMouse = absolutePoint; } else { lastAbsoluteMouse = absolutePoint; haveLastAbsoluteMouse = true; } } } else { haveLastAbsoluteMouse = false; dx = rm.lLastX; dy = rm.lLastY; } // Raw Input relative deltas only. The absolute packet conversion above // converts absolute devices into sane relative deltas; do not mix this // with GetCursorPos/WM_MOUSEMOVE deltas. if (dx != 0) { mouseState.lX += dx; PushMouseAxis_NoLock(DIMOFS_X, dx, time); } if (dy != 0) { mouseState.lY += dy; PushMouseAxis_NoLock(DIMOFS_Y, dy, time); } const USHORT flags = rm.usButtonFlags; if (flags & RI_MOUSE_WHEEL) { SHORT wheel = (SHORT)rm.usButtonData; mouseState.lZ += wheel; PushMouseAxis_NoLock(DIMOFS_Z, wheel, time); } if (flags & RI_MOUSE_BUTTON_1_DOWN) { PushMouseButton_NoLock(0, true, time); } if (flags & RI_MOUSE_BUTTON_1_UP) { PushMouseButton_NoLock(0, false, time); } if (flags & RI_MOUSE_BUTTON_2_DOWN) { PushMouseButton_NoLock(1, true, time); } if (flags & RI_MOUSE_BUTTON_2_UP) { PushMouseButton_NoLock(1, false, time); } if (flags & RI_MOUSE_BUTTON_3_DOWN) { PushMouseButton_NoLock(2, true, time); } if (flags & RI_MOUSE_BUTTON_3_UP) { PushMouseButton_NoLock(2, false, time); } if (flags & RI_MOUSE_BUTTON_4_DOWN) { PushMouseButton_NoLock(3, true, time); } if (flags & RI_MOUSE_BUTTON_4_UP) { PushMouseButton_NoLock(3, false, time); } if (flags & RI_MOUSE_BUTTON_5_DOWN) { PushMouseButton_NoLock(4, true, time); } if (flags & RI_MOUSE_BUTTON_5_UP) { PushMouseButton_NoLock(4, false, time); } } void ClearQueue() { std::lock_guard lock(mutex); queue.clear(); } void ResetMouseMotion() { if (deviceType != ICE_DI_DEVICE_MOUSE) { return; } std::lock_guard lock(mutex); queue.clear(); memset(&mouseState, 0, sizeof(mouseState)); haveLastAbsoluteMouse = false; lastAbsoluteMouse.x = 0; lastAbsoluteMouse.y = 0; } private: void PushMouseAxis_NoLock(DWORD ofs, LONG value, DWORD time) { DIDEVICEOBJECTDATA od; memset(&od, 0, sizeof(od)); od.dwOfs = ofs; od.dwData = (DWORD)value; od.dwTimeStamp = time; od.dwSequence = 0; od.uAppData = 0; PushEvent_NoLock(od); } void PushMouseButton_NoLock(int button, bool down, DWORD time) { if (button < 0 || button >= 8) { return; } mouseState.rgbButtons[button] = down ? 0x80 : 0x00; DIDEVICEOBJECTDATA od; memset(&od, 0, sizeof(od)); od.dwOfs = DIMOFS_BUTTON0 + button; od.dwData = down ? 0x80 : 0x00; od.dwTimeStamp = time; od.dwSequence = 0; od.uAppData = 0; PushEvent_NoLock(od); } void PushEvent_NoLock(const DIDEVICEOBJECTDATA& od) { queue.push_back(od); TrimQueue_NoLock(); } void TrimQueue_NoLock() { while (queue.size() > bufferSize) { queue.pop_front(); } } private: std::atomic refCount; iceDIShimDeviceType deviceType; bool acquired; DWORD bufferSize; DWORD coopFlags; HWND hwnd; std::mutex mutex; std::deque queue; BYTE keyboardState[256]; DIMOUSESTATE2 mouseState; bool haveLastAbsoluteMouse; POINT lastAbsoluteMouse; }; // ------------------------------------------------------------ // IceDirectInput8 // ------------------------------------------------------------ class IceDirectInput8 : public IDirectInput8A { public: IceDirectInput8() : refCount(1) { } virtual ~IceDirectInput8() { } STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) override { if (ppvObj == NULL) { return E_POINTER; } *ppvObj = NULL; if (riid == IID_IUnknown || riid == IID_IDirectInput8A) { *ppvObj = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHOD_(ULONG, AddRef)() override { return ++refCount; } STDMETHOD_(ULONG, Release)() override { ULONG r = --refCount; if (r == 0) { delete this; } return r; } STDMETHOD(CreateDevice)(REFGUID rguid, LPDIRECTINPUTDEVICE8A* lplpDirectInputDevice, LPUNKNOWN pUnkOuter) override { if (lplpDirectInputDevice == NULL) { return E_POINTER; } *lplpDirectInputDevice = NULL; if (pUnkOuter != NULL) { return CLASS_E_NOAGGREGATION; } IceDirectInputDevice8* dev = NULL; IceDirectInputDevice8* oldDev = NULL; if (rguid == GUID_SysKeyboard) { dev = new IceDirectInputDevice8(ICE_DI_DEVICE_KEYBOARD); { std::lock_guard lock(g_deviceMutex); oldDev = g_keyboardDevice; g_keyboardDevice = dev; g_keyboardDevice->AddRef(); } } else if (rguid == GUID_SysMouse) { dev = new IceDirectInputDevice8(ICE_DI_DEVICE_MOUSE); { std::lock_guard lock(g_deviceMutex); oldDev = g_mouseDevice; g_mouseDevice = dev; g_mouseDevice->AddRef(); } } else { return DIERR_DEVICENOTREG; } // Do not Release while holding g_deviceMutex. The device destructor also // takes g_deviceMutex to clear the globals. if (oldDev != NULL) { oldDev->Release(); } *lplpDirectInputDevice = dev; return DI_OK; } STDMETHOD(EnumDevices)(DWORD dwDevType, LPDIENUMDEVICESCALLBACKA lpCallback, LPVOID pvRef, DWORD dwFlags) override { if (lpCallback == NULL) { return DIERR_INVALIDPARAM; } if (dwDevType == 0 || dwDevType == DI8DEVCLASS_KEYBOARD || dwDevType == DI8DEVTYPE_KEYBOARD) { DIDEVICEINSTANCEA di; memset(&di, 0, sizeof(di)); di.dwSize = sizeof(di); di.guidInstance = GUID_SysKeyboard; di.guidProduct = GUID_SysKeyboard; di.dwDevType = DI8DEVTYPE_KEYBOARD; strcpy_s(di.tszInstanceName, "Ice RawInput Keyboard"); strcpy_s(di.tszProductName, "Ice RawInput Keyboard"); if (lpCallback(&di, pvRef) == DIENUM_STOP) { return DI_OK; } } if (dwDevType == 0 || dwDevType == DI8DEVCLASS_POINTER || dwDevType == DI8DEVTYPE_MOUSE) { DIDEVICEINSTANCEA di; memset(&di, 0, sizeof(di)); di.dwSize = sizeof(di); di.guidInstance = GUID_SysMouse; di.guidProduct = GUID_SysMouse; di.dwDevType = DI8DEVTYPE_MOUSE; strcpy_s(di.tszInstanceName, "Ice RawInput Mouse"); strcpy_s(di.tszProductName, "Ice RawInput Mouse"); lpCallback(&di, pvRef); } return DI_OK; } STDMETHOD(GetDeviceStatus)(REFGUID rguidInstance) override { if (rguidInstance == GUID_SysKeyboard || rguidInstance == GUID_SysMouse) { return DI_OK; } return DIERR_NOTFOUND; } STDMETHOD(RunControlPanel)(HWND hwndOwner, DWORD dwFlags) override { return DIERR_UNSUPPORTED; } STDMETHOD(Initialize)(HINSTANCE hinst, DWORD dwVersion) override { return DI_OK; } STDMETHOD(FindDevice)(REFGUID rguidClass, LPCSTR ptszName, LPGUID pguidInstance) override { if (pguidInstance == NULL) { return E_POINTER; } if (ptszName != NULL) { if (_stricmp(ptszName, "keyboard") == 0) { *pguidInstance = GUID_SysKeyboard; return DI_OK; } if (_stricmp(ptszName, "mouse") == 0) { *pguidInstance = GUID_SysMouse; return DI_OK; } } return DIERR_NOTFOUND; } STDMETHOD(EnumDevicesBySemantics)( LPCSTR ptszUserName, LPDIACTIONFORMATA lpdiActionFormat, LPDIENUMDEVICESBYSEMANTICSCBA lpCallback, LPVOID pvRef, DWORD dwFlags ) override { return DIERR_UNSUPPORTED; } STDMETHOD(ConfigureDevices)( LPDICONFIGUREDEVICESCALLBACK lpdiCallback, LPDICONFIGUREDEVICESPARAMSA lpdiCDParams, DWORD dwFlags, LPVOID pvRefData ) override { return DIERR_UNSUPPORTED; } private: std::atomic refCount; }; // ------------------------------------------------------------ // Public API // ------------------------------------------------------------ HRESULT WINAPI IceDirectInput8Create( HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut, LPUNKNOWN punkOuter ) { if (ppvOut == NULL) { return E_POINTER; } *ppvOut = NULL; if (punkOuter != NULL) { return CLASS_E_NOAGGREGATION; } IceDirectInput8* di = new IceDirectInput8(); HRESULT hr = di->QueryInterface(riidltf, ppvOut); di->Release(); return hr; } bool IceDirectInputShim_RegisterRawInput(HWND hwnd) { if (hwnd == NULL || !::IsWindow(hwnd)) { return false; } { std::lock_guard lock(g_windowMutex); g_rawInputHwnd = hwnd; g_keyboardTargetHwnd = hwnd; g_mouseTargetHwnd = hwnd; } bool ok = IceDI_RegisterRawInputForCurrentTargets(hwnd); if (ok) { IceDI_ForceMouseCenterInternal(false); } return ok; } void IceDirectInputShim_SetMouseTargetWindow(HWND hwnd) { if (hwnd == NULL || !::IsWindow(hwnd)) { return; } { std::lock_guard lock(g_windowMutex); g_mouseTargetHwnd = hwnd; g_rawInputHwnd = hwnd; } IceDI_RegisterRawInputForCurrentTargets(hwnd); IceDI_ForceMouseCenterInternal(true); } void IceDirectInputShim_SetMouseCenterLock(bool enabled) { g_iceDI_CenterMouseWhenActive.store(enabled); if (!enabled) { IceDI_ClearMouseClipIfOwned(); } else { IceDI_ForceMouseCenterInternal(true); } } bool IceDirectInputShim_ForceMouseCenter() { return IceDI_ForceMouseCenterInternal(true); } bool IceDirectInputShim_HandleRawInput(LPARAM lParam) { UINT size = 0; if (::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)) != 0) { return false; } if (size == 0) { return false; } std::vector buffer; buffer.resize(size); UINT result = ::GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER) ); if (result == (UINT)-1 || result == 0) { return false; } RAWINPUT* raw = reinterpret_cast(buffer.data()); // RAWINPUTHEADER intentionally has no target HWND field. The target window // is chosen when registering RAWINPUTDEVICE::hwndTarget, in SetCooperativeLevel, // or through IceDirectInputShim_SetMouseTargetWindow(). Do not try to read // the target HWND from the raw-input header; that member does not exist. if (raw->header.dwType == RIM_TYPEKEYBOARD) { IceDirectInputDevice8* kbd = NULL; { std::lock_guard lock(g_deviceMutex); kbd = g_keyboardDevice; if (kbd != NULL) { kbd->AddRef(); } } if (kbd != NULL) { kbd->PushKeyboardRaw(raw->data.keyboard); kbd->Release(); return true; } } if (raw->header.dwType == RIM_TYPEMOUSE) { if (!g_iceDI_GameMouseActive.load()) { IceDI_ClearMouseClipIfOwned(); return false; } IceDirectInputDevice8* mouse = NULL; { std::lock_guard lock(g_deviceMutex); mouse = g_mouseDevice; if (mouse != NULL) { mouse->AddRef(); } } if (mouse != NULL) { mouse->PushMouseRaw(raw->data.mouse); mouse->Release(); IceDI_ForceMouseCenterInternal(false); return true; } } return false; } void IceDirectInputShim_SetGameMouseActive(bool active) { g_iceDI_GameMouseActive.store(active); IceDirectInputDevice8* mouse = NULL; { std::lock_guard lock(g_deviceMutex); mouse = g_mouseDevice; if (mouse != NULL) { mouse->AddRef(); } } if (mouse != NULL) { mouse->ResetMouseMotion(); mouse->Release(); } if (active) { IceDI_ForceMouseCenterInternal(true); } else { IceDI_ClearMouseClipIfOwned(); } } void IceDirectInputShim_Shutdown() { IceDirectInputDevice8* keyboard = NULL; IceDirectInputDevice8* mouse = NULL; { std::lock_guard lock(g_deviceMutex); keyboard = g_keyboardDevice; mouse = g_mouseDevice; g_keyboardDevice = NULL; g_mouseDevice = NULL; } if (keyboard != NULL) { keyboard->Release(); } if (mouse != NULL) { mouse->Release(); } { std::lock_guard lock(g_windowMutex); g_rawInputHwnd = NULL; g_keyboardTargetHwnd = NULL; g_mouseTargetHwnd = NULL; g_lastCenterWarpTick = 0; } g_iceDI_MouseAcquired.store(false); g_iceDI_GameMouseActive.store(false); IceDI_ClearMouseClipIfOwned(); }