mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
1634 lines
43 KiB
C++
1634 lines
43 KiB
C++
#ifndef DIRECTINPUT_VERSION
|
|
#define DIRECTINPUT_VERSION 0x0800
|
|
#endif
|
|
|
|
#include "DirectInputShim.h"
|
|
|
|
#include <windows.h>
|
|
#include <dinput.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <deque>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <stdint.h>
|
|
|
|
#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<bool> g_iceDI_GameMouseActive(true);
|
|
static std::atomic<bool> g_iceDI_MouseAcquired(false);
|
|
static std::atomic<bool> 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<LONG>(pts[0].x, pts[1].x);
|
|
outRect->top = std::min<LONG>(pts[0].y, pts[1].y);
|
|
outRect->right = std::max<LONG>(pts[0].x, pts[1].x);
|
|
outRect->bottom = std::max<LONG>(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<std::mutex> 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<std::mutex> lock(g_windowMutex);
|
|
g_lastCenterWarpPoint = center;
|
|
g_lastCenterWarpTick = IceDI_GetTimeStamp();
|
|
}
|
|
|
|
static bool IceDI_IsRecentCenterWarpPoint(const POINT& pt) {
|
|
POINT lastPoint;
|
|
DWORD lastTick;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<IDirectInputDevice8A*>(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<DIPROPDWORD*>(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<const DIPROPDWORD*>(pdiph);
|
|
|
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<DWORD>(requested, (DWORD)queue.size());
|
|
|
|
for (DWORD i = 0; i < count; i++) {
|
|
queue.pop_front();
|
|
}
|
|
}
|
|
|
|
*pdwInOut = count;
|
|
return DI_OK;
|
|
}
|
|
|
|
DWORD count = std::min<DWORD>(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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mutex);
|
|
queue.clear();
|
|
}
|
|
|
|
void ResetMouseMotion() {
|
|
if (deviceType != ICE_DI_DEVICE_MOUSE) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> 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<ULONG> refCount;
|
|
iceDIShimDeviceType deviceType;
|
|
bool acquired;
|
|
DWORD bufferSize;
|
|
DWORD coopFlags;
|
|
HWND hwnd;
|
|
|
|
std::mutex mutex;
|
|
std::deque<DIDEVICEOBJECTDATA> 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<IDirectInput8A*>(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<std::mutex> 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<std::mutex> 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<ULONG> 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<std::mutex> 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<std::mutex> 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<BYTE> 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<RAWINPUT*>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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();
|
|
}
|