/* Copyright (C) 1997-2001 Id Software, Inc. This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define KEYBINDINGS_HARDCODED //#define PRINT_HARDCODING_TABLES //#define MAX_HARDCODED_KEYS 118 // also in x11_hardcoded.h #include "x11.h" #include "../client/client.h" #ifdef KEYBINDINGS_HARDCODED #include "x11_hardcoded.h" #endif // TODO: add in_mouse? static cvar_t *in_dgamouse; cvar_t *in_grabinconsole; static qboolean input_inited = qfalse; static qboolean mouse_active = qfalse; static qboolean input_active = qfalse; static qboolean dgamouse = qfalse; static int mx, my; static qboolean ignore_one = qfalse; static Time myxtime; static Cursor CreateNullCursor (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 install_grabs (void) { assert(x11display.dpy && x11display.win); XDefineCursor (x11display.dpy, x11display.win, CreateNullCursor(x11display.dpy, x11display.win)); XGrabPointer (x11display.dpy, x11display.win, True, 0, GrabModeAsync, GrabModeAsync, x11display.win, None, CurrentTime); if (in_dgamouse->integer) { int MajorVersion, MinorVersion; if (XF86DGAQueryVersion (x11display.dpy, &MajorVersion, &MinorVersion)) { XF86DGADirectVideo (x11display.dpy, x11display.scr, XF86DGADirectMouse); XWarpPointer (x11display.dpy, None, x11display.win, 0, 0, 0, 0, 0, 0); dgamouse = qtrue; } else { // unable to query, probalby not supported Com_Printf( "Failed to detect XF86DGA Mouse\n" ); Cvar_Set( "in_dgamouse", "0" ); dgamouse = qfalse; } } else { XWarpPointer (x11display.dpy, None, x11display.win, 0, 0, 0, 0, x11display.win_width/2, x11display.win_height/2); } ignore_one = qtrue; // first mouse update after install_grabs is ignored mx = my = 0; mouse_active = qtrue; in_dgamouse->modified = qfalse; XGrabKeyboard (x11display.dpy, x11display.win, False, GrabModeAsync, GrabModeAsync, CurrentTime); input_active = qtrue; } static void uninstall_grabs (void) { assert(x11display.dpy && x11display.win); if (mouse_active) { if (dgamouse) { dgamouse = qfalse; XF86DGADirectVideo (x11display.dpy, x11display.scr, 0); } XUngrabPointer (x11display.dpy, CurrentTime); XUndefineCursor (x11display.dpy, x11display.win); // inviso cursor mouse_active = qfalse; mx = my = 0; } XUngrabKeyboard (x11display.dpy, CurrentTime); input_active = qfalse; } #ifdef PRINT_HARDCODING_TABLES static char *KeysymToKey(KeySym keysym) { char *buf; switch (keysym) { case XK_KP_Page_Up: case XK_KP_9: return "KP_PGUP"; case XK_Page_Up: return "K_PGUP"; case XK_KP_Page_Down: case XK_KP_3: return "KP_PGDN"; case XK_Page_Down: return "K_PGDN"; case XK_KP_Home: return "KP_HOME"; case XK_KP_7: return "KP_HOME"; case XK_Home: return "K_HOME"; case XK_KP_End: case XK_KP_1: return "KP_END"; case XK_End: return "K_END"; case XK_KP_Left: return "KP_LEFTARROW"; case XK_KP_4: return "KP_LEFTARROW"; case XK_Left: return "K_LEFTARROW"; case XK_KP_Right: return "KP_RIGHTARROW"; case XK_KP_6: return "KP_RIGHTARROW"; case XK_Right: return "K_RIGHTARROW"; case XK_KP_Down: case XK_KP_2: return "KP_DOWNARROW"; case XK_Down: return "K_DOWNARROW"; case XK_KP_Up: case XK_KP_8: return "KP_UPARROW"; case XK_Up: return "K_UPARROW"; case XK_Escape: return "K_ESCAPE"; case XK_KP_Enter: return "KP_ENTER"; case XK_Return: return "K_ENTER"; case XK_Tab: return "K_TAB"; case XK_F1: return "K_F1"; case XK_F2: return "K_F2"; case XK_F3: return "K_F3"; case XK_F4: return "K_F4"; case XK_F5: return "K_F5"; case XK_F6: return "K_F6"; case XK_F7: return "K_F7"; case XK_F8: return "K_F8"; case XK_F9: return "K_F9"; case XK_F10: return "K_F10"; case XK_F11: return "K_F11"; case XK_F12: return "K_F12"; // bk001206 - from Ryan's Fakk2 //case XK_BackSpace: return 8; // ctrl-h case XK_BackSpace: return "K_BACKSPACE"; case XK_KP_Delete: case XK_KP_Decimal: return "KP_DEL"; case XK_Delete: return "K_DEL"; case XK_Pause: return "K_PAUSE"; case XK_Shift_L: case XK_Shift_R: return "K_SHIFT"; case XK_Execute: case XK_Control_L: case XK_Control_R: return "K_CTRL"; case XK_Alt_L: case XK_Meta_L: case XK_Alt_R: case XK_Meta_R: return "K_ALT"; case XK_KP_Begin: return "KP_5"; case XK_Insert: return "K_INS"; case XK_KP_Insert: case XK_KP_0: return "KP_INS"; case XK_KP_Multiply: return "KP_STAR"; case XK_KP_Add: return "KP_PLUS"; case XK_KP_Subtract: return "KP_MINUS"; case XK_KP_Divide: return "KP_SLASH"; // bk001130 - from cvs1.17 (mkv) case XK_exclam: return va("%i", '1'); case XK_at: return va("%i", '2'); case XK_numbersign: return va("%i", '3'); case XK_dollar: return va("%i", '4'); case XK_percent: return va("%i", '5'); case XK_asciicircum: return va("%i", '6'); case XK_ampersand: return va("%i", '7'); case XK_asterisk: return va("%i", '8'); case XK_parenleft: return va("%i", '9'); case XK_parenright: return va("%i", '0'); // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=472 case XK_space: case XK_KP_Space: return "K_SPACE"; // wsw : mdr case XK_Caps_Lock: return "K_CAPSLOCK"; case XK_Num_Lock: return "K_NUMLOCK"; case XK_Scroll_Lock: return "K_SCROLLLOCK"; } buf = XKeysymToString(keysym); if (buf != NULL) { int key = *(unsigned char *)buf; if (strlen(buf) == 1 && (key >= 9 && key <= 127)) return va("%i", key); } if( keysym >= 9 && keysym < 127 ) return va("%i", (int)keysym); return "-1"; // no luck } static void PrintKeycodeToKeysymToKeyTable(void) { int i; KeySym keysym; char *key; printf("#define MAX_HARDCODED_KEYS %i\n", MAX_HARDCODED_KEYS); printf("\n"); printf("int keycode_to_keysym_key[MAX_HARDCODED_KEYS][2] = {\n"); for( i = 0; i < MAX_HARDCODED_KEYS; i++ ) { keysym = XKeycodeToKeysym(x11display.dpy, i, 0); key = KeysymToKey(keysym); printf("\t{ %i, %s },\n", (int)keysym, key); } printf("};\n"); } #endif // PRINT_HARDCODING_TABLES #ifdef KEYBINDINGS_HARDCODED // Q3 version static char *XLateKey(XKeyEvent *ev, int *key) { static char buf[64]; #ifdef PRINT_HARDCODING_TABLES static qboolean done = qfalse; if( !done ) { PrintKeycodeToKeysymToKeyTable(); done = qtrue; } #endif // binds come from hardcoded US keymap if( ev->keycode < MAX_HARDCODED_KEYS ) *key = keycode_to_keysym_key[ev->keycode][1]; else *key = -1; // can't handle it // char events use the real layout XLookupString(ev, buf, sizeof(buf), NULL, 0); return buf; } #else // KEYBINDINGS_HARDCODED // Q3 version static char *XLateKey(XKeyEvent *ev, int *key) { static char buf[64]; KeySym keysym; int XLookupRet; #ifdef PRINT_HARDCODING_TABLES static qboolean done = qfalse; if( !done ) { PrintKeycodeToKeysymToKeyTable(); done = qtrue; } #endif *key = 0; XLookupRet = XLookupString(ev, buf, sizeof buf, &keysym, 0); #ifdef KBD_DBG ri.Printf("XLookupString ret: %d buf: %s keysym: %x\n", XLookupRet, buf, keysym); #endif switch (keysym) { case XK_KP_Page_Up: case XK_KP_9: *key = KP_PGUP; break; case XK_Page_Up: *key = K_PGUP; break; case XK_KP_Page_Down: case XK_KP_3: *key = KP_PGDN; break; case XK_Page_Down: *key = K_PGDN; break; case XK_KP_Home: *key = KP_HOME; break; case XK_KP_7: *key = KP_HOME; break; case XK_Home: *key = K_HOME; break; case XK_KP_End: case XK_KP_1: *key = KP_END; break; case XK_End: *key = K_END; break; case XK_KP_Left: *key = KP_LEFTARROW; break; case XK_KP_4: *key = KP_LEFTARROW; break; case XK_Left: *key = K_LEFTARROW; break; case XK_KP_Right: *key = KP_RIGHTARROW; break; case XK_KP_6: *key = KP_RIGHTARROW; break; case XK_Right: *key = K_RIGHTARROW; break; case XK_KP_Down: case XK_KP_2: *key = KP_DOWNARROW; break; case XK_Down: *key = K_DOWNARROW; break; case XK_KP_Up: case XK_KP_8: *key = KP_UPARROW; break; case XK_Up: *key = K_UPARROW; break; case XK_Escape: *key = K_ESCAPE; break; case XK_KP_Enter: *key = KP_ENTER; break; case XK_Return: *key = K_ENTER; break; case XK_Tab: *key = K_TAB; break; case XK_F1: *key = K_F1; break; case XK_F2: *key = K_F2; break; case XK_F3: *key = K_F3; break; case XK_F4: *key = K_F4; break; case XK_F5: *key = K_F5; break; case XK_F6: *key = K_F6; break; case XK_F7: *key = K_F7; break; case XK_F8: *key = K_F8; break; case XK_F9: *key = K_F9; break; case XK_F10: *key = K_F10; break; case XK_F11: *key = K_F11; break; case XK_F12: *key = K_F12; break; // bk001206 - from Ryan's Fakk2 //case XK_BackSpace: *key = 8; break; // ctrl-h case XK_BackSpace: *key = K_BACKSPACE; break; // ctrl-h case XK_KP_Delete: case XK_KP_Decimal: *key = KP_DEL; break; case XK_Delete: *key = K_DEL; break; case XK_Pause: *key = K_PAUSE; break; case XK_Shift_L: case XK_Shift_R: *key = K_SHIFT; break; case XK_Execute: case XK_Control_L: case XK_Control_R: *key = K_CTRL; break; case XK_Alt_L: case XK_Meta_L: case XK_Alt_R: case XK_Meta_R: *key = K_ALT; break; case XK_KP_Begin: *key = KP_5; break; case XK_Insert: *key = K_INS; break; case XK_KP_Insert: case XK_KP_0: *key = KP_INS; break; case XK_KP_Multiply: *key = KP_STAR; break; case XK_KP_Add: *key = KP_PLUS; break; case XK_KP_Subtract: *key = KP_MINUS; break; case XK_KP_Divide: *key = KP_SLASH; break; // bk001130 - from cvs1.17 (mkv) case XK_exclam: *key = '1'; break; case XK_at: *key = '2'; break; case XK_numbersign: *key = '3'; break; case XK_dollar: *key = '4'; break; case XK_percent: *key = '5'; break; case XK_asciicircum: *key = '6'; break; case XK_ampersand: *key = '7'; break; case XK_asterisk: *key = '8'; break; case XK_parenleft: *key = '9'; break; case XK_parenright: *key = '0'; break; // weird french keyboards .. // NOTE: console toggle is hardcoded in cl_keys.c, can't be unbound // cleaner would be .. using hardware key codes instead of the key syms // could also add a new K_KP_CONSOLE case XK_twosuperior: *key = '~'; break; // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=472 case XK_space: case XK_KP_Space: *key = K_SPACE; break; // wsw : mdr case XK_Caps_Lock: *key = K_CAPSLOCK; break; case XK_Num_Lock: *key = K_NUMLOCK; break; case XK_Scroll_Lock: *key = K_SCROLLLOCK; break; default: if (XLookupRet == 0) { /* if (com_developer->value) { ri.Printf(PRINT_ALL, "Warning: XLookupString failed on KeySym %d\n", keysym); } */ return NULL; } else { // XK_* tests failed, but XLookupString got a buffer, so let's try it *key = *(unsigned char *)buf; if (*key >= 'A' && *key <= 'Z') *key = *key - 'A' + 'a'; // if ctrl is pressed, the keys are not between 'A' and 'Z', for instance ctrl-z == 26 ^Z ^C etc. // see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=19 else if (*key >= 1 && *key <= 26) *key = *key + 'a' - 1; } break; } return buf; } #endif // KEYBINDINGS_HARDCODED /** * 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 qboolean X11_PendingInput(void) { assert(x11display.dpy); // Flush the display connection and look to see if events are queued XFlush(x11display.dpy); if (XEventsQueued(x11display.dpy, QueuedAlready)) return qtrue; { // 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( x11display.dpy ); FD_ZERO(&fdset); FD_SET(x11_fd, &fdset); if (select(x11_fd+1, &fdset, NULL, NULL, &zero_time) == 1) return(XPending(x11display.dpy)); } // Oh well, nothing is ready .. return qfalse; } static qboolean repeated_press(XEvent *event) { XEvent peekevent; qboolean repeated = qfalse; assert(x11display.dpy); if (X11_PendingInput()) { XPeekEvent(x11display.dpy, &peekevent); if ((peekevent.type == KeyPress) && (peekevent.xkey.keycode == event->xkey.keycode) && (peekevent.xkey.time == event->xkey.time)) { repeated = qtrue; // we only skip the KeyRelease event, so we send many key down events, but no releases, while repeating //XNextEvent(x11display.dpy, &peekevent); // skip event. } } return repeated; } static void HandleEvents(void) { XEvent event; qboolean dowarp = qfalse; int mwx = x11display.win_width / 2; int mwy = x11display.win_height / 2; unsigned char *p; int key = 0; unsigned int time = Sys_Milliseconds(); assert(x11display.dpy && x11display.win); while (XPending(x11display.dpy)) { XNextEvent (x11display.dpy, &event); switch (event.type) { case KeyPress: myxtime = event.xkey.time; p = (unsigned char *)XLateKey(&event.xkey, &key); if (key >= 0 && key <= 256) Key_Event(key, qtrue, time); if (p) { while (*p) Key_CharEvent(*p++); } break; case KeyRelease: if (repeated_press(&event)) break; // don't send release events when repeating XLateKey(&event.xkey, &key); Key_Event(key, event.type == KeyPress, time); break; case MotionNotify: // FIXME if (mouse_active) { if (dgamouse) { mx += event.xmotion.x_root * 2; my += event.xmotion.y_root * 2; } else { mx += ((int)event.xmotion.x - mwx); my += ((int)event.xmotion.y - mwy); if (mx || my) dowarp = qtrue; } } break; case ButtonPress: myxtime = event.xbutton.time; if (event.xbutton.button == 1) Key_Event(K_MOUSE1, 1, time); else if (event.xbutton.button == 2) Key_Event(K_MOUSE3, 1, time); else if (event.xbutton.button == 3) Key_Event(K_MOUSE2, 1, time); else if (event.xbutton.button == 4) Key_Event(K_MWHEELUP, 1, time); else if (event.xbutton.button == 5) Key_Event(K_MWHEELDOWN, 1, time); else if (event.xbutton.button >= 6 && event.xbutton.button <= 10) Key_Event(K_MOUSE4+event.xbutton.button-6, 1, time); break; case ButtonRelease: if (event.xbutton.button == 1) Key_Event(K_MOUSE1, 0, time); else if (event.xbutton.button == 2) Key_Event(K_MOUSE3, 0, time); else if (event.xbutton.button == 3) Key_Event(K_MOUSE2, 0, time); else if (event.xbutton.button == 4) Key_Event(K_MWHEELUP, 0, time); else if (event.xbutton.button == 5) Key_Event(K_MWHEELDOWN, 0, time); else if (event.xbutton.button >= 6 && event.xbutton.button <= 10) Key_Event(K_MOUSE4+event.xbutton.button-6, 0, time); break; case ClientMessage: if (event.xclient.data.l[0] == x11display.wmDeleteWindow) Cbuf_ExecuteText(EXEC_NOW, "quit"); break; } } if (dowarp) { XWarpPointer (x11display.dpy, None, x11display.win, 0, 0, 0, 0, x11display.win_width/2, x11display.win_height/2); } } /*****************************************************************************/ void IN_Commands (void) { } void IN_Move (usercmd_t *cmd) { if (mouse_active) { // wsw : pb : moved mousemove to cl_input (q3 like) if (ignore_one) { mx = my = 0; ignore_one = qfalse; } CL_MouseMove(cmd, mx, my); mx = my = 0; } } void IN_Activate (qboolean active) { if (!input_inited) return; assert(x11display.dpy && x11display.win); if (active) install_grabs(); else uninstall_grabs(); } void IN_Init(void) { if (input_inited) return; in_dgamouse = Cvar_Get ("in_dgamouse", "1", CVAR_ARCHIVE); in_grabinconsole = Cvar_Get ("in_grabinconsole", "0", CVAR_ARCHIVE); install_grabs(); input_inited = qtrue; } void IN_Shutdown (void) { if (!input_inited) return; uninstall_grabs(); input_inited = qfalse; } void IN_Restart(void) { IN_Shutdown(); IN_Init(); } void IN_Frame (void) { if (!input_inited) return; if ((cls.key_dest == key_console) && !in_grabinconsole->integer && !Cvar_VariableValue("vid_fullscreen")) { if (input_active) IN_Activate(qfalse); } else { if (!input_active) IN_Activate(qtrue); } HandleEvents(); if (input_active && in_dgamouse->modified) { uninstall_grabs(); install_grabs(); } }