/* =========================================================================== 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 . 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. =========================================================================== */ #ifdef USE_SDL #include "../idlib/precompiled.h" #include "../idlib/containers/List.h" #include "../idlib/Heap.h" #include "../framework/Common.h" #include "../framework/KeyInput.h" #include "../renderer/RenderSystem.h" #include "../renderer/tr_local.h" #include "../sys/sys_public.h" #ifdef __WINDOWS__ #include "win32/win_local.h" #include #endif extern bool g_inputGrabbed; const char *kbdNames[] = { "english", "french", "german", "italian", "spanish", "turkish", "norwegian", NULL }; idCVar in_kbd("in_kbd", "english", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT, "keyboard layout", kbdNames, idCmdSystem::ArgCompletion_String ); struct kbd_poll_t { int key; bool state; kbd_poll_t() { } kbd_poll_t(int k, bool s) { key = k; state = s; } }; struct mouse_poll_t { int action; int value; mouse_poll_t() { } mouse_poll_t(int a, int v) { action = a; value = v; } }; struct joystick_poll_t { int action; int value; joystick_poll_t() { } joystick_poll_t(int a, int v) { action = a; value = v; } }; static idList kbd_polls; static idList mouse_polls; static idList joystick_polls; static bool buttonStates[K_LAST_KEY]; static int joyAxis[MAX_JOYSTICK_AXIS]; static idList event_overflow; static byte mapkey(SDL_Keycode key) { switch (key) { case SDLK_BACKSPACE: return K_BACKSPACE; case SDLK_PAUSE: return K_PAUSE; } if (key <= SDLK_z) return key & 0xff; switch (key) { case SDLK_APPLICATION: return K_COMMAND; case SDLK_CAPSLOCK: return K_CAPSLOCK; case SDLK_SCROLLLOCK: return K_SCROLL; case SDLK_POWER: return K_POWER; case SDLK_UP: return K_UPARROW; case SDLK_DOWN: return K_DOWNARROW; case SDLK_LEFT: return K_LEFTARROW; case SDLK_RIGHT: return K_RIGHTARROW; case SDLK_LGUI: return K_LWIN; case SDLK_RGUI: return K_RWIN; case SDLK_MENU: return K_MENU; case SDLK_LALT: case SDLK_RALT: return K_ALT; case SDLK_RCTRL: case SDLK_LCTRL: return K_CTRL; case SDLK_RSHIFT: case SDLK_LSHIFT: return K_SHIFT; case SDLK_INSERT: return K_INS; case SDLK_DELETE: return K_DEL; case SDLK_PAGEDOWN: return K_PGDN; case SDLK_PAGEUP: return K_PGUP; case SDLK_HOME: return K_HOME; case SDLK_END: return K_END; case SDLK_F1: return K_F1; case SDLK_F2: return K_F2; case SDLK_F3: return K_F3; case SDLK_F4: return K_F4; case SDLK_F5: return K_F5; case SDLK_F6: return K_F6; case SDLK_F7: return K_F7; case SDLK_F8: return K_F8; case SDLK_F9: return K_F9; case SDLK_F10: return K_F10; case SDLK_F11: return K_F11; case SDLK_F12: return K_F12; // K_INVERTED_EXCLAMATION; case SDLK_F13: return K_F13; case SDLK_F14: return K_F14; case SDLK_F15: return K_F15; case SDLK_KP_7: return K_KP_HOME; case SDLK_KP_8: return K_KP_UPARROW; case SDLK_KP_9: return K_KP_PGUP; case SDLK_KP_4: return K_KP_LEFTARROW; case SDLK_KP_5: return K_KP_5; case SDLK_KP_6: return K_KP_RIGHTARROW; case SDLK_KP_1: return K_KP_END; case SDLK_KP_2: return K_KP_DOWNARROW; case SDLK_KP_3: return K_KP_PGDN; case SDLK_KP_ENTER: return K_KP_ENTER; case SDLK_KP_0: return K_KP_INS; case SDLK_KP_PERIOD: return K_KP_DEL; case SDLK_KP_DIVIDE: return K_KP_SLASH; // K_SUPERSCRIPT_TWO; case SDLK_KP_MINUS: return K_KP_MINUS; // K_ACUTE_ACCENT; case SDLK_KP_PLUS: return K_KP_PLUS; case SDLK_NUMLOCKCLEAR: return K_KP_NUMLOCK; case SDLK_KP_MULTIPLY: return K_KP_STAR; case SDLK_KP_EQUALS: return K_KP_EQUALS; // K_MASCULINE_ORDINATOR; // K_GRAVE_A; // K_AUX1; // K_CEDILLA_C; // K_GRAVE_E; // K_AUX2; // K_AUX3; // K_AUX4; // K_GRAVE_I; // K_AUX5; // K_AUX6; // K_AUX7; // K_AUX8; // K_TILDE_N; // K_GRAVE_O; // K_AUX9; // K_AUX10; // K_AUX11; // K_AUX12; // K_AUX13; // K_AUX14; // K_GRAVE_U; // K_AUX15; // K_AUX16; case SDLK_PRINTSCREEN: return K_PRINT_SCR; case SDLK_MODE: return K_RIGHT_ALT; } return 0; } static sys_jEvents mapjoybutton(SDL_GameControllerButton button) { switch (button) { case SDL_CONTROLLER_BUTTON_A: return J_ACTION1; case SDL_CONTROLLER_BUTTON_B: return J_ACTION2; case SDL_CONTROLLER_BUTTON_X: return J_ACTION3; case SDL_CONTROLLER_BUTTON_Y: return J_ACTION4; case SDL_CONTROLLER_BUTTON_BACK: return J_ACTION10; case SDL_CONTROLLER_BUTTON_GUIDE: // TODO: break; case SDL_CONTROLLER_BUTTON_START: return J_ACTION9; case SDL_CONTROLLER_BUTTON_LEFTSTICK: return J_ACTION7; case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return J_ACTION8; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return J_ACTION5; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return J_ACTION6; case SDL_CONTROLLER_BUTTON_DPAD_UP: return J_DPAD_UP; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return J_DPAD_DOWN; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return J_DPAD_LEFT; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return J_DPAD_RIGHT; default: common->Warning("unknown game controller button %u", button); break; } return MAX_JOY_EVENT; } static sys_jEvents mapjoyaxis(SDL_GameControllerAxis axis) { switch (axis) { case SDL_CONTROLLER_AXIS_LEFTX: return J_AXIS_LEFT_X; case SDL_CONTROLLER_AXIS_LEFTY: return J_AXIS_LEFT_Y; case SDL_CONTROLLER_AXIS_RIGHTX: return J_AXIS_RIGHT_X; case SDL_CONTROLLER_AXIS_RIGHTY: return J_AXIS_RIGHT_Y; case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return J_AXIS_LEFT_TRIG; case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return J_AXIS_RIGHT_TRIG; default: common->Warning("unknown game controller axis %u", axis); break; } return J_AXIS_MAX; } static void PushConsoleEvent(const char *s) { char *b; size_t len; len = strlen(s) + 1; b = (char *)Mem_Alloc(len); strcpy(b, s); SDL_Event event; event.type = SDL_USEREVENT; event.user.code = SE_CONSOLE; event.user.data1 = (void *)len; event.user.data2 = b; SDL_PushEvent(&event); } /* ================= Sys_InitInput ================= */ void Sys_InitInput() { kbd_polls.SetGranularity(64); mouse_polls.SetGranularity(64); joystick_polls.SetGranularity(64); event_overflow.SetGranularity(64); memset( buttonStates, 0, sizeof( buttonStates ) ); memset( joyAxis, 0, sizeof( joyAxis ) ); in_kbd.SetModified(); const int NumJoysticks = SDL_NumJoysticks(); for( int i = 0; i < NumJoysticks; ++i ) { SDL_GameControllerOpen( i ); } } /* ================= Sys_ShutdownInput ================= */ void Sys_ShutdownInput() { kbd_polls.Clear(); mouse_polls.Clear(); joystick_polls.Clear(); event_overflow.Clear(); } /* =========== Sys_InitScanTable =========== */ // Windows has its own version due to the tools #ifndef _WIN32 void Sys_InitScanTable() { } #endif /* =============== Sys_GetConsoleKey =============== */ unsigned char Sys_GetConsoleKey(bool shifted) { static unsigned char keys[2] = { '`', '~' }; if (in_kbd.IsModified()) { idStr lang = in_kbd.GetString(); if (lang.Length()) { if (!lang.Icmp("french")) { keys[0] = '<'; keys[1] = '>'; } else if (!lang.Icmp("german")) { keys[0] = '^'; keys[1] = 176; // ° } else if (!lang.Icmp("italian")) { keys[0] = '\\'; keys[1] = '|'; } else if (!lang.Icmp("spanish")) { keys[0] = 186; // º keys[1] = 170; // ª } else if (!lang.Icmp("turkish")) { keys[0] = '"'; keys[1] = 233; // é } else if (!lang.Icmp("norwegian")) { keys[0] = 124; // | keys[1] = 167; // § } } in_kbd.ClearModified(); } return shifted ? keys[1] : keys[0]; } /* =============== Sys_MapCharForKey =============== */ unsigned char Sys_MapCharForKey(int key) { return key & 0xff; } /* =============== Sys_GrabMouseCursor =============== */ void Sys_GrabMouseCursor(bool grabIt) { int flags; if (grabIt) flags = GRAB_ENABLE | GRAB_HIDECURSOR; else flags = 0; GLimp_GrabInput(flags); } void PushButton( int key, bool value ) { // So we don't keep sending the same SE_KEY message over and over again if ( buttonStates[key] != value ) { buttonStates[key] = value; sysEvent_t res = { SE_KEY, key, value ? 1 : 0, 0, NULL }; event_overflow.Append(res); } } /* ================ Sys_GetEvent ================ */ sysEvent_t Sys_GetEvent() { SDL_Event ev; sysEvent_t res = { }; byte key; static const sysEvent_t res_none = { SE_NONE, 0, 0, 0, NULL }; // process any overflow. if (event_overflow.Num() > 0) { res = event_overflow[0]; event_overflow.RemoveIndex(0); return res; } // overflow text input. static char *s = NULL; static size_t s_pos = 0; if (s) { res.evType = SE_CHAR; res.evValue = s[s_pos]; s_pos++; if (!s[s_pos]) { free(s); s = NULL; s_pos = 0; } return res; } static byte c = 0; if (c) { res.evType = SE_CHAR; res.evValue = c; c = 0; return res; } bool getNext = true; while (SDL_PollEvent(&ev) && getNext) { getNext = false; switch (ev.type) { #ifdef __WINDOWS__ // on windows we need to grab the hwnd. case SDL_SYSWMEVENT: if (win32.hWnd == NULL) { win32.hWnd = ev.syswm.msg->msg.win.hwnd; } getNext = true; // try to get a decent event. break; #endif case SDL_WINDOWEVENT: switch (ev.window.event) { case SDL_WINDOWEVENT_FOCUS_GAINED: { // unset modifier, in case alt-tab was used to leave window and ALT is still set // as that can cause fullscreen-toggling when pressing enter... SDL_Keymod currentmod = SDL_GetModState(); int newmod = KMOD_NONE; if (currentmod & KMOD_CAPS) // preserve capslock newmod |= KMOD_CAPS; SDL_SetModState((SDL_Keymod)newmod); } // new context because visual studio complains about newmod and currentmod not initialized because of the case SDL_WINDOWEVENT_FOCUS_LOST GLimp_GrabInput(GRAB_ENABLE | GRAB_REENABLE | GRAB_HIDECURSOR); break; case SDL_WINDOWEVENT_FOCUS_LOST: GLimp_GrabInput(0); break; } return res_none; case SDL_KEYDOWN: if (ev.key.keysym.sym == SDLK_RETURN && (ev.key.keysym.mod & KMOD_ALT) > 0) { cvarSystem->SetCVarBool("r_fullscreen", !renderSystem->IsFullScreen()); PushConsoleEvent("vid_restart"); return res_none; } // fall through case SDL_KEYUP: key = mapkey(ev.key.keysym.sym); if(!key) { if (ev.key.keysym.scancode == SDL_SCANCODE_GRAVE) { key = Sys_GetConsoleKey(true); } else { if (ev.type == SDL_KEYDOWN) { common->Warning("unmapped SDL key %d", ev.key.keysym.sym); getNext = true; // try to get a decent event. break; } } } res.evType = SE_KEY; res.evValue = key; res.evValue2 = ev.key.state == SDL_PRESSED ? 1 : 0; kbd_polls.Append(kbd_poll_t(key, ev.key.state == SDL_PRESSED)); if ( (key == K_BACKSPACE && ev.key.state == SDL_PRESSED) || SDL_GetEventState(SDL_TEXTINPUT) == SDL_DISABLE) c = key; return res; case SDL_TEXTINPUT: if (ev.text.text && *ev.text.text) { res.evType = SE_CHAR; res.evValue = *ev.text.text; // if there are more characters hold onto them for later events. if (ev.text.text[1] != 0) s = strdup(ev.text.text+1); return res; } getNext = true; // try to get a decent event. break; case SDL_MOUSEMOTION: if (g_inputGrabbed) { res.evType = SE_MOUSE; res.evValue = ev.motion.xrel; res.evValue2 = ev.motion.yrel; mouse_polls.Append(mouse_poll_t(M_DELTAX, ev.motion.xrel)); mouse_polls.Append(mouse_poll_t(M_DELTAY, ev.motion.yrel)); return res; } getNext = true; break; case SDL_MOUSEWHEEL: if (g_inputGrabbed) { res.evType = SE_KEY; if (ev.wheel.y > 0) { res.evValue = K_MWHEELUP; mouse_polls.Append(mouse_poll_t(M_DELTAZ, 1)); } else { res.evValue = K_MWHEELDOWN; mouse_polls.Append(mouse_poll_t(M_DELTAZ, -1)); } res.evValue2 = 1; return res; } getNext = true; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if (g_inputGrabbed) { res.evType = SE_KEY; switch (ev.button.button) { case SDL_BUTTON_LEFT: res.evValue = K_MOUSE1; mouse_polls.Append(mouse_poll_t(M_ACTION1, ev.button.state == SDL_PRESSED ? 1 : 0)); break; case SDL_BUTTON_MIDDLE: res.evValue = K_MOUSE3; mouse_polls.Append(mouse_poll_t(M_ACTION3, ev.button.state == SDL_PRESSED ? 1 : 0)); break; case SDL_BUTTON_RIGHT: res.evValue = K_MOUSE2; mouse_polls.Append(mouse_poll_t(M_ACTION2, ev.button.state == SDL_PRESSED ? 1 : 0)); break; } res.evValue2 = ev.button.state == SDL_PRESSED ? 1 : 0; return res; } getNext = true; break; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: { sys_jEvents jEvent = mapjoybutton( (SDL_GameControllerButton)ev.cbutton.button); joystick_polls.Append(joystick_poll_t( jEvent, ev.cbutton.state == SDL_PRESSED ? 1 : 0) ); res.evType = SE_KEY; res.evValue2 = ev.cbutton.state == SDL_PRESSED ? 1 : 0; if ( ( jEvent >= J_ACTION1 ) && ( jEvent <= J_ACTION_MAX ) ) { res.evValue = K_JOY1 + ( jEvent - J_ACTION1 ); return res; } else if ( ( jEvent >= J_DPAD_UP ) && ( jEvent <= J_DPAD_RIGHT ) ) { res.evValue = K_JOY_DPAD_UP + ( jEvent - J_DPAD_UP ); return res; } getNext = true; // try to get a decent event. } break; case SDL_CONTROLLERAXISMOTION: { const int range = 16384; sys_jEvents jEvent = mapjoyaxis( (SDL_GameControllerAxis)ev.caxis.axis); joystick_polls.Append(joystick_poll_t( jEvent, ev.caxis.value) ); if ( jEvent == J_AXIS_LEFT_X ) { PushButton( K_JOY_STICK1_LEFT, ( ev.caxis.value < -range ) ); PushButton( K_JOY_STICK1_RIGHT, ( ev.caxis.value > range ) ); } else if ( jEvent == J_AXIS_LEFT_Y ) { PushButton( K_JOY_STICK1_UP, ( ev.caxis.value < -range ) ); PushButton( K_JOY_STICK1_DOWN, ( ev.caxis.value > range ) ); } else if ( jEvent == J_AXIS_RIGHT_X ) { PushButton( K_JOY_STICK2_LEFT, ( ev.caxis.value < -range ) ); PushButton( K_JOY_STICK2_RIGHT, ( ev.caxis.value > range ) ); } else if ( jEvent == J_AXIS_RIGHT_Y ) { PushButton( K_JOY_STICK2_UP, ( ev.caxis.value < -range ) ); PushButton( K_JOY_STICK2_DOWN, ( ev.caxis.value > range ) ); } else if ( jEvent == J_AXIS_LEFT_TRIG ) { PushButton( K_JOY_TRIGGER1, ( ev.caxis.value > range ) ); } else if ( jEvent == J_AXIS_RIGHT_TRIG ) { PushButton( K_JOY_TRIGGER2, ( ev.caxis.value > range ) ); } if ( jEvent >= J_AXIS_MIN && jEvent <= J_AXIS_MAX ) { int axis = jEvent - J_AXIS_MIN; int percent = ( ev.caxis.value * 16 ) / range; if ( joyAxis[axis] != percent ) { joyAxis[axis] = percent; res.evType = SE_JOYSTICK; res.evValue = axis; res.evValue2 = percent; return res; } } getNext = true; // try to get a decent event. } break; case SDL_JOYDEVICEADDED: SDL_GameControllerOpen( ev.jdevice.which ); // TODO: hot swapping maybe. //lbOnControllerPlugIn(event.jdevice.which); break; case SDL_JOYDEVICEREMOVED: // TODO: hot swapping maybe. //lbOnControllerUnPlug(event.jdevice.which); break; case SDL_QUIT: PushConsoleEvent("quit"); return res_none; case SDL_USEREVENT: switch (ev.user.code) { case SE_CONSOLE: res.evType = SE_CONSOLE; res.evPtrLength = (intptr_t)ev.user.data1; res.evPtr = ev.user.data2; return res; default: common->Warning("unknown user event %u", ev.user.code); getNext = true; // try to get a decent event. break; } default: getNext = true; // try to get a decent event. break; } } return res_none; } /* ================ Sys_ClearEvents ================ */ void Sys_ClearEvents() { SDL_Event ev; while (SDL_PollEvent(&ev)) ; kbd_polls.SetNum(0, false); mouse_polls.SetNum(0, false); joystick_polls.SetNum(0, false); memset( buttonStates, 0, sizeof( buttonStates ) ); memset( joyAxis, 0, sizeof( joyAxis ) ); event_overflow.SetNum(0, false); } /* ================ Sys_GenerateEvents ================ */ void Sys_GenerateEvents() { char *s = Sys_ConsoleInput(); if (s) PushConsoleEvent(s); SDL_PumpEvents(); } /* ================ Sys_PollKeyboardInputEvents ================ */ int Sys_PollKeyboardInputEvents() { return kbd_polls.Num(); } /* ================ Sys_ReturnKeyboardInputEvent ================ */ int Sys_ReturnKeyboardInputEvent(const int n, int &key, bool &state) { if (n >= kbd_polls.Num()) return 0; key = kbd_polls[n].key; state = kbd_polls[n].state; return 1; } /* ================ Sys_EndKeyboardInputEvents ================ */ void Sys_EndKeyboardInputEvents() { kbd_polls.SetNum(0, false); } /* ================ Sys_PollMouseInputEvents ================ */ int Sys_PollMouseInputEvents() { return mouse_polls.Num(); } /* ================ Sys_ReturnMouseInputEvent ================ */ int Sys_ReturnMouseInputEvent(const int n, int &action, int &value) { if (n >= mouse_polls.Num()) return 0; action = mouse_polls[n].action; value = mouse_polls[n].value; return 1; } /* ================ Sys_EndMouseInputEvents ================ */ void Sys_EndMouseInputEvents() { mouse_polls.SetNum(0, false); } /* ================ Joystick Input Methods ================ */ void Sys_SetRumble( int device, int low, int hi ) { // TODO: support multiple controllers. assert(device == 0); // TODO: support rumble maybe. assert(0); } int Sys_PollJoystickInputEvents( int deviceNum ) { // TODO: support multiple controllers. assert(deviceNum == 0); return joystick_polls.Num(); } int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) { if (n >= joystick_polls.Num()) return 0; action = joystick_polls[n].action; value = joystick_polls[n].value; return 1; } void Sys_EndJoystickInputEvents() { joystick_polls.SetNum(0, false); } #endif