mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-14 22:41:53 +00:00
ed8f1ec8db
SVN r1888 (trunk)
559 lines
15 KiB
C++
559 lines
15 KiB
C++
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define DIRECTINPUT_VERSION 0x800
|
|
#define _WIN32_WINNT 0x0501
|
|
#include <windows.h>
|
|
#include <dinput.h>
|
|
|
|
#define USE_WINDOWS_DWORD
|
|
#include "i_input.h"
|
|
#include "i_system.h"
|
|
#include "d_event.h"
|
|
#include "d_gui.h"
|
|
#include "c_cvars.h"
|
|
#include "doomdef.h"
|
|
#include "doomstat.h"
|
|
#include "win32iface.h"
|
|
#include "rawinput.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
#define DINPUT_BUFFERSIZE 32
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
class FDInputKeyboard : public FKeyboard
|
|
{
|
|
public:
|
|
FDInputKeyboard();
|
|
~FDInputKeyboard();
|
|
|
|
bool GetDevice();
|
|
void ProcessInput();
|
|
|
|
protected:
|
|
LPDIRECTINPUTDEVICE8 Device;
|
|
};
|
|
|
|
class FRawKeyboard : public FKeyboard
|
|
{
|
|
public:
|
|
FRawKeyboard();
|
|
~FRawKeyboard();
|
|
|
|
bool GetDevice();
|
|
bool ProcessRawInput(RAWINPUT *rawinput, int code);
|
|
|
|
protected:
|
|
USHORT E1Prefix;
|
|
};
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
extern HWND Window;
|
|
extern LPDIRECTINPUT8 g_pdi;
|
|
extern LPDIRECTINPUT g_pdi3;
|
|
extern bool GUICapture;
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
// Convert DIK_* code to ASCII using Qwerty keymap
|
|
static const BYTE Convert[256] =
|
|
{
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 8, 9, // 0
|
|
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 13, 0, 'a', 's', // 1
|
|
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 39, '`', 0,'\\', 'z', 'x', 'c', 'v', // 2
|
|
'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, // 3
|
|
0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', // 4
|
|
'2', '3', '0', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '=', 0, 0, // 8
|
|
0, '@', ':', '_', 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, // 9
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
|
|
0, 0, 0, ',', 0, '/', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
|
|
0, 0, 0, 0, 0, 0, 0, 0
|
|
|
|
};
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
FKeyboard *Keyboard;
|
|
|
|
// Set this to false to make keypad-enter a usable separate key.
|
|
CVAR (Bool, k_mergekeys, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// FKeyboard - Constructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FKeyboard::FKeyboard()
|
|
{
|
|
memset(KeyStates, 0, sizeof(KeyStates));
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FKeyboard - Destructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FKeyboard::~FKeyboard()
|
|
{
|
|
AllKeysUp();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FKeyboard :: CheckAndSetKey
|
|
//
|
|
// Returns true if the key was already in the desired state, false if it
|
|
// wasn't.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FKeyboard::CheckAndSetKey(int keynum, INTBOOL down)
|
|
{
|
|
BYTE *statebyte = &KeyStates[keynum >> 3];
|
|
BYTE mask = 1 << (keynum & 7);
|
|
if (down)
|
|
{
|
|
if (*statebyte & mask)
|
|
{
|
|
return true;
|
|
}
|
|
*statebyte |= mask;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (*statebyte & mask)
|
|
{
|
|
*statebyte &= ~mask;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FKeyboard :: AllKeysUp
|
|
//
|
|
// For every key currently marked as down, send a key up event and clear it.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FKeyboard::AllKeysUp()
|
|
{
|
|
event_t ev = { 0 };
|
|
ev.type = EV_KeyUp;
|
|
|
|
for (int i = 0; i < 256/8; ++i)
|
|
{
|
|
if (KeyStates[i] != 0)
|
|
{
|
|
BYTE states = KeyStates[i];
|
|
int j = 0;
|
|
KeyStates[i] = 0;
|
|
do
|
|
{
|
|
if (states & 1)
|
|
{
|
|
ev.data1 = (i << 3) + j;
|
|
ev.data2 = Convert[ev.data1];
|
|
D_PostEvent(&ev);
|
|
}
|
|
states >>= 1;
|
|
++j;
|
|
}
|
|
while (states != 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FKeyboard :: PostKeyEvent
|
|
//
|
|
// Posts a keyboard event, but only if the state is different from what we
|
|
// currently think it is. (For instance, raw keyboard input sends key
|
|
// down events every time the key automatically repeats, so we want to
|
|
// discard those.)
|
|
//
|
|
//==========================================================================
|
|
|
|
void FKeyboard::PostKeyEvent(int key, INTBOOL down, bool foreground)
|
|
{
|
|
event_t ev = { 0 };
|
|
|
|
// Printf("key=%02x down=%02x\n", key, down);
|
|
// "Merge" multiple keys that are considered to be the same. If the
|
|
// original unmerged key is down, it also needs to go up. (In case
|
|
// somebody was holding the key down when they changed this setting.)
|
|
if (k_mergekeys)
|
|
{
|
|
if (key == DIK_NUMPADENTER || key == DIK_RMENU || key == DIK_RCONTROL)
|
|
{
|
|
k_mergekeys = false;
|
|
PostKeyEvent(key, false, foreground);
|
|
k_mergekeys = true;
|
|
key &= 0x7F;
|
|
}
|
|
else if (key == DIK_RSHIFT)
|
|
{
|
|
k_mergekeys = false;
|
|
PostKeyEvent(key, false, foreground);
|
|
k_mergekeys = true;
|
|
key = DIK_LSHIFT;
|
|
}
|
|
}
|
|
if (key == 0x59)
|
|
{ // Turn kp= on a Mac keyboard into kp= on a PC98 keyboard.
|
|
key = DIK_NUMPADEQUALS;
|
|
}
|
|
|
|
// Generate the event, if appropriate.
|
|
if (down)
|
|
{
|
|
if (!foreground || GUICapture)
|
|
{ // Do not generate key down events if we are in the background
|
|
// or in "GUI Capture" mode.
|
|
return;
|
|
}
|
|
ev.type = EV_KeyDown;
|
|
}
|
|
else
|
|
{
|
|
ev.type = EV_KeyUp;
|
|
}
|
|
if (CheckAndSetKey(key, down))
|
|
{ // Key is already down or up.
|
|
return;
|
|
}
|
|
ev.data1 = key;
|
|
ev.data2 = Convert[key];
|
|
D_PostEvent(&ev);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FDInputKeyboard - Constructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FDInputKeyboard::FDInputKeyboard()
|
|
{
|
|
Device = NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FDInputKeyboard - Destructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FDInputKeyboard::~FDInputKeyboard()
|
|
{
|
|
if (Device != NULL)
|
|
{
|
|
Device->Release();
|
|
Device = NULL;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FDInputKeyboard :: GetDevice
|
|
//
|
|
// Create the device interface and initialize it.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FDInputKeyboard::GetDevice()
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (g_pdi3 != NULL)
|
|
{ // DirectInput3 interface
|
|
hr = g_pdi3->CreateDevice(GUID_SysKeyboard, (LPDIRECTINPUTDEVICE*)&Device, NULL);
|
|
}
|
|
else if (g_pdi != NULL)
|
|
{ // DirectInput8 interface
|
|
hr = g_pdi->CreateDevice(GUID_SysKeyboard, &Device, NULL);
|
|
}
|
|
else
|
|
{
|
|
hr = -1;
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Yes, this is a keyboard.
|
|
hr = Device->SetDataFormat(&c_dfDIKeyboard);
|
|
if (FAILED(hr))
|
|
{
|
|
ufailit:
|
|
Device->Release();
|
|
Device = NULL;
|
|
return false;
|
|
}
|
|
// Set cooperative level.
|
|
hr = Device->SetCooperativeLevel(Window, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
|
if (FAILED(hr))
|
|
{
|
|
goto ufailit;
|
|
}
|
|
// Set buffer size so we can use buffered input.
|
|
DIPROPDWORD prop;
|
|
prop.diph.dwSize = sizeof(prop);
|
|
prop.diph.dwHeaderSize = sizeof(prop.diph);
|
|
prop.diph.dwObj = 0;
|
|
prop.diph.dwHow = DIPH_DEVICE;
|
|
prop.dwData = DINPUT_BUFFERSIZE;
|
|
hr = Device->SetProperty(DIPROP_BUFFERSIZE, &prop.diph);
|
|
if (FAILED(hr))
|
|
{
|
|
goto ufailit;
|
|
}
|
|
Device->Acquire();
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FDInputKeyboard :: ProcessInput
|
|
//
|
|
//==========================================================================
|
|
|
|
void FDInputKeyboard::ProcessInput()
|
|
{
|
|
DIDEVICEOBJECTDATA od;
|
|
DWORD dwElements;
|
|
HRESULT hr;
|
|
bool foreground = (GetForegroundWindow() == Window);
|
|
|
|
for (;;)
|
|
{
|
|
DWORD cbObjectData = g_pdi3 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA);
|
|
dwElements = 1;
|
|
hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0);
|
|
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
|
|
{
|
|
Device->Acquire();
|
|
hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0);
|
|
}
|
|
if (FAILED(hr) || !dwElements)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (od.dwOfs >= 1 && od.dwOfs <= 255)
|
|
{
|
|
PostKeyEvent(od.dwOfs, od.dwData & 0x80, foreground);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/**************************************************************************/
|
|
|
|
//==========================================================================
|
|
//
|
|
// FRawKeyboard - Constructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FRawKeyboard::FRawKeyboard()
|
|
{
|
|
E1Prefix = 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FRawKeyboard - Destructor
|
|
//
|
|
//==========================================================================
|
|
|
|
FRawKeyboard::~FRawKeyboard()
|
|
{
|
|
if (MyRegisterRawInputDevices != NULL)
|
|
{
|
|
RAWINPUTDEVICE rid;
|
|
rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE;
|
|
rid.usUsage = HID_GDP_KEYBOARD;
|
|
rid.dwFlags = RIDEV_REMOVE;
|
|
rid.hwndTarget = NULL;
|
|
MyRegisterRawInputDevices(&rid, 1, sizeof(rid));
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FRawKeyboard :: GetDevice
|
|
//
|
|
// Ensure the API is present and we can listen for keyboard input.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FRawKeyboard::GetDevice()
|
|
{
|
|
RAWINPUTDEVICE rid;
|
|
|
|
if (MyRegisterRawInputDevices == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE;
|
|
rid.usUsage = HID_GDP_KEYBOARD;
|
|
rid.dwFlags = RIDEV_INPUTSINK;
|
|
rid.hwndTarget = Window;
|
|
if (!MyRegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FRawKeyboard :: ProcessRawInput
|
|
//
|
|
// Convert scan codes to DirectInput key codes. For the most part, this is
|
|
// straight forward: Scan codes without any prefix are passed unmodified.
|
|
// Scan codes with an 0xE0 prefix byte are generally passed by ORing them
|
|
// with 0x80. And scan codes with an 0xE1 prefix are the annoying Pause key
|
|
// which will generate another scan code that looks like Num Lock.
|
|
//
|
|
// This is a bit complicated only because the state of PC key codes is a bit
|
|
// of a mess. Keyboards may use simpler codes internally, but for the sake
|
|
// of compatibility, programs are presented with XT-compatible codes. This
|
|
// means that keys which were originally a shifted form of another key and
|
|
// were split off into a separate key all their own, or which were formerly
|
|
// a separate key and are now part of another key (most notable PrtScn and
|
|
// SysRq), will still generate code sequences that XT-era software will
|
|
// still perceive as the original sequences to use those keys.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FRawKeyboard::ProcessRawInput(RAWINPUT *raw, int code)
|
|
{
|
|
if (raw->header.dwType != RIM_TYPEKEYBOARD)
|
|
{
|
|
return false;
|
|
}
|
|
int keycode = raw->data.keyboard.MakeCode;
|
|
if (keycode == 0 && (raw->data.keyboard.Flags & RI_KEY_E0))
|
|
{ // Even if the make code is 0, we might still be able to extract a
|
|
// useful key from the message.
|
|
if (raw->data.keyboard.VKey >= VK_BROWSER_BACK && raw->data.keyboard.VKey <= VK_LAUNCH_APP2)
|
|
{
|
|
static const BYTE MediaKeys[VK_LAUNCH_APP2 - VK_BROWSER_BACK + 1] =
|
|
{
|
|
DIK_WEBBACK, DIK_WEBFORWARD, DIK_WEBREFRESH, DIK_WEBSTOP,
|
|
DIK_WEBSEARCH, DIK_WEBFAVORITES, DIK_WEBHOME,
|
|
|
|
DIK_MUTE, DIK_VOLUMEDOWN, DIK_VOLUMEUP,
|
|
DIK_NEXTTRACK, DIK_PREVTRACK, DIK_MEDIASTOP, DIK_PLAYPAUSE,
|
|
|
|
DIK_MAIL, DIK_MEDIASELECT, DIK_MYCOMPUTER, DIK_CALCULATOR
|
|
};
|
|
keycode = MediaKeys[raw->data.keyboard.VKey - VK_BROWSER_BACK];
|
|
}
|
|
}
|
|
if (keycode < 1 || keycode > 0xFF)
|
|
{
|
|
return false;
|
|
}
|
|
if (raw->data.keyboard.Flags & RI_KEY_E1)
|
|
{
|
|
E1Prefix = raw->data.keyboard.MakeCode;
|
|
return false;
|
|
}
|
|
if (raw->data.keyboard.Flags & RI_KEY_E0)
|
|
{
|
|
if (keycode == DIK_LSHIFT || keycode == DIK_RSHIFT)
|
|
{ // Ignore fake shifts.
|
|
return false;
|
|
}
|
|
keycode |= 0x80;
|
|
}
|
|
// The sequence for an unshifted pause is E1 1D 45 (E1 Prefix +
|
|
// Control + Num Lock).
|
|
if (E1Prefix)
|
|
{
|
|
if (E1Prefix == 0x1D && keycode == DIK_NUMLOCK)
|
|
{
|
|
keycode = DIK_PAUSE;
|
|
E1Prefix = 0;
|
|
}
|
|
else
|
|
{
|
|
E1Prefix = 0;
|
|
return false;
|
|
}
|
|
}
|
|
// If you press Ctrl+Pause, the keyboard sends the Break make code
|
|
// E0 46 instead of the Pause make code.
|
|
if (keycode == 0xC6)
|
|
{
|
|
keycode = DIK_PAUSE;
|
|
}
|
|
// If you press Ctrl+PrtScn (to get SysRq), the keyboard sends
|
|
// the make code E0 37. If you press PrtScn without any modifiers,
|
|
// it sends E0 2A E0 37. And if you press Alt+PrtScn, it sends 54
|
|
// (which is undefined in the charts I can find.)
|
|
if (keycode == 0x54)
|
|
{
|
|
keycode = DIK_SYSRQ;
|
|
}
|
|
// If you press any keys in the island between the main keyboard
|
|
// and the numeric keypad with Num Lock turned on, they generate
|
|
// a fake shift before their actual codes. They do not generate this
|
|
// fake shift if Num Lock is off. We unconditionally discard fake
|
|
// shifts above, so we don't need to do anything special for these,
|
|
// since they are also prefixed by E0 so we can tell them apart from
|
|
// their keypad counterparts.
|
|
|
|
// Okay, we're done translating the keycode. Post it (or ignore it.)
|
|
PostKeyEvent(keycode, !(raw->data.keyboard.Flags & RI_KEY_BREAK), code == RIM_INPUT);
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// I_StartupKeyboard
|
|
//
|
|
//==========================================================================
|
|
|
|
void I_StartupKeyboard()
|
|
{
|
|
Keyboard = new FRawKeyboard;
|
|
if (Keyboard->GetDevice())
|
|
{
|
|
return;
|
|
}
|
|
delete Keyboard;
|
|
Keyboard = new FDInputKeyboard;
|
|
if (!Keyboard->GetDevice())
|
|
{
|
|
delete Keyboard;
|
|
}
|
|
}
|
|
|