Descent3/ddio_win/winmouse.cpp
2024-04-18 17:54:41 -05:00

575 lines
18 KiB
C++

// ----------------------------------------------------------------------------
// Mouse Interface
// ----------------------------------------------------------------------------
#include <stdlib.h>
#include <math.h>
#include "DDAccess.h"
#include "pserror.h"
#include "mono.h"
#include "ddio.h"
#include "ddio_win.h"
#include "Application.h"
#include "psclass.h"
#include "Macros.h"
typedef struct t_mse_button_info {
bool is_down[N_MSEBTNS];
ubyte down_count[N_MSEBTNS];
ubyte up_count[N_MSEBTNS];
DWORD time_down[N_MSEBTNS]; // in milliseconds windows ticks
DWORD time_up[N_MSEBTNS];
} t_mse_button_info;
typedef struct t_mse_event {
short btn;
short state;
} t_mse_event;
#define MOUSE_ZMIN 0 // mouse wheel z min and max (increments of 120 = 10 units)
#define MOUSE_ZMAX 1200
#define N_DIMSEBTNS 4 // # of REAL mouse buttons
#define MSEBTN_WHL_UP (N_DIMSEBTNS) // button index for mouse wheel up
#define MSEBTN_WHL_DOWN (N_DIMSEBTNS + 1) // button index for mouse wheel down
// taken from winuser.h
#ifndef WHEEL_DELTA
#define WHEEL_DELTA 120
#endif
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL 0x20a
#endif
bool rawInputOpened = false;
bool handlerCalled = false;
// ----------------------------------------------------------------------------
static bool DDIO_mouse_init = 0;
static struct mses_state {
LPDIRECTINPUTDEVICE lpdimse;
RECT brect; // limit rectangle of absolute mouse coords
short x, y, z; // current x,y,z in absolute mouse coords
short cx, cy, cz; // prior values of x,y,z from last mouse frame
short zmin, zmax; // 3 dimensional mouse devices use this
int btn_mask, btn_flags; // btn_flags are the avaiable buttons on this device in mask form.
float timer; // done to keep track of mouse polling. [ISB] this is in InjectD3 but not here?
bool emulated; // are we emulating direct input?
bool acquired;
bool suspended;
sbyte cursor_count;
float x_aspect, y_aspect; // used in calculating coordinates returned from ddio_MouseGetState
HANDLE hmseevt; // signaled if mouse input is awaiting.
short dx, dy, dz, imm_dz;
short mode; // mode of mouse operation.
short nbtns, naxis; // device caps.
} DDIO_mouse_state;
// Normally mouse events use ticks, attempting to use timer_GetTime which has more accuracy to smooth over bug with
// mouse buttons dropping.
float localDownStart[N_MSEBTNS];
float localUpStart[N_MSEBTNS];
static t_mse_button_info DIM_buttons;
static tQueue<t_mse_event, 16> MB_queue;
int wheelAccum = 0;
void DDIOShowCursor(BOOL show) {
if (show) {
if (DDIO_mouse_state.cursor_count == -1) {
ShowCursor(TRUE);
}
DDIO_mouse_state.cursor_count = 0;
} else {
if (DDIO_mouse_state.cursor_count == 0) {
ShowCursor(FALSE);
}
DDIO_mouse_state.cursor_count = -1;
}
}
void ddio_MouseMode(int mode) {
if (mode == MOUSE_EXCLUSIVE_MODE) {
DDIOShowCursor(FALSE);
} else if (mode == MOUSE_STANDARD_MODE) {
DDIOShowCursor(TRUE);
} else {
Int3();
return;
}
DDIO_mouse_state.mode = mode;
}
void ddio_MouseQueueFlush() {
memset(&DIM_buttons, 0, sizeof(DIM_buttons));
MB_queue.flush();
// Need to clear the new arrays, since otherwise the game will think you're holding down a button when leaving a UI
// screen.
memset(localDownStart, 0, sizeof(localDownStart));
memset(localUpStart, 0, sizeof(localUpStart));
}
void ddio_MouseReset() {
SetRect(&DDIO_mouse_state.brect, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
DDIO_mouse_state.zmin = MOUSE_ZMIN;
DDIO_mouse_state.zmax = MOUSE_ZMAX;
// set up new coordinates for mouse pointer.
DDIO_mouse_state.btn_mask = 0;
DDIO_mouse_state.dx = 0;
DDIO_mouse_state.dy = 0;
DDIO_mouse_state.dz = 0;
DDIO_mouse_state.imm_dz = 0;
DDIO_mouse_state.x = (DDIO_mouse_state.brect.right - DDIO_mouse_state.brect.left) / 2;
DDIO_mouse_state.y = (DDIO_mouse_state.brect.bottom - DDIO_mouse_state.brect.top) / 2;
DDIO_mouse_state.z = (DDIO_mouse_state.zmax = DDIO_mouse_state.zmin) / 2;
DDIO_mouse_state.cz = 0;
DDIO_mouse_state.x_aspect = 1.0f;
DDIO_mouse_state.y_aspect = 1.0f;
// reset button states
ddio_MouseQueueFlush();
}
// return mouse button down time.
// This function has been hacked to use timer_GetTime which can be much more accurate.
float ddio_MouseBtnDownTime(int btn) {
// DWORD ticks, curticks = GetTickCount();
float time, curtime = timer_GetTime();
// ASSERT(btn >= 0 && btn < N_MSEBTNS);
if (DIM_buttons.is_down[btn]) {
time = curtime - localDownStart[btn];
DIM_buttons.time_down[btn] = (int)(curtime * 1000);
localDownStart[btn] = curtime;
} else {
time = localUpStart[btn] - localDownStart[btn];
localUpStart[btn] = localDownStart[btn] = 0;
DIM_buttons.time_down[btn] = DIM_buttons.time_up[btn] = 0;
}
DIM_buttons.is_down[MSEBTN_WHL_UP] = false;
DIM_buttons.is_down[MSEBTN_WHL_DOWN] = false;
return time;
}
int ddio_MouseGetState(int *x, int *y, int *dx, int *dy, int *z, int *dz) {
// update mouse timer.
int btn_mask = DDIO_mouse_state.btn_mask;
DDIO_mouse_state.timer = timer_GetTime();
// get return values.
if (x)
*x = DDIO_mouse_state.x;
if (y)
*y = DDIO_mouse_state.y;
if (z)
*z = DDIO_mouse_state.z;
if (dx)
*dx = DDIO_mouse_state.dx;
if (dy)
*dy = DDIO_mouse_state.dy;
if (dz)
*dz = DDIO_mouse_state.dz;
DDIO_mouse_state.dx = 0;
DDIO_mouse_state.dy = 0;
DDIO_mouse_state.dz = 0;
DDIO_mouse_state.btn_mask = 0;
DIM_buttons.is_down[MSEBTN_WHL_UP] = false;
DIM_buttons.is_down[MSEBTN_WHL_DOWN] = false;
return btn_mask;
}
void ddio_InternalMouseFrame() {
int btn_mask = 0;
// These need to be continually maintained, since a small number of inputs rely on it being set every frame.
if (DIM_buttons.is_down[0])
btn_mask |= MOUSE_LB;
if (DIM_buttons.is_down[1])
btn_mask |= MOUSE_RB;
if (DIM_buttons.is_down[2])
btn_mask |= MOUSE_CB;
if (DIM_buttons.is_down[3])
btn_mask |= MOUSE_B4;
if (DIM_buttons.is_down[4])
btn_mask |= MOUSE_B5;
if (DIM_buttons.is_down[5])
btn_mask |= MOUSE_B6;
if (DIM_buttons.is_down[6])
btn_mask |= MOUSE_B7;
if (DIM_buttons.is_down[7])
btn_mask |= MOUSE_B8;
DDIO_mouse_state.btn_mask = btn_mask;
}
void MouseError() { MessageBoxA(nullptr, "Failed to init raw input for mouse", "Error", MB_ICONERROR); }
int RawInputHandler(HWND hWnd, unsigned int msg, unsigned int wParam, long lParam) {
unsigned int buttons;
t_mse_event ev;
float curtime = timer_GetTime();
if (DDIO_mouse_state.suspended) {
DDIO_mouse_state.btn_mask = 0;
DDIO_mouse_state.dx = 0;
DDIO_mouse_state.dy = 0;
DDIO_mouse_state.dz = 0;
return 0;
}
HRAWINPUT rawinputHandle = (HRAWINPUT)lParam;
UINT size = 0;
UINT result = GetRawInputData(rawinputHandle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
if (result == 0 && size > 0) {
void *buf = malloc(size);
if (!buf) {
return 0;
}
result = GetRawInputData(rawinputHandle, RID_INPUT, buf, &size, sizeof(RAWINPUTHEADER));
if (result >= 0) {
RAWINPUT *rawinput = (RAWINPUT *)buf;
if (rawinput->header.dwType == RIM_TYPEMOUSE) {
buttons = rawinput->data.mouse.ulButtons;
if (buttons & RI_MOUSE_LEFT_BUTTON_DOWN && !DIM_buttons.is_down[0]) {
DIM_buttons.down_count[0]++;
DIM_buttons.time_down[0] = GetTickCount();
localDownStart[0] = curtime;
DIM_buttons.is_down[0] = true;
DDIO_mouse_state.btn_mask |= MOUSE_LB;
ev.btn = 0;
ev.state = true;
MB_queue.send(ev);
/*if (hack)
{
fprintf(hack, "MB1 down at %f\n", timer_GetTime());
}*/
} else if (buttons & RI_MOUSE_LEFT_BUTTON_UP && DIM_buttons.is_down[0]) {
DIM_buttons.up_count[0]++;
DIM_buttons.is_down[0] = false;
DIM_buttons.time_up[0] = GetTickCount();
localUpStart[0] = curtime;
DDIO_mouse_state.btn_mask &= ~MOUSE_LB;
ev.btn = 0;
ev.state = false;
MB_queue.send(ev);
/*if (hack)
{
fprintf(hack, "MB1 up at %f\n", timer_GetTime());
}*/
}
if (buttons & RI_MOUSE_RIGHT_BUTTON_DOWN && !DIM_buttons.is_down[1]) {
DIM_buttons.down_count[1]++;
DIM_buttons.time_down[1] = GetTickCount();
localDownStart[1] = curtime;
DIM_buttons.is_down[1] = true;
DDIO_mouse_state.btn_mask |= MOUSE_RB;
ev.btn = 1;
ev.state = true;
MB_queue.send(ev);
} else if (buttons & RI_MOUSE_RIGHT_BUTTON_UP && DIM_buttons.is_down[1]) {
DIM_buttons.up_count[1]++;
DIM_buttons.is_down[1] = false;
DIM_buttons.time_up[1] = GetTickCount();
localUpStart[1] = curtime;
DDIO_mouse_state.btn_mask &= ~MOUSE_RB;
ev.btn = 1;
ev.state = false;
MB_queue.send(ev);
}
if (buttons & RI_MOUSE_MIDDLE_BUTTON_DOWN && !DIM_buttons.is_down[2]) {
DIM_buttons.down_count[2]++;
DIM_buttons.time_down[2] = GetTickCount();
localDownStart[2] = curtime;
DIM_buttons.is_down[2] = true;
DDIO_mouse_state.btn_mask |= MOUSE_CB;
ev.btn = 2;
ev.state = true;
MB_queue.send(ev);
} else if (buttons & RI_MOUSE_MIDDLE_BUTTON_UP && DIM_buttons.is_down[2]) {
DIM_buttons.up_count[2]++;
DIM_buttons.is_down[2] = false;
DIM_buttons.time_up[2] = GetTickCount();
localUpStart[2] = curtime;
DDIO_mouse_state.btn_mask &= ~MOUSE_CB;
ev.btn = 2;
ev.state = false;
MB_queue.send(ev);
}
if (buttons & RI_MOUSE_WHEEL) {
wheelAccum += (int)(short)rawinput->data.mouse.usButtonData;
if (wheelAccum >= WHEEL_DELTA) {
DIM_buttons.down_count[MSEBTN_WHL_UP]++;
DIM_buttons.up_count[MSEBTN_WHL_UP]++;
DIM_buttons.is_down[MSEBTN_WHL_UP] = true;
DIM_buttons.time_down[MSEBTN_WHL_UP] = GetTickCount();
DIM_buttons.time_up[MSEBTN_WHL_UP] = GetTickCount() + 100;
localDownStart[MSEBTN_WHL_UP] = curtime;
localUpStart[MSEBTN_WHL_UP] = curtime + .1f;
wheelAccum = 0;
} else if (wheelAccum <= -WHEEL_DELTA) {
DIM_buttons.down_count[MSEBTN_WHL_DOWN]++;
DIM_buttons.up_count[MSEBTN_WHL_DOWN]++;
DIM_buttons.is_down[MSEBTN_WHL_DOWN] = true;
DIM_buttons.time_down[MSEBTN_WHL_DOWN] = GetTickCount();
DIM_buttons.time_up[MSEBTN_WHL_DOWN] = GetTickCount() + 100;
localDownStart[MSEBTN_WHL_DOWN] = curtime;
localUpStart[MSEBTN_WHL_DOWN] = curtime + .1f;
wheelAccum = 0;
}
}
DDIO_mouse_state.dx += rawinput->data.mouse.lLastX;
DDIO_mouse_state.dy += rawinput->data.mouse.lLastY;
// DDIO_mouse_state.btn_mask = buttons;
}
DDIO_mouse_state.x += (float)rawinput->data.mouse.lLastX;
;
DDIO_mouse_state.y += rawinput->data.mouse.lLastY;
DDIO_mouse_state.z = 0;
// check bounds of mouse cursor.
if (DDIO_mouse_state.x < DDIO_mouse_state.brect.left)
DDIO_mouse_state.x = (short)DDIO_mouse_state.brect.left;
if (DDIO_mouse_state.x >= DDIO_mouse_state.brect.right)
DDIO_mouse_state.x = (short)DDIO_mouse_state.brect.right - 1;
if (DDIO_mouse_state.y < DDIO_mouse_state.brect.top)
DDIO_mouse_state.y = (short)DDIO_mouse_state.brect.top;
if (DDIO_mouse_state.y >= DDIO_mouse_state.brect.bottom)
DDIO_mouse_state.y = (short)DDIO_mouse_state.brect.bottom - 1;
if (DDIO_mouse_state.z > DDIO_mouse_state.zmax)
DDIO_mouse_state.z = (short)DDIO_mouse_state.zmax;
if (DDIO_mouse_state.z < DDIO_mouse_state.zmin)
DDIO_mouse_state.z = (short)DDIO_mouse_state.zmin;
}
free(buf);
}
return 0;
}
bool InitNewMouse() {
int i;
if (!rawInputOpened) {
char buf[256];
RAWINPUTDEVICE rawInputDevice = {};
rawInputDevice.usUsage = 0x0002;
rawInputDevice.usUsagePage = 0x0001;
//TODO: This code should be renabled when some solution for mouse capturing is decided on.
// The game should free the capture when the cursor is visible, and recapture it when it isn't visible.
// Account for the original mode.
//if (DDIO_mouse_state.mode == MOUSE_EXCLUSIVE_MODE)
rawInputDevice.dwFlags = RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY;
//else
// rawInputDevice.dwFlags = 0;
rawInputDevice.hwndTarget = DInputData.hwnd;
if (RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice)) == FALSE) {
Error("InitNewMouse: HID Registration failed: %d", GetLastError());
return false;
}
// HACK: Need to flush messages for this to work.
MSG msg;
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
DInputData.app->add_handler(WM_INPUT, (tOEWin32MsgCallback)&RawInputHandler);
DDIO_mouse_state.timer = timer_GetTime();
DDIO_mouse_state.naxis = 2;
DDIO_mouse_state.nbtns = N_DIMSEBTNS + 2; // always have a mousewheel
for (i = 0; i < DDIO_mouse_state.nbtns; i++) {
DDIO_mouse_state.btn_flags |= (1 << i);
}
memset(&DIM_buttons, 0, sizeof(t_mse_button_info));
rawInputOpened = true;
}
return true;
}
bool ddio_MouseInit() {
tWin32OS os;
int major, minor;
// see if we need to emulate directinput.
os = oeWin32Application::version(&major, &minor);
DDIO_mouse_state.emulated = true;
DDIO_mouse_state.lpdimse = NULL;
InitNewMouse();
// standard initialization
DDIO_mouse_state.emulated = (DDIO_mouse_state.lpdimse) ? false : true;
DDIO_mouse_state.cursor_count = ShowCursor(TRUE); // get initial count
while (DDIO_mouse_state.cursor_count >= 0) // hide cursor until truly hidden.
{
DDIO_mouse_state.cursor_count = ShowCursor(FALSE);
}
ddio_MouseMode(MOUSE_STANDARD_MODE);
DDIO_mouse_state.suspended = false;
ddio_MouseReset();
DDIO_mouse_init = true;
return true;
}
// here we deinitialize our Mouse from DirectInput.
void ddio_MouseClose() {
if (!DDIO_mouse_init)
return;
if (rawInputOpened) {
char buf[256];
RAWINPUTDEVICE rawInputDevice = {};
rawInputDevice.usUsage = 0x0002;
rawInputDevice.usUsagePage = 0x0001;
rawInputDevice.dwFlags = RIDEV_REMOVE;
rawInputDevice.hwndTarget = 0; // not sure why?
if (RegisterRawInputDevices(&rawInputDevice, 1, sizeof(rawInputDevice)) == FALSE) {
rawInputOpened = false;
DDIO_mouse_init = false;
Error("ddio_MouseClose: HID Registration failed: %d", GetLastError());
}
DInputData.app->remove_handler(WM_INPUT, (tOEWin32MsgCallback)&RawInputHandler);
rawInputOpened = false;
}
DDIO_mouse_init = false;
}
// used to prevent mouse input from being registered
void ddio_InternalMouseSuspend() {
if (!DDIO_mouse_init)
return;
DDIO_mouse_state.suspended = true;
}
void ddio_InternalMouseResume() {
if (!DDIO_mouse_init)
return;
DDIO_mouse_state.suspended = false;
}
// return mouse button down time
int ddio_MouseBtnDownCount(int btn) {
if (btn < 0 || btn >= N_MSEBTNS)
return 0;
int n_downs = DIM_buttons.down_count[btn];
if (n_downs) {
DIM_buttons.down_count[btn] = 0;
}
return n_downs;
}
// return mouse button up count
int ddio_MouseBtnUpCount(int btn) {
if (btn < 0 || btn >= N_MSEBTNS)
return 0;
int n_ups = DIM_buttons.up_count[btn];
DIM_buttons.up_count[btn] = 0;
return n_ups;
}
// get device caps
int ddio_MouseGetCaps(int *btn, int *axis) {
*btn = (int)DDIO_mouse_state.nbtns;
*axis = (int)DDIO_mouse_state.naxis;
return DDIO_mouse_state.btn_flags;
}
// gets limits on the position of the mouse cursor (or atleast what's returned from GetState)
void ddio_MouseGetLimits(int *left, int *top, int *right, int *bottom, int *zmin, int *zmax) {
*left = DDIO_mouse_state.brect.left;
*top = DDIO_mouse_state.brect.top;
*right = DDIO_mouse_state.brect.right;
*bottom = DDIO_mouse_state.brect.bottom;
if (zmin)
*zmin = DDIO_mouse_state.zmin;
if (zmax)
*zmax = DDIO_mouse_state.zmax;
}
// sets limits on the position of the mouse cursor (or atleast what's returned from GetState)
void ddio_MouseSetLimits(int left, int top, int right, int bottom, int zmin, int zmax) {
bool zaxis = (DDIO_mouse_state.naxis >= 3);
SetRect(&DDIO_mouse_state.brect, left, top, right, bottom);
DDIO_mouse_state.zmin = (!zmin && zaxis) ? MOUSE_ZMIN : zmin;
DDIO_mouse_state.zmax = (!zmax && zaxis) ? MOUSE_ZMAX : zmax;
DDIO_mouse_state.cx = left + (right - left) / 2;
DDIO_mouse_state.cy = top + (bottom - top) / 2;
}
// virtual coordinate system for mouse (match to video resolution set for optimal mouse usage.
void ddio_MouseSetVCoords(int width, int height) { ddio_MouseSetLimits(0, 0, width, height); }
// gets a mouse button event, returns false if none.
bool ddio_MouseGetEvent(int *btn, bool *state) {
t_mse_event evt;
if (MB_queue.recv(&evt)) {
*btn = (int)evt.btn;
*state = evt.state ? true : false;
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
char Ctltext_MseBtnBindings[N_MSEBTNS][32] = {"mse-l\0\0\0\0\0\0\0\0\0\0\0\0",
"mse-r\0\0\0\0\0\0\0\0\0\0\0\0",
"mse-c\0\0\0\0\0\0\0\0\0\0\0\0",
"mse-b4\0\0\0\0\0\0\0\0\0\0\0",
"msew-u\0\0\0\0\0\0\0\0\0\0\0",
"msew-d\0\0\0\0\0\0\0\0\0\0\0",
"",
""};
char Ctltext_MseAxisBindings[][32] = {"mse-X\0\0\0\0\0\0\0\0\0\0\0\0", "mse-Y\0\0\0\0\0\0\0\0\0\0\0\0",
"msewheel\0\0\0\0\0\0\0\0\0\0"};
// returns string to binding.
const char *ddio_MouseGetBtnText(int btn) {
if (btn >= N_MSEBTNS || btn < 0)
return ("");
return Ctltext_MseBtnBindings[btn];
}
const char *ddio_MouseGetAxisText(int axis) {
if (axis >= (sizeof(Ctltext_MseAxisBindings) / sizeof(char *)) || axis < 0)
return ("");
return Ctltext_MseAxisBindings[axis];
}