/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena 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 2 of the License, or (at your option) any later version. Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../client/client.h" #include "win_local.h" #define DEBUG_KEYPRESS 0 static cvar_t* vid_xpos; // X coordinate of window position static cvar_t* vid_ypos; // Y coordinate of window position static cvar_t* in_qwerty; // forces the QWERTY layout for key bindings cvar_t* r_fullscreen; static void WIN_AppActivate( BOOL fActive, BOOL fMinimized ) { const qbool active = fActive && !fMinimized; if ( r_fullscreen->integer ) SetWindowPos( g_wv.hWnd, active ? HWND_TOPMOST : HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); Key_ClearStates(); // we don't want to act like we're active if we're minimized g_wv.activeApp = active; } #if DEBUG_KEYPRESS static const char* GetKeyName( int q3key ) { switch ( q3key ) { case K_TAB: return "TAB"; case K_ENTER: return "ENTER"; case K_ESCAPE: return "ESCAPE"; case K_SPACE: return "SPACE"; case K_BACKSPACE: return "BACKSPACE"; case K_COMMAND: return "COMMAND"; case K_CAPSLOCK: return "CAPSLOCK"; case K_POWER: return "POWER"; case K_PAUSE: return "PAUSE"; case K_UPARROW: return "UPARROW"; case K_DOWNARROW: return "DOWNARROW"; case K_LEFTARROW: return "LEFTARROW"; case K_RIGHTARROW: return "RIGHTARROW"; case K_ALT: return "ALT"; case K_CTRL: return "CTRL"; case K_SHIFT: return "SHIFT"; case K_INS: return "INS"; case K_DEL: return "DEL"; case K_PGDN: return "PGDN"; case K_PGUP: return "PGUP"; case K_HOME: return "HOME"; case K_END: return "END"; case K_F1: return "F1"; case K_F2: return "F2"; case K_F3: return "F3"; case K_F4: return "F4"; case K_F5: return "F5"; case K_F6: return "F6"; case K_F7: return "F7"; case K_F8: return "F8"; case K_F9: return "F9"; case K_F10: return "F10"; case K_F11: return "F11"; case K_F12: return "F12"; case K_F13: return "F13"; case K_F14: return "F14"; case K_F15: return "F15"; case K_KP_HOME: return "KP_HOME"; case K_KP_UPARROW: return "KP_UPARROW"; case K_KP_PGUP: return "KP_PGUP"; case K_KP_LEFTARROW: return "KP_LEFTARROW"; case K_KP_5: return "KP_5"; case K_KP_RIGHTARROW: return "KP_RIGHTARROW"; case K_KP_END: return "KP_END"; case K_KP_DOWNARROW: return "KP_DOWNARROW"; case K_KP_PGDN: return "KP_PGDN"; case K_KP_ENTER: return "KP_ENTER"; case K_KP_INS: return "KP_INS"; case K_KP_DEL: return "KP_DEL"; case K_KP_SLASH: return "KP_SLASH"; case K_KP_MINUS: return "KP_MINUS"; case K_KP_PLUS: return "KP_PLUS"; case K_KP_NUMLOCK: return "KP_NUMLOCK"; case K_KP_STAR: return "KP_STAR"; case K_KP_EQUALS: return "KP_EQUALS"; case K_MOUSE1: return "MOUSE1"; case K_MOUSE2: return "MOUSE2"; case K_MOUSE3: return "MOUSE3"; case K_MOUSE4: return "MOUSE4"; case K_MOUSE5: return "MOUSE5"; case K_MWHEELDOWN: return "MWHEELDOWN"; case K_MWHEELUP: return "MWHEELUP"; case K_JOY1: return "JOY1"; case K_JOY2: return "JOY2"; case K_JOY3: return "JOY3"; case K_JOY4: return "JOY4"; case K_JOY5: return "JOY5"; case K_JOY6: return "JOY6"; case K_JOY7: return "JOY7"; case K_JOY8: return "JOY8"; case K_JOY9: return "JOY9"; case K_JOY10: return "JOY10"; case K_JOY11: return "JOY11"; case K_JOY12: return "JOY12"; case K_JOY13: return "JOY13"; case K_JOY14: return "JOY14"; case K_JOY15: return "JOY15"; case K_JOY16: return "JOY16"; case K_JOY17: return "JOY17"; case K_JOY18: return "JOY18"; case K_JOY19: return "JOY19"; case K_JOY20: return "JOY20"; case K_JOY21: return "JOY21"; case K_JOY22: return "JOY22"; case K_JOY23: return "JOY23"; case K_JOY24: return "JOY24"; case K_JOY25: return "JOY25"; case K_JOY26: return "JOY26"; case K_JOY27: return "JOY27"; case K_JOY28: return "JOY28"; case K_JOY29: return "JOY29"; case K_JOY30: return "JOY30"; case K_JOY31: return "JOY31"; case K_JOY32: return "JOY32"; case K_AUX1: return "AUX1"; case K_AUX2: return "AUX2"; case K_AUX3: return "AUX3"; case K_AUX4: return "AUX4"; case K_AUX5: return "AUX5"; case K_AUX6: return "AUX6"; case K_AUX7: return "AUX7"; case K_AUX8: return "AUX8"; case K_AUX9: return "AUX9"; case K_AUX10: return "AUX10"; case K_AUX11: return "AUX11"; case K_AUX12: return "AUX12"; case K_AUX13: return "AUX13"; case K_AUX14: return "AUX14"; case K_AUX15: return "AUX15"; case K_AUX16: return "AUX16"; case K_MOUSE6: return "MOUSE6"; case K_MOUSE7: return "MOUSE7"; case K_MOUSE8: return "MOUSE8"; case K_MOUSE9: return "MOUSE9"; case K_WIN: return "WIN"; case K_MENU: return "MENU"; case K_BACKSLASH: return "BACKSLASH"; case K_F16: return "F16"; case K_F17: return "F17"; case K_F18: return "F18"; case K_F19: return "F19"; case K_F20: return "F20"; case K_F21: return "F21"; case K_F22: return "F22"; case K_F23: return "F23"; case K_F24: return "F24"; default: return "???"; } } #endif /////////////////////////////////////////////////////////////// static const byte s_scantokey[128] = { // 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', '-', '=', K_BACKSPACE, 9, // 0 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', 13 , K_CTRL,'a', 's', // 1 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'' , '`', K_SHIFT,'\\', 'z', 'x', 'c', 'v', // 2 'b', 'n', 'm', ',', '.', '/', K_SHIFT,'*', K_ALT,' ', K_CAPSLOCK , K_F1, K_F2, K_F3, K_F4, K_F5, // 3 K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, 0 , K_HOME, K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5,K_RIGHTARROW, K_KP_PLUS,K_END, //4 K_DOWNARROW,K_PGDN,K_INS,K_DEL,0,0, 0, K_F11, K_F12,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 }; static byte s_winVKeyToQ3Key[256]; static qbool s_keyTableValid = qfalse; static void InitKeyMap() { if ( s_keyTableValid ) { return; } for ( int c = '0'; c <= '9'; c++ ) { s_winVKeyToQ3Key[c] = c; } for ( int c = 'A'; c <= 'Z'; c++ ) { s_winVKeyToQ3Key[c] = c + 32; } struct keyMap_t { byte winKey; byte quakeKey; }; #define KEY(WinKey, QuakeKey) { WinKey, QuakeKey } static const keyMap_t keys[] = { KEY(VK_TAB, K_TAB), KEY(VK_RETURN, K_ENTER), KEY(VK_ESCAPE, K_ESCAPE), KEY(VK_SPACE, K_SPACE), KEY(VK_BACK, K_BACKSPACE), KEY(VK_CAPITAL, K_CAPSLOCK), KEY(VK_PAUSE, K_PAUSE), KEY(VK_UP, K_UPARROW), KEY(VK_DOWN, K_DOWNARROW), KEY(VK_LEFT, K_LEFTARROW), KEY(VK_RIGHT, K_RIGHTARROW), KEY(VK_MENU, K_ALT), KEY(VK_CONTROL, K_CTRL), KEY(VK_SHIFT, K_SHIFT), KEY(VK_INSERT, K_INS), KEY(VK_DELETE, K_DEL), KEY(VK_NEXT, K_PGDN), KEY(VK_PRIOR, K_PGUP), KEY(VK_HOME, K_HOME), KEY(VK_END, K_END), KEY(VK_F1, K_F1), KEY(VK_F2, K_F2), KEY(VK_F3, K_F3), KEY(VK_F4, K_F4), KEY(VK_F5, K_F5), KEY(VK_F6, K_F6), KEY(VK_F7, K_F7), KEY(VK_F8, K_F8), KEY(VK_F9, K_F9), KEY(VK_F10, K_F10), KEY(VK_F11, K_F11), KEY(VK_F12, K_F12), KEY(VK_F13, K_F13), KEY(VK_F14, K_F14), KEY(VK_F15, K_F15), KEY(VK_NUMPAD7, K_KP_HOME), KEY(VK_NUMPAD8, K_KP_UPARROW), KEY(VK_NUMPAD9, K_KP_PGUP), KEY(VK_NUMPAD4, K_KP_LEFTARROW), KEY(VK_NUMPAD5, K_KP_5), KEY(VK_NUMPAD6, K_KP_RIGHTARROW), KEY(VK_NUMPAD1, K_KP_END), KEY(VK_NUMPAD2, K_KP_DOWNARROW), KEY(VK_NUMPAD3, K_KP_PGDN), KEY(VK_NUMPAD0, K_KP_INS), KEY(VK_NUMLOCK, K_KP_DEL), KEY(VK_DIVIDE, K_KP_SLASH), KEY(VK_SUBTRACT, K_KP_MINUS), KEY(VK_ADD, K_KP_PLUS), KEY(VK_DECIMAL, K_KP_NUMLOCK), KEY(VK_MULTIPLY, K_KP_STAR), KEY(VK_LBUTTON, K_MOUSE1), KEY(VK_RBUTTON, K_MOUSE2), KEY(VK_MBUTTON, K_MOUSE3), KEY(VK_XBUTTON1, K_MOUSE4), KEY(VK_XBUTTON2, K_MOUSE5), KEY(VK_F16, K_F16), KEY(VK_F17, K_F17), KEY(VK_F18, K_F18), KEY(VK_F19, K_F19), KEY(VK_F20, K_F20), KEY(VK_F21, K_F21), KEY(VK_F22, K_F22), KEY(VK_F23, K_F23), KEY(VK_F24, K_F24) #if 0 // not handled K_COMMAND K_POWER K_BACKSLASH K_MOUSE6 K_MOUSE7 K_MOUSE8 K_MOUSE9 K_KP_ENTER K_KP_EQUALS K_WIN K_MENU #endif }; #undef KEY for ( int i = 0; i < ARRAY_LEN(keys); i++ ) { const byte winKey = keys[i].winKey; const byte quakeKey = keys[i].quakeKey; s_winVKeyToQ3Key[winKey] = quakeKey; } s_keyTableValid = qtrue; } /* ======= MapKey Map from windows to quake keynums ======= */ static int MapKey( int wParam, int lParam ) { // this is needed for the in_qwerty 1 case // the K_F13 to K_F24 values are *not* contiguous for mod compatibility reasons switch ( wParam ) { case VK_F13: return K_F13; case VK_F14: return K_F14; case VK_F15: return K_F15; case VK_F16: return K_F16; case VK_F17: return K_F17; case VK_F18: return K_F18; case VK_F19: return K_F19; case VK_F20: return K_F20; case VK_F21: return K_F21; case VK_F22: return K_F22; case VK_F23: return K_F23; case VK_F24: return K_F24; default: break; } #if DEBUG_KEYPRESS char keyName[64]; if ( GetKeyNameTextA((LONG)lParam, keyName, (int)sizeof(keyName)) > 0 ) { Sys_DebugPrintf( "Key: Win32 %d %s\n", wParam, keyName ); } #endif Q_assert( in_qwerty != NULL ); const qbool qwerty = in_qwerty != NULL && in_qwerty->integer != 0; if ( !qwerty && wParam < ARRAY_LEN(s_winVKeyToQ3Key) ) { const int q3key = s_winVKeyToQ3Key[wParam]; if ( q3key > 0 ) { #if DEBUG_KEYPRESS Sys_DebugPrintf( "Mapped key: Win32 %d -> Q3 %d (%c, %s)\n", wParam, q3key, (char)q3key, GetKeyName(q3key) ); #endif return q3key; } } const int scanCode = ( lParam >> 16 ) & 255; if ( scanCode > 127 ) return 0; // why? const qbool isExtended = (lParam & ( 1 << 24 )) != 0; const int result = s_scantokey[scanCode]; if ( !isExtended ) { switch ( result ) { case K_HOME: return K_KP_HOME; case K_UPARROW: return K_KP_UPARROW; case K_PGUP: return K_KP_PGUP; case K_LEFTARROW: return K_KP_LEFTARROW; case K_RIGHTARROW: return K_KP_RIGHTARROW; case K_END: return K_KP_END; case K_DOWNARROW: return K_KP_DOWNARROW; case K_PGDN: return K_KP_PGDN; case K_INS: return K_KP_INS; case K_DEL: return K_KP_DEL; case '*': return K_KP_STAR; case 0x00: return K_BACKSLASH; default: return result; } } else { switch ( result ) { case K_PAUSE: return K_KP_NUMLOCK; case 0x0D: return K_KP_ENTER; case 0x2F: return K_KP_SLASH; case 0xAF: return K_KP_PLUS; } return result; } } /* ==================== MainWndProc main window procedure ==================== */ LRESULT CALLBACK MainWndProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static qbool draggingWindow = qfalse; switch (uMsg) { case WM_CREATE: g_wv.hWnd = hWnd; vid_xpos = Cvar_Get( "vid_xpos", "3", CVAR_ARCHIVE ); vid_ypos = Cvar_Get( "vid_ypos", "22", CVAR_ARCHIVE ); r_fullscreen = Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); Cvar_Get( "r_monitor", "0", CVAR_ARCHIVE | CVAR_LATCH ); // 1-based monitor index, 0 means primary in_qwerty = Cvar_Get( "in_qwerty", "1", CVAR_ARCHIVE ); Cvar_SetRange( "in_qwerty", CVART_BOOL, NULL, NULL ); Cvar_SetHelp( "in_qwerty", "forces the QWERTY layout for key bindings" ); WIN_RegisterLastValidHotKey(); InitKeyMap(); break; case WM_DESTROY: WIN_UnregisterHotKey(); g_wv.hWnd = NULL; break; case WM_CLOSE: Cbuf_AddText( "quit\n" ); break; case WM_ACTIVATE: WIN_AppActivate( (LOWORD(wParam) != WA_INACTIVE), !!(BOOL)HIWORD(wParam) ); WIN_S_Mute( !g_wv.activeApp ); break; case WM_MOVING: draggingWindow = qtrue; break; case WM_MOVE: { WIN_UpdateMonitorIndexFromMainWindow(); if ( !r_fullscreen->integer ) { RECT r; r.left = 0; r.top = 0; r.right = 1; r.bottom = 1; AdjustWindowRect( &r, GetWindowLong( hWnd, GWL_STYLE ), FALSE ); const RECT& monRect = g_wv.monitorRects[g_wv.monitor]; const int x = (int)(short)LOWORD( lParam ); const int y = (int)(short)HIWORD( lParam ); Cvar_SetValue( "vid_xpos", x + r.left - monRect.left ); Cvar_SetValue( "vid_ypos", y + r.top - monRect.top ); vid_xpos->modified = qfalse; vid_ypos->modified = qfalse; } draggingWindow = qfalse; } break; case WM_SIZE: if ( wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED ) { WIN_UpdateMonitorIndexFromMainWindow(); // note that WM_SIZE can be called with no actual size change const int w = (int)LOWORD( lParam ); const int h = (int)HIWORD( lParam ); if ( g_wv.duringCreateWindow ) WIN_UpdateResolution( w, h ); } break; case WM_WINDOWPOSCHANGED: { const int prevMon = g_wv.monitor; WIN_UpdateMonitorIndexFromMainWindow(); const int currMon = g_wv.monitor; if ( !g_wv.duringCreateWindow && !draggingWindow && currMon != prevMon ) Cbuf_AddText( "vid_restart\n" ); } break; case WM_SYSCOMMAND: if ( wParam == SC_SCREENSAVE ) return 0; break; case WM_SYSKEYDOWN: if ( wParam == VK_RETURN ) { if ( r_fullscreen ) { Cvar_SetValue( "r_fullscreen", !r_fullscreen->integer ); Cbuf_AddText( "vid_restart\n" ); } return 0; } // fall through case WM_KEYDOWN: WIN_QueEvent( g_wv.sysMsgTime, SE_KEY, MapKey( wParam, lParam ), qtrue, 0, NULL ); break; case WM_SYSKEYUP: case WM_KEYUP: WIN_QueEvent( g_wv.sysMsgTime, SE_KEY, MapKey( wParam, lParam ), qfalse, 0, NULL ); break; case WM_CHAR: { const char scanCode = (char)( ( lParam >> 16 ) & 0xFF ); if ( scanCode != 0x29 ) // never send an event for the console key ('~' or '`') WIN_QueEvent( g_wv.sysMsgTime, SE_CHAR, wParam, 0, 0, NULL ); } break; case WM_HOTKEY: if ( g_wv.minimizeHotKeyValid && (int)wParam == g_wv.minimizeHotKeyId ) { if ( g_wv.activeApp && !CL_VideoRecording() ) { ShowWindow( hWnd, SW_MINIMIZE ); } else { ShowWindow( hWnd, SW_RESTORE ); SetForegroundWindow( hWnd ); SetFocus( hWnd ); } } break; case WM_SETFOCUS: if ( g_wv.cdsDevModeValid ) // is there a valid mode to restore? { WIN_SetGameDisplaySettings(); if ( g_wv.cdsDevModeValid ) // was the mode successfully restored? { const RECT& rect = g_wv.monitorRects[g_wv.monitor]; const DEVMODE& dm = g_wv.cdsDevMode; SetWindowPos( hWnd, NULL, (int)rect.left, (int)rect.top, (int)dm.dmPelsWidth, (int)dm.dmPelsHeight, SWP_NOZORDER ); } } g_wv.activeApp = (qbool)!IsIconic( hWnd ); g_wv.forceUnmute = qfalse; break; case WM_KILLFOCUS: g_wv.activeApp = qfalse; if ( g_wv.cdsDevModeValid ) WIN_SetDesktopDisplaySettings(); break; default: // this is complicated because Win32 seems to pack multiple mouse events into // one update sometimes, so we always check all states and look for events if ( uMsg == WM_INPUT || (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) || uMsg == WM_MOUSEWHEEL ) if ( IN_ProcessMessage(uMsg, wParam, lParam) ) return 0; break; } return DefWindowProc( hWnd, uMsg, wParam, lParam ); }