/* =========================================================================== 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. =========================================================================== */ #include #include "sys/platform.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" #if !SDL_VERSION_ATLEAST(2, 0, 0) #define SDL_Keycode SDLKey #define SDLK_APPLICATION SDLK_COMPOSE #define SDLK_SCROLLLOCK SDLK_SCROLLOCK #define SDLK_LGUI SDLK_LSUPER #define SDLK_RGUI SDLK_RSUPER #define SDLK_KP_0 SDLK_KP0 #define SDLK_KP_1 SDLK_KP1 #define SDLK_KP_2 SDLK_KP2 #define SDLK_KP_3 SDLK_KP3 #define SDLK_KP_4 SDLK_KP4 #define SDLK_KP_5 SDLK_KP5 #define SDLK_KP_6 SDLK_KP6 #define SDLK_KP_7 SDLK_KP7 #define SDLK_KP_8 SDLK_KP8 #define SDLK_KP_9 SDLK_KP9 #define SDLK_NUMLOCKCLEAR SDLK_NUMLOCK #define SDLK_PRINTSCREEN SDLK_PRINT #endif 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; } }; static idList kbd_polls; static idList mouse_polls; 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 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 ); #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableUNICODE( 1 ); SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL ); #endif in_kbd.SetModified(); } /* ================= Sys_ShutdownInput ================= */ void Sys_ShutdownInput() { kbd_polls.Clear(); mouse_polls.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 | GRAB_SETSTATE; else flags = GRAB_SETSTATE; GLimp_GrabInput( flags ); } /* ================ 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 }; #if SDL_VERSION_ATLEAST(2, 0, 0) 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; } #endif static byte c = 0; if( c ) { res.evType = SE_CHAR; res.evValue = c; c = 0; return res; } if( SDL_PollEvent( &ev ) ) { switch( ev.type ) { #if SDL_VERSION_ATLEAST(2, 0, 0) 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 ); GLimp_GrabInput( GRAB_ENABLE | GRAB_REENABLE | GRAB_HIDECURSOR ); break; case SDL_WINDOWEVENT_FOCUS_LOST: GLimp_GrabInput( 0 ); break; } return res_none; #else case SDL_ACTIVEEVENT: { int flags = 0; if( ev.active.gain ) { flags = GRAB_ENABLE | GRAB_REENABLE | GRAB_HIDECURSOR; // 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... SDLMod currentmod = SDL_GetModState(); int newmod = KMOD_NONE; if( currentmod & KMOD_CAPS ) // preserve capslock newmod |= KMOD_CAPS; SDL_SetModState( ( SDLMod )newmod ); } GLimp_GrabInput( flags ); } return res_none; case SDL_VIDEOEXPOSE: return res_none; #endif 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 ) { unsigned char c; // check if its an unmapped console key if( ev.key.keysym.unicode == ( c = Sys_GetConsoleKey( false ) ) ) { key = c; } else if( ev.key.keysym.unicode == ( c = Sys_GetConsoleKey( true ) ) ) { key = c; } else { if( ev.type == SDL_KEYDOWN ) common->Warning( "unmapped SDL key %d (0x%x)", ev.key.keysym.sym, ev.key.keysym.unicode ); return res_none; } } 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 SDL_VERSION_ATLEAST(2, 0, 0) if( key == K_BACKSPACE && ev.key.state == SDL_PRESSED ) c = key; #else if( ev.key.state == SDL_PRESSED && ( ev.key.keysym.unicode & 0xff00 ) == 0 ) c = ev.key.keysym.unicode & 0xff; #endif return res; #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_TEXTINPUT: if( ev.text.text && *ev.text.text ) { if( !ev.text.text[1] ) c = *ev.text.text; else s = strdup( ev.text.text ); } return res_none; #endif case SDL_MOUSEMOTION: 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; #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_MOUSEWHEEL: 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; #endif case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: 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; #if !SDL_VERSION_ATLEAST(2, 0, 0) case SDL_BUTTON_WHEELUP: res.evValue = K_MWHEELUP; if( ev.button.state == SDL_PRESSED ) mouse_polls.Append( mouse_poll_t( M_DELTAZ, 1 ) ); break; case SDL_BUTTON_WHEELDOWN: res.evValue = K_MWHEELDOWN; if( ev.button.state == SDL_PRESSED ) mouse_polls.Append( mouse_poll_t( M_DELTAZ, -1 ) ); break; #endif } res.evValue2 = ev.button.state == SDL_PRESSED ? 1 : 0; return res; 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 ); return res_none; } default: common->Warning( "unknown event %u", ev.type ); return res_none; } } 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 ); } /* ================ 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 ); }