dhewm3/neo/sys/linux/input.cpp
dhewg 736ec20d4d Untangle the epic precompiled.h mess
Don't include the lazy precompiled.h everywhere, only what's
required for the compilation unit.
platform.h needs to be included instead to provide all essential
defines and types.
All includes use the relative path to the neo or the game
specific root.
Move all idlib related includes from idlib/Lib.h to precompiled.h.
precompiled.h still exists for the MFC stuff in tools/.
Add some missing header guards.
2011-12-19 23:21:47 +01:00

565 lines
16 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 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.
Doom 3 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include <pthread.h>
#include "sys/platform.h"
#include "framework/KeyInput.h"
#include "sys/posix/posix_public.h"
#include "sys/linux/local.h"
idCVar in_mouse( "in_mouse", "1", CVAR_SYSTEM | CVAR_ARCHIVE, "" );
idCVar in_dgamouse( "in_dgamouse", "1", CVAR_SYSTEM | CVAR_ARCHIVE, "" );
idCVar in_nograb( "in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "" );
// have a working xkb extension
static bool have_xkb = false;
// toggled by grab calls - decides if we ignore MotionNotify events
static bool mouse_active = false;
// non-DGA pointer-warping mouse input
static int mwx, mwy;
static int mx = 0, my = 0;
// time mouse was last reset, we ignore the first 50ms of the mouse to allow settling of events
static int mouse_reset_time = 0;
#define MOUSE_RESET_DELAY 50
// backup original values for pointer grab/ungrab
static int mouse_accel_numerator;
static int mouse_accel_denominator;
static int mouse_threshold;
static byte s_scantokey[128] = {
/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 8 */ 0, 27, '1', '2', '3', '4', '5', '6', // 27 - ESC
/* 10 */ '7', '8', '9', '0', '-', '=', K_BACKSPACE, 9, // 9 - TAB
/* 18 */ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
/* 20 */ 'o', 'p', '[', ']', K_ENTER, K_CTRL, 'a', 's',
/* 28 */ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
/* 30 */ '\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v',
/* 38 */ 'b', 'n', 'm', ',', '.', '/', K_SHIFT, K_KP_STAR,
/* 40 */ K_ALT, ' ', K_CAPSLOCK, K_F1, K_F2, K_F3, K_F4, K_F5,
/* 48 */ K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, 0, K_HOME,
/* 50 */ K_UPARROW, K_PGUP, K_KP_MINUS, K_LEFTARROW, K_KP_5, K_RIGHTARROW, K_KP_PLUS, K_END,
/* 58 */ K_DOWNARROW, K_PGDN, K_INS, K_DEL, 0, 0, '\\', K_F11,
/* 60 */ K_F12, K_HOME, K_UPARROW, K_PGUP, K_LEFTARROW, 0, K_RIGHTARROW, K_END,
/* 68 */ K_DOWNARROW, K_PGDN, K_INS, K_DEL, K_ENTER, K_CTRL, K_PAUSE, 0,
/* 70 */ '/', K_ALT, 0, 0, 0, 0, 0, 0,
/* 78 */ 0, 0, 0, 0, 0, 0, 0, 0
};
/*
=================
IN_Clear_f
=================
*/
void IN_Clear_f( const idCmdArgs &args ) {
idKeyInput::ClearStates();
}
/*
=================
Sys_InitInput
=================
*/
void Sys_InitInput(void) {
int major_in_out, minor_in_out, opcode_rtrn, event_rtrn, error_rtrn;
bool ret;
common->Printf( "\n------- Input Initialization -------\n" );
assert( dpy );
cmdSystem->AddCommand( "in_clear", IN_Clear_f, CMD_FL_SYSTEM, "reset the input keys" );
major_in_out = XkbMajorVersion;
minor_in_out = XkbMinorVersion;
ret = XkbLibraryVersion( &major_in_out, &minor_in_out );
common->Printf( "XKB extension: compile time 0x%x:0x%x, runtime 0x%x:0x%x: %s\n", XkbMajorVersion, XkbMinorVersion, major_in_out, minor_in_out, ret ? "OK" : "Not compatible" );
if ( ret ) {
ret = XkbQueryExtension( dpy, &opcode_rtrn, &event_rtrn, &error_rtrn, &major_in_out, &minor_in_out );
if ( ret ) {
common->Printf( "XKB extension present on server ( 0x%x:0x%x )\n", major_in_out, minor_in_out );
have_xkb = true;
} else {
common->Printf( "XKB extension not present on server\n" );
have_xkb = false;
}
} else {
have_xkb = false;
}
common->Printf( "------------------------------------\n" );
}
//#define XEVT_DBG
//#define XEVT_DBG2
static Cursor Sys_XCreateNullCursor( Display *display, Window root ) {
Pixmap cursormask;
XGCValues xgc;
GC gc;
XColor dummycolour;
Cursor cursor;
cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/);
xgc.function = GXclear;
gc = XCreateGC(display, cursormask, GCFunction, &xgc);
XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
dummycolour.pixel = 0;
dummycolour.red = 0;
dummycolour.flags = 04;
cursor = XCreatePixmapCursor(display, cursormask, cursormask,
&dummycolour,&dummycolour, 0,0);
XFreePixmap(display,cursormask);
XFreeGC(display,gc);
return cursor;
}
static void Sys_XInstallGrabs( void ) {
assert( dpy );
XWarpPointer( dpy, None, win,
0, 0, 0, 0,
glConfig.vidWidth / 2, glConfig.vidHeight / 2 );
XSync( dpy, False );
XDefineCursor( dpy, win, Sys_XCreateNullCursor( dpy, win ) );
XGrabPointer( dpy, win,
False,
MOUSE_MASK,
GrabModeAsync, GrabModeAsync,
win,
None,
CurrentTime );
XGetPointerControl( dpy, &mouse_accel_numerator, &mouse_accel_denominator,
&mouse_threshold );
XChangePointerControl( dpy, True, True, 1, 1, 0 );
XSync( dpy, False );
mouse_reset_time = Sys_Milliseconds ();
if ( in_dgamouse.GetBool() && !dga_found ) {
common->Printf("XF86DGA not available, forcing DGA mouse off\n");
in_dgamouse.SetBool( false );
}
if ( in_dgamouse.GetBool() ) {
#if defined( ID_ENABLE_DGA )
XF86DGADirectVideo( dpy, DefaultScreen( dpy ), XF86DGADirectMouse );
XWarpPointer( dpy, None, win, 0, 0, 0, 0, 0, 0 );
#endif
} else {
mwx = glConfig.vidWidth / 2;
mwy = glConfig.vidHeight / 2;
mx = my = 0;
}
XGrabKeyboard( dpy, win,
False,
GrabModeAsync, GrabModeAsync,
CurrentTime );
XSync( dpy, False );
mouse_active = true;
}
void Sys_XUninstallGrabs(void) {
assert( dpy );
#if defined( ID_ENABLE_DGA )
if ( in_dgamouse.GetBool() ) {
common->DPrintf( "DGA Mouse - Disabling DGA DirectVideo\n" );
XF86DGADirectVideo( dpy, DefaultScreen( dpy ), 0 );
}
#endif
XChangePointerControl( dpy, true, true, mouse_accel_numerator,
mouse_accel_denominator, mouse_threshold );
XUngrabPointer( dpy, CurrentTime );
XUngrabKeyboard( dpy, CurrentTime );
XWarpPointer( dpy, None, win,
0, 0, 0, 0,
glConfig.vidWidth / 2, glConfig.vidHeight / 2);
XUndefineCursor( dpy, win );
mouse_active = false;
}
void Sys_GrabMouseCursor( bool grabIt ) {
#if defined( ID_DEDICATED )
return;
#endif
if ( !dpy ) {
#ifdef XEVT_DBG
common->DPrintf("Sys_GrabMouseCursor: !dpy\n");
#endif
return;
}
if ( glConfig.isFullscreen ) {
if ( !grabIt ) {
return; // never ungrab while fullscreen
}
if ( in_nograb.GetBool() ) {
common->DPrintf("forcing in_nograb 0 while running fullscreen\n");
in_nograb.SetBool( false );
}
}
if ( in_nograb.GetBool() ) {
if ( in_dgamouse.GetBool() ) {
common->DPrintf("in_nograb 1, forcing forcing DGA mouse off\n");
in_dgamouse.SetBool( false );
}
if (grabIt) {
mouse_active = true;
} else {
mouse_active = false;
}
return;
}
if ( grabIt && !mouse_active ) {
Sys_XInstallGrabs();
} else if ( !grabIt && mouse_active ) {
Sys_XUninstallGrabs();
}
}
/**
* XPending() actually performs a blocking read
* if no events available. From Fakk2, by way of
* Heretic2, by way of SDL, original idea GGI project.
* The benefit of this approach over the quite
* badly behaved XAutoRepeatOn/Off is that you get
* focus handling for free, which is a major win
* with debug and windowed mode. It rests on the
* assumption that the X server will use the
* same timestamp on press/release event pairs
* for key repeats.
*/
static bool Sys_XPendingInput( void ) {
// Flush the display connection
// and look to see if events are queued
XFlush( dpy );
if ( XEventsQueued( dpy, QueuedAlready) ) {
return true;
}
// More drastic measures are required -- see if X is ready to talk
static struct timeval zero_time;
int x11_fd;
fd_set fdset;
x11_fd = ConnectionNumber( dpy );
FD_ZERO( &fdset );
FD_SET( x11_fd, &fdset );
if ( select( x11_fd+1, &fdset, NULL, NULL, &zero_time ) == 1 ) {
return XPending( dpy );
}
// Oh well, nothing is ready ..
return false;
}
/**
* Intercept a KeyRelease-KeyPress sequence and ignore
*/
static bool Sys_XRepeatPress( XEvent *event ) {
XEvent peekevent;
bool repeated = false;
int lookupRet;
char buf[5];
KeySym keysym;
if ( Sys_XPendingInput() ) {
XPeekEvent( dpy, &peekevent );
if ((peekevent.type == KeyPress) &&
(peekevent.xkey.keycode == event->xkey.keycode) &&
(peekevent.xkey.time == event->xkey.time)) {
repeated = true;
XNextEvent( dpy, &peekevent );
// emit an SE_CHAR for the repeat
lookupRet = XLookupString( (XKeyEvent*)&peekevent, buf, sizeof(buf), &keysym, NULL );
if (lookupRet > 0) {
Posix_QueEvent( SE_CHAR, buf[ 0 ], 0, 0, NULL);
} else {
// shouldn't we be doing a release/press in this order rather?
// ( doesn't work .. but that's what I would have expected to do though )
Posix_QueEvent( SE_KEY, s_scantokey[peekevent.xkey.keycode], true, 0, NULL);
Posix_QueEvent( SE_KEY, s_scantokey[peekevent.xkey.keycode], false, 0, NULL);
}
}
}
return repeated;
}
/*
==========================
Posix_PollInput
==========================
*/
void Posix_PollInput() {
static char buf[16];
static XEvent event;
static XKeyEvent *key_event = (XKeyEvent*)&event;
int lookupRet;
int b, dx, dy;
KeySym keysym;
if ( !dpy ) {
return;
}
// NOTE: Sys_GetEvent only calls when there are no events left
// but here we pump all X events that have accumulated
// pump one by one? or use threaded input?
while ( XPending( dpy ) ) {
XNextEvent( dpy, &event );
switch (event.type) {
case KeyPress:
#ifdef XEVT_DBG
if (key_event->keycode > 0x7F)
common->DPrintf("WARNING: KeyPress keycode > 0x7F");
#endif
key_event->keycode &= 0x7F;
#ifdef XEVT_DBG2
printf("SE_KEY press %d\n", key_event->keycode);
#endif
Posix_QueEvent( SE_KEY, s_scantokey[key_event->keycode], true, 0, NULL);
lookupRet = XLookupString(key_event, buf, sizeof(buf), &keysym, NULL);
if (lookupRet > 0) {
char s = buf[0];
#ifdef XEVT_DBG
if (buf[1]!=0)
common->DPrintf("WARNING: got XLookupString buffer '%s' (%d)\n", buf, strlen(buf));
#endif
#ifdef XEVT_DBG2
printf("SE_CHAR %s\n", buf);
#endif
Posix_QueEvent( SE_CHAR, s, 0, 0, NULL);
}
if (!Posix_AddKeyboardPollEvent( s_scantokey[key_event->keycode], true ))
return;
break;
case KeyRelease:
if (Sys_XRepeatPress(&event)) {
#ifdef XEVT_DBG2
printf("RepeatPress\n");
#endif
continue;
}
#ifdef XEVT_DBG
if (key_event->keycode > 0x7F)
common->DPrintf("WARNING: KeyRelease keycode > 0x7F");
#endif
key_event->keycode &= 0x7F;
#ifdef XEVT_DBG2
printf("SE_KEY release %d\n", key_event->keycode);
#endif
Posix_QueEvent( SE_KEY, s_scantokey[key_event->keycode], false, 0, NULL);
if (!Posix_AddKeyboardPollEvent( s_scantokey[key_event->keycode], false ))
return;
break;
case ButtonPress:
if (event.xbutton.button == 4) {
Posix_QueEvent( SE_KEY, K_MWHEELUP, true, 0, NULL);
if (!Posix_AddMousePollEvent( M_DELTAZ, 1 ))
return;
} else if (event.xbutton.button == 5) {
Posix_QueEvent( SE_KEY, K_MWHEELDOWN, true, 0, NULL);
if (!Posix_AddMousePollEvent( M_DELTAZ, -1 ))
return;
} else {
b = -1;
if (event.xbutton.button == 1) {
b = 0; // K_MOUSE1
} else if (event.xbutton.button == 2) {
b = 2; // K_MOUSE3
} else if (event.xbutton.button == 3) {
b = 1; // K_MOUSE2
} else if (event.xbutton.button == 6) {
b = 3; // K_MOUSE4
} else if (event.xbutton.button == 7) {
b = 4; // K_MOUSE5
}
if (b == -1 || b > 4) {
common->DPrintf("X ButtonPress %d not supported\n", event.xbutton.button);
} else {
Posix_QueEvent( SE_KEY, K_MOUSE1 + b, true, 0, NULL);
if (!Posix_AddMousePollEvent( M_ACTION1 + b, true ))
return;
}
}
break;
case ButtonRelease:
if (event.xbutton.button == 4) {
Posix_QueEvent( SE_KEY, K_MWHEELUP, false, 0, NULL);
} else if (event.xbutton.button == 5) {
Posix_QueEvent( SE_KEY, K_MWHEELDOWN, false, 0, NULL);
} else {
b = -1;
if (event.xbutton.button == 1) {
b = 0;
} else if (event.xbutton.button == 2) {
b = 2;
} else if (event.xbutton.button == 3) {
b = 1;
} else if (event.xbutton.button == 6) {
b = 3; // K_MOUSE4
} else if (event.xbutton.button == 7) {
b = 4; // K_MOUSE5
}
if (b == -1 || b > 4) {
common->DPrintf("X ButtonRelease %d not supported\n", event.xbutton.button);
} else {
Posix_QueEvent( SE_KEY, K_MOUSE1 + b, false, 0, NULL);
if (!Posix_AddMousePollEvent( M_ACTION1 + b, false ))
return;
}
}
break;
case MotionNotify:
if (!mouse_active)
break;
if (in_dgamouse.GetBool()) {
dx = event.xmotion.x_root;
dy = event.xmotion.y_root;
Posix_QueEvent( SE_MOUSE, dx, dy, 0, NULL);
// if we overflow here, we'll get a warning, but the delta will be completely processed anyway
Posix_AddMousePollEvent( M_DELTAX, dx );
if (!Posix_AddMousePollEvent( M_DELTAY, dy ))
return;
} else {
// if it's a center motion, we've just returned from our warp
// FIXME: we generate mouse delta on wrap return, but that lags us quite a bit from the initial event..
if (event.xmotion.x == glConfig.vidWidth / 2 &&
event.xmotion.y == glConfig.vidHeight / 2) {
mwx = glConfig.vidWidth / 2;
mwy = glConfig.vidHeight / 2;
Posix_QueEvent( SE_MOUSE, mx, my, 0, NULL);
Posix_AddMousePollEvent( M_DELTAX, mx );
if (!Posix_AddMousePollEvent( M_DELTAY, my ))
return;
mx = my = 0;
break;
}
dx = ((int) event.xmotion.x - mwx);
dy = ((int) event.xmotion.y - mwy);
mx += dx;
my += dy;
mwx = event.xmotion.x;
mwy = event.xmotion.y;
XWarpPointer(dpy,None,win,0,0,0,0, (glConfig.vidWidth/2),(glConfig.vidHeight/2));
}
break;
}
}
}
/*
=================
Sys_ShutdownInput
=================
*/
void Sys_ShutdownInput( void ) { }
/*
===============
Sys_MapCharForKey
===============
*/
unsigned char Sys_MapCharForKey( int _key ) {
int key; // scan key ( != doom key )
XkbStateRec kbd_state;
XEvent event;
KeySym keysym;
int lookupRet;
char buf[5];
if ( !have_xkb || !dpy ) {
return (unsigned char)_key;
}
// query the current keyboard group, must be passed as bit 13-14 in the constructed XEvent
// see X Keyboard Extension library specifications
XkbGetState( dpy, XkbUseCoreKbd, &kbd_state );
// lookup scancode from doom key code. unique hits
for ( key = 0; key < 128; key++ ) {
if ( _key == s_scantokey[ key ] ) {
break;
}
}
if ( key == 128 ) {
// it happens. these, we can't convert
common->DPrintf( "Sys_MapCharForKey: doom key %d -> keycode failed\n", _key );
return (unsigned char)_key;
}
memset( &event, 0, sizeof( XEvent ) );
event.xkey.type = KeyPress;
event.xkey.display = dpy;
event.xkey.time = CurrentTime;
event.xkey.keycode = key;
event.xkey.state = kbd_state.group << 13;
lookupRet = XLookupString( (XKeyEvent *)&event, buf, sizeof( buf ), &keysym, NULL );
if ( lookupRet <= 0 ) {
Sys_Printf( "Sys_MapCharForKey: XLookupString key 0x%x failed\n", key );
return (unsigned char)_key;
}
if ( lookupRet > 1 ) {
// only ever expecting 1 char..
Sys_Printf( "Sys_MapCharForKey: XLookupString returned '%s'\n", buf );
}
return buf[ 0 ];
}