/* 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. */ #include "win_local.h" #ifdef _WIN32_WCE #include #endif /* _WIN32_WCE */ qboolean win_initialized; HWND hWndMain; PRIVATE DWORD lastMsgTime; static qboolean s_alttab_disabled; PRIVATE HHOOK kbdHook; static cvar_t *vid_fullscreen; static cvar_t *vid_xpos; static cvar_t *vid_ypos; static cvar_t *sys_winnt; static cvar_t *win_hwnd; static cvar_t *win_noalttab; static cvar_t *win_disablewinkey; void GLimp_AppActivate( qboolean active ); // ============================================================================ #ifdef _WIN32_WCE BOOL AdjustWindowRect( LPRECT lpRect, DWORD dwStyle, BOOL bMenu ) { return AdjustWindowRectEx( lpRect, dwStyle, bMenu, 0 ); } #endif /* ================= Win_CreateWindow ================= */ HWND Win_CreateWindow( int width, int height, qboolean fullscreen ) { #ifdef _WIN32_WCE WNDCLASS wc; #else WNDCLASSEX wc; #endif RECT r; HWND hWnd; int stylebits; int x, y, w, h; int exstyle; RECT screen; /* Register the frame class */ memset( &wc, 0, sizeof( wc ) ); #ifndef _WIN32_WCE wc.cbSize = sizeof( wc ); #endif wc.lpfnWndProc = ( WNDPROC )Win_MainWndProc; wc.hInstance = hGlobalInstance; #ifdef _WIN32_WCE #else wc.hIcon = LoadImage( hGlobalInstance, MAKEINTRESOURCE( IDI_APP ), IMAGE_ICON, 32, 32, LR_CREATEDIBSECTION ); wc.hIconSm = LoadImage( hGlobalInstance, MAKEINTRESOURCE( IDI_APP ), IMAGE_ICON, 16, 16, LR_CREATEDIBSECTION ); #endif wc.hCursor = LoadCursor ( NULL, IDC_ARROW ); wc.hbrBackground = ( void * )COLOR_GRAYTEXT; wc.lpszClassName = WINDOW_CLASS_NAME; #ifdef _WIN32_WCE if( !RegisterClass( &wc ) ) { #else if( !RegisterClassEx( &wc ) ) { #endif Com_Error( ERR_FATAL, "Couldn't register window class" ); } if( fullscreen ) { exstyle = WS_EX_TOPMOST; stylebits = WS_POPUP|WS_VISIBLE; } else { exstyle = 0; stylebits = WINDOW_STYLE; } r.left = 0; r.top = 0; r.right = width; r.bottom = height; AdjustWindowRect( &r, stylebits, FALSE ); w = r.right - r.left; h = r.bottom - r.top; if( fullscreen ) { /* postion on primary monitor */ x = 0; y = 0; } else { /* get virtual screen dimensions */ if( GetSystemMetrics( SM_CMONITORS ) > 1 ) { screen.left = GetSystemMetrics( SM_XVIRTUALSCREEN ); screen.top = GetSystemMetrics( SM_YVIRTUALSCREEN ); screen.right = screen.left + GetSystemMetrics( SM_CXVIRTUALSCREEN ); screen.bottom = screen.top + GetSystemMetrics( SM_CYVIRTUALSCREEN ); } else { screen.left = 0; screen.top = 0; screen.right = GetSystemMetrics( SM_CXSCREEN ); screen.bottom = GetSystemMetrics( SM_CYSCREEN ); } x = Cvar_VariableInteger( "vid_xpos" ); y = Cvar_VariableInteger( "vid_ypos" ); /* clip to virtual screen */ if( x + w > screen.right ) { x = screen.right - w; } if( y + h > screen.bottom ) { y = screen.bottom - h; } if( x < screen.left ) { x = screen.left; } if( y < screen.top ) { y = screen.top; } } hWnd = CreateWindowEx( exstyle, WINDOW_CLASS_NAME, #ifdef _WIN32_WCE __TEXT( "q2pro" ), #else Cvar_VariableString( "vid_window" ), #endif stylebits, x, y, w, h, NULL, NULL, hGlobalInstance, NULL ); if( !hWnd ) { Com_Error( ERR_FATAL, "Couldn't create window" ); } ShowWindow( hWnd, SW_SHOW ); SetForegroundWindow( hWnd ); SetFocus( hWnd ); return hWnd; } /* ================= Win_DestroyWindow ================= */ void Win_DestroyWindow( HWND hWnd ) { ShowWindow( hWnd, SW_SHOWNORMAL ); // prevents leaving empty slots in the taskbar if( !DestroyWindow( hWnd ) ) { Com_Error( ERR_FATAL, "Couldn't destroy window" ); } if( !UnregisterClass( WINDOW_CLASS_NAME, hGlobalInstance ) ) { Com_Error( ERR_FATAL, "Couldn't unregister window class" ); } } /* ================= Win_DisableAltTab ================= */ static void Win_DisableAltTab( void ) { #ifndef _WIN32_WCE if( s_alttab_disabled ) return; if( !sys_winnt->integer ) { BOOL old; SystemParametersInfo( SPI_SETSCREENSAVERRUNNING, 1, &old, 0 ); } else { RegisterHotKey( 0, 0, MOD_ALT, VK_TAB ); RegisterHotKey( 0, 1, MOD_ALT, VK_RETURN ); } s_alttab_disabled = qtrue; #endif /* !_WIN32_WCE */ } /* ================= Win_EnableAltTab ================= */ static void Win_EnableAltTab( void ) { #ifndef _WIN32_WCE if( s_alttab_disabled ) { if( !sys_winnt->integer ) { BOOL old; SystemParametersInfo( SPI_SETSCREENSAVERRUNNING, 0, &old, 0 ); } else { UnregisterHotKey( 0, 0 ); UnregisterHotKey( 0, 1 ); } s_alttab_disabled = qfalse; } #endif /* !_WIN32_WCE */ } /* ============ Win_NotAltTab_OnChange ============ */ static void Win_NotAltTab_OnChange( cvar_t *self, void *arg ) { if( self->integer ) { Win_DisableAltTab(); } else { Win_EnableAltTab(); } } /* ============ Win_VidPos_OnChange ============ */ static void Win_VidPos_OnChange( cvar_t *self, void *arg ) { if( vid_fullscreen->integer ) { return; } SetWindowPos( hWndMain, HWND_TOPMOST, vid_xpos->integer, vid_ypos->integer, 0, 0, SWP_NOSIZE ); } /* ================= Win_AppActivate ================= */ static void Win_AppActivate( WPARAM wParam ) { qboolean active, minimized; // KJB: Watch this for problems in fullscreen modes with Alt-tabbing. minimized = active = qfalse; if( HIWORD( wParam ) ) { // we don't want to act like we're active if we're minimized minimized = qtrue; } else if( LOWORD( wParam ) ) { active = qtrue; } CL_AppActivate( active ); if( win_noalttab->integer ) { if( !active ) { Win_EnableAltTab(); } else { Win_DisableAltTab(); } } if( vid_fullscreen->integer ) { if( active ) { ShowWindow( hWndMain, SW_RESTORE ); } else { ShowWindow( hWndMain, SW_MINIMIZE ); } } #ifndef _WIN32_WCE GLimp_AppActivate( active ); #endif if( active ) { SetForegroundWindow( hWndMain ); } } static LRESULT CALLBACK LowLevelKeyboardProc( int nCode, WPARAM wParam, LPARAM lParam ) { PKBDLLHOOKSTRUCT kb = ( PKBDLLHOOKSTRUCT )lParam; uint32 key; if( nCode != HC_ACTION ) { goto ignore; } switch( kb->vkCode ) { case VK_LWIN: key = K_LWINKEY; break; case VK_RWIN: key = K_RWINKEY; break; default: goto ignore; } switch( wParam ) { case WM_KEYDOWN: Key_Event( key, qtrue, kb->time ); return TRUE; case WM_KEYUP: Key_Event( key, qfalse, kb->time ); return TRUE; default: break; } ignore: return CallNextHookEx( NULL, nCode, wParam, lParam ); } static void DisableWinKey_OnChange( cvar_t *self, void *arg ) { if( self->integer ) { if( !sys_winnt->integer ) { Com_Printf( "Low-level keyboard hook requires Windows NT\n" ); Cvar_Set( "win_disablewinkey", "0" ); return; } kbdHook = SetWindowsHookEx( WH_KEYBOARD_LL, LowLevelKeyboardProc, hGlobalInstance, 0 ); if( !kbdHook ) { Com_EPrintf( "Couldn't set low-level keyboard hook, error %#lX\n", GetLastError() ); Cvar_Set( "win_disablewinkey", "0" ); } } else { if( kbdHook ) { UnhookWindowsHookEx( kbdHook ); kbdHook = NULL; } } } #ifdef _WIN32_WCE static byte scantokey[256] = { 0, '\x001', '\x002', '\x003', '\x004', '\x005', '\x006', '\x007', K_BACKSPACE, '\x009', '\x00a', '\x00b', '\x00c', K_ENTER, '\x00e', '\x00f', K_SHIFT, '\x011', '\x012', '\x013', '\x014', '\x015', '\x016', '\x017', '\x018', '\x019', '\x01a', '\x01b', '\x01c', '\x01d', '\x01e', '\x01f', '\x020', '\x021', '\x022', '\x023', '\x024', K_LEFTARROW, K_UPARROW, K_RIGHTARROW, K_DOWNARROW, '\x029', '\x02a', '\x02b', '\x02c', '\x02d', '\x02e', '\x02f', ')', '!', '@', '#', '$', '%', '^', '&', '*', '(', '\x03a', '\x03b', '\x03c', '\x03d', '\x03e', '\x03f', '\x040', '\x041', '\x042', '\x043', '\x044', '\x045', '\x046', '\x047', '\x048', '\x049', '\x04a', '\x04b', '\x04c', '\x04d', '\x04e', '\x04f', '\x050', '\x051', '\x052', '\x053', '\x054', '\x055', '\x056', '\x057', '\x058', '\x059', '\x05a', 0/*'\x05b'*/, '\x05c', '\x05d', '\x05e', '\x05f', '\x060', '\x061', '\x062', '\x063', '\x064', '\x065', '\x066', '\x067', '\x068', '\x069', '\x06a', '\x06b', '\x06c', '\x06d', '\x06e', '\x06f', '\x070', '\x071', '\x072', '\x073', '\x074', '\x075', '\x076', '\x077', '\x078', '\x079', '\x07a', '\x07b', '\x07c', '\x07d', '\x07e', '\x07f', '\x080', '\x081', '\x082', '\x083', '\x084', '\x085', K_JOY1, '\x087', '\x088', '\x089', '\x08a', '\x08b', '\x08c', '\x08d', '\x08e', '\x08f', '\x090', '\x091', '\x092', '\x093', '\x094', '\x095', '\x096', '\x097', '\x098', '\x099', '\x09a', '\x09b', '\x09c', '\x09d', '\x09e', '\x09f', '\x0a0', '\x0a1', '\x0a2', '\x0a3', '\x0a4', '\x0a5', '\x0a6', '\x0a7', '\x0a8', '\x0a9', '\x0aa', '\x0ab', '\x0ac', '\x0ad', '\x0ae', '\x0af', '\x0b0', '\x0b1', '\x0b2', '\x0b3', '\x0b4', '\x0b5', '\x0b6', '\x0b7', '\x0b8', '\x0b9', ':', '+', '<', '_', '>', '?', '~', K_AUX1, K_AUX2, K_AUX3, K_AUX4, K_AUX5, '\x0c6', '\x0c7', '\x0c8', '\x0c9', '\x0ca', '\x0cb', '\x0cc', '\x0cd', '\x0ce', '\x0cf', '\x0d0', '\x0d1', '\x0d2', '\x0d3', '\x0d4', '\x0d5', '\x0d6', '\x0d7', '\x0d8', '\x0d9', '\x0da', '{', '|', '}', '"', '\x0df', '\x0e0', '\x0e1', '\x0e2', '\x0e3', '\x0e4', '\x0e5', '\x0e6', '\x0e7', '\x0e8', '\x0e9', '\x0ea', '\x0eb', '\x0ec', '\x0ed', '\x0ee', '\x0ef', '\x0f0', '\x0f1', '\x0f2', '\x0f3', '\x0f4', '\x0f5', '\x0f6', '\x0f7', '\x0f8', '\x0f9', '\x0fa', '\x0fb', '\x0fc', '\x0fd', '\x0fe', '\x0ff'}; static int Win_MapKey( WPARAM wParam, LPARAM lParam ) { if( wParam < 0 || wParam > 255 ) return 0; return scantokey[wParam]; } #else static byte 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, K_SCROLLOCK, 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, K_LWINKEY, K_RWINKEY, K_MENU, 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 }; /* ======= Win_KeyEvent Map from windows to quake keynums ======= */ static void Win_KeyEvent( WPARAM wParam, LPARAM lParam, qboolean down ) { uint32 result; uint32 scancode = ( lParam >> 16 ) & 255; qboolean is_extended = qfalse; if( scancode > 127 ) { return; } if( lParam & ( 1 << 24 ) ) { is_extended = qtrue; } result = scantokey[scancode]; if( !result ) { Com_DPrintf( "Win_KeyEvent: unknown scancode %u\n", scancode ); return; } if( !is_extended ) { switch( result ) { case K_HOME: result = K_KP_HOME; break; case K_UPARROW: result = K_KP_UPARROW; break; case K_PGUP: result = K_KP_PGUP; break; case K_LEFTARROW: result = K_KP_LEFTARROW; break; case K_RIGHTARROW: result = K_KP_RIGHTARROW; break; case K_END: result = K_KP_END; break; case K_DOWNARROW: result = K_KP_DOWNARROW; break; case K_PGDN: result = K_KP_PGDN; break; case K_INS: result = K_KP_INS; break; case K_DEL: result = K_KP_DEL; break; } } else { switch( result ) { case 0x0D: result = K_KP_ENTER; break; case 0x2F: result = K_KP_SLASH; break; case 0xAF: result = K_KP_PLUS; break; } } Key_Event( result, down, lastMsgTime ); } #endif /* ==================== Win_MainWndProc main window procedure ==================== */ LONG WINAPI Win_MainWndProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch( uMsg ) { case WM_MOUSEWHEEL: { UINT lines; int key; // this chunk of code theoretically only works under NT4 and Win98 // since this message doesn't exist under Win95 if( ( short )HIWORD( wParam ) > 0 ) { key = K_MWHEELUP; } else { key = K_MWHEELDOWN; } if( keys.GetDest() & KEY_CONSOLE ) { SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 0, &lines, 0 ); if( !lines ) { lines = 1; } else if( lines > 6 ) { lines = 6; } } else { lines = 1; } do { Key_Event( key, qtrue, lastMsgTime ); Key_Event( key, qfalse, lastMsgTime ); } while( --lines ); } break; // 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 case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MOUSEMOVE: case WM_XBUTTONDOWN: case WM_XBUTTONUP: { int temp = 0; if( wParam & MK_LBUTTON ) temp |= 1; if( wParam & MK_RBUTTON ) temp |= 2; if( wParam & MK_MBUTTON ) temp |= 4; if( wParam & MK_XBUTTON1 ) temp |= 8; if( wParam & MK_XBUTTON2 ) temp |= 16; Win_SendMouseButtonEvents( temp, lastMsgTime ); } break; case WM_HOTKEY: return FALSE; case WM_CREATE: hWndMain = hWnd; Cvar_SetIntegerHex( "win_hwnd", ( uint32 )hWnd ); break; case WM_CLOSE: PostQuitMessage( 0 ); return FALSE; case WM_DESTROY: hWndMain = NULL; Cvar_SetInteger( "win_hwnd", 0 ); break; case WM_ACTIVATE: Win_AppActivate( wParam ); break; case WM_SIZE: if( wParam == SIZE_MAXIMIZED ) { if( !vid_fullscreen->integer ) { Cvar_SetInteger( "vid_fullscreen", 1 ); Cbuf_AddText( "vid_restart\n" ); } } return FALSE; case WM_MOVE: { short xPos, yPos; RECT r; int style; if( vid_fullscreen->integer ) { break; } xPos = ( signed short )LOWORD( lParam ); // horizontal position yPos = ( signed short )HIWORD( lParam ); // vertical position r.left = 0; r.top = 0; r.right = 1; r.bottom = 1; style = GetWindowLong( hWnd, GWL_STYLE ); AdjustWindowRect( &r, style, FALSE ); Cvar_SetInteger( "vid_xpos", xPos + r.left ); Cvar_SetInteger( "vid_ypos", yPos + r.top ); } break; #ifdef _WIN32_WCE case WM_SETTINGCHANGE: if( wParam == SPI_SETSIPINFO ) { SIPINFO si; /* TODO */ memset( &si, 0, sizeof( si ) ); si.cbSize = sizeof( si ); SHSipInfo( SPI_GETSIPINFO, 0, &si, 0 ); if( si.fdwFlags & SIPF_ON ) { Con_SetMaxHeight( 1.0f - 80.0f / 296 ); } else { Con_SetMaxHeight( 1.0f ); } } break; #else /* _WIN32_WCE */ case WM_SYSCOMMAND: if( wParam == SC_SCREENSAVE ) { return FALSE; } break; #endif /* !_WIN32_WCE */ case WM_KEYDOWN: case WM_SYSKEYDOWN: Win_KeyEvent( wParam, lParam, qtrue ); break; case WM_CHAR: #ifdef UNICODE { char tinystr[2]; wchar_t wstr[2]; wstr[0] = wParam; wstr[1] = 0; WideCharToMultiByte( CP_ACP, 0, wstr, -1, tinystr, 2, NULL, NULL ); Key_CharEvent( tinystr[0] ); } #elif (defined USE_CHAR_EVENTS) Key_CharEvent( wParam ); #endif break; case WM_SYSKEYUP: case WM_KEYUP: Win_KeyEvent( wParam, lParam, qfalse ); break; default: break; } // pass all unhandled messages to DefWindowProc return DefWindowProc( hWnd, uMsg, wParam, lParam ); } /* ============ Win_PumpEvents ============ */ void Video_PumpEvents( void ) { MSG msg; while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { if( msg.message == WM_QUIT ) { Com_Quit(); break; } lastMsgTime = msg.time; TranslateMessage( &msg ); DispatchMessage( &msg ); } } /* ============ Win_Init ============ */ void Win_Init( void ) { vid_fullscreen = Cvar_Get( "vid_fullscreen", "", CVAR_USER_CREATED ); vid_xpos = Cvar_Get( "vid_xpos", "0", CVAR_ARCHIVE ); vid_xpos->changedFunc = Win_VidPos_OnChange; vid_ypos = Cvar_Get( "vid_ypos", "0", CVAR_ARCHIVE ); vid_ypos->changedFunc = Win_VidPos_OnChange; sys_winnt = Cvar_Get( "sys_winnt", "0", CVAR_ROM|CVAR_USER_CREATED ); win_hwnd = Cvar_Get( "win_hwnd", "0", CVAR_ROM ); win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE ); win_noalttab->changedFunc = Win_NotAltTab_OnChange; win_disablewinkey = Cvar_Get( "win_disablewinkey", "0", CVAR_ARCHIVE ); win_disablewinkey->changedFunc = DisableWinKey_OnChange; DisableWinKey_OnChange( win_disablewinkey, NULL ); win_initialized = qtrue; } /* ============ Win_Shutdown ============ */ void Win_Shutdown( void ) { if( kbdHook ) { UnhookWindowsHookEx( kbdHook ); kbdHook = NULL; } win_initialized = qfalse; }