/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. This file is part of Quake 2 source code. Quake 2 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 2 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 2 source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // New dedicated server and debug console #include "../qcommon/qcommon.h" #include "resource.h" #include "winquake.h" #ifdef NEW_DED_CONSOLE #define CONSOLE_WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_CLIPCHILDREN|WS_GROUP) #define CONSOLE_WINDOW_CLASS_NAME "KMQ2 Console" #ifdef ERASER_COMPAT_BUILD #define CONSOLE_WINDOW_NAME "KMQuake2 Console (Eraser Compatible)" #else // ERASER_COMPAT_BUILD #define CONSOLE_WINDOW_NAME "KMQuake2 Console" #endif // ERASER_COMPAT_BUILD #define MAX_OUTPUT 131072 // increased from 32768 #define MAX_INPUT 256 #define MAX_PRINTMSG 8192 typedef struct { int outLen; // To keep track of output buffer len char cmdBuffer[MAX_INPUT]; // Buffered input from dedicated console qboolean timerActive; // Timer is active (for fatal errors) qboolean flashColor; // If true, flash error message to red // Window stuff HWND hWnd; HWND hWndCopy; HWND hWndClear; HWND hWndQuit; HWND hWndOutput; HWND hWndInput; HWND hWndMsg; HWND hWndLogo; HBITMAP hLogoImage; HFONT hFont; HFONT hFontBold; HBRUSH hBrushMsg; HBRUSH hBrushOutput; HBRUSH hBrushInput; WNDPROC defOutputProc; WNDPROC defInputProc; } sysConsole_t; static sysConsole_t sys_console; /* ================= Sys_ConsoleInput ================= */ char *Sys_ConsoleInput (void) { static char buffer[MAX_INPUT]; if (!sys_console.cmdBuffer[0]) return NULL; strncpy(buffer, sys_console.cmdBuffer, sizeof(buffer)-1); sys_console.cmdBuffer[0] = 0; return buffer; } /* ================= Sys_ConsoleOutput ================= */ void Sys_ConsoleOutput (char *text) { char buffer[MAX_PRINTMSG]; int len = 0; // Change \n to \r\n so it displays properly in the edit box and // remove color escapes while (*text) { if (*text == '\n') { buffer[len++] = '\r'; buffer[len++] = '\n'; } else if (Q_IsColorString(text)) { text++; } else if (*text == '\02') { // skip STX char before map name text++; continue; } else buffer[len++] = *text; text++; } buffer[len] = 0; sys_console.outLen += len; if (sys_console.outLen >= MAX_OUTPUT) { SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1); sys_console.outLen = len; } // Scroll down before adding more text SendMessage(sys_console.hWndOutput, EM_LINESCROLL, 0, 0xFFFF); SendMessage(sys_console.hWndOutput, EM_SCROLLCARET, 0, 0); SendMessage(sys_console.hWndOutput, EM_REPLACESEL, FALSE, (LPARAM)buffer); // Scroll down SendMessage(sys_console.hWndOutput, EM_LINESCROLL, 0, 0xFFFF); SendMessage(sys_console.hWndOutput, EM_SCROLLCARET, 0, 0); // Fix text bleed InvalidateRect(sys_console.hWndOutput, NULL, TRUE); } /* ================= Sys_Error ================= */ void Sys_Error (const char *error, ...) { char string[1024]; va_list argPtr; MSG msg; // Make sure all subsystems are down CL_Shutdown (); Qcommon_Shutdown(); va_start(argPtr, error); Q_vsnprintf (string, sizeof(string), error, argPtr); va_end(argPtr); // Echo to console Sys_ConsoleOutput("\n"); Sys_ConsoleOutput(string); Sys_ConsoleOutput("\n"); // Display the message and set a timer so we can flash the text SetWindowText(sys_console.hWndMsg, string); SetTimer(sys_console.hWnd, 1, 1000, NULL); sys_console.timerActive = true; // Show/hide everything we need ShowWindow(sys_console.hWndMsg, SW_SHOW); ShowWindow(sys_console.hWndInput, SW_HIDE); Sys_ShowConsole(true); // Wait for the user to quit while (1) { while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, 0, 0)) Sys_Quit (); TranslateMessage(&msg); DispatchMessage(&msg); } // Don't hog the CPU Sleep(25); } } /* ================= Sys_ShowConsole ================= */ void Sys_ShowConsole (qboolean show) { if (!show) { ShowWindow(sys_console.hWnd, SW_HIDE); return; } ShowWindow(sys_console.hWnd, SW_SHOW); UpdateWindow(sys_console.hWnd); SetForegroundWindow(sys_console.hWnd); SetFocus(sys_console.hWnd); // Set the focus to the input edit box if possible SetFocus(sys_console.hWndInput); // Scroll down SendMessage(sys_console.hWndOutput, EM_LINESCROLL, 0, 0xFFFF); SendMessage(sys_console.hWndOutput, EM_SCROLLCARET, 0, 0); } /* ================= Sys_ConsoleProc ================= */ static LRESULT WINAPI Sys_ConsoleProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_ACTIVATE: if (LOWORD(wParam) != WA_INACTIVE) { SetFocus(sys_console.hWndInput); return 0; } break; case WM_CLOSE: Sys_Quit (); break; case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED) { if ((HWND)lParam == sys_console.hWndCopy) { SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1); SendMessage(sys_console.hWndOutput, WM_COPY, 0, 0); } else if ((HWND)lParam == sys_console.hWndClear) { SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1); SendMessage(sys_console.hWndOutput, WM_CLEAR, 0, 0); } else if ((HWND)lParam == sys_console.hWndQuit) Sys_Quit (); } else if (HIWORD(wParam) == EN_VSCROLL) InvalidateRect(sys_console.hWndOutput, NULL, TRUE); break; case WM_CTLCOLOREDIT: if ((HWND)lParam == sys_console.hWndOutput) { SetBkMode((HDC)wParam, TRANSPARENT); SetBkColor((HDC)wParam, RGB(39, 115, 102)); SetTextColor((HDC)wParam, RGB(255, 255, 255)); return (LRESULT)sys_console.hBrushOutput; } else if ((HWND)lParam == sys_console.hWndInput) { SetBkMode((HDC)wParam, TRANSPARENT); SetBkColor((HDC)wParam, RGB(255, 255, 255)); SetTextColor((HDC)wParam, RGB(0, 0, 0)); return (LRESULT)sys_console.hBrushInput; } break; case WM_CTLCOLORSTATIC: if ((HWND)lParam == sys_console.hWndMsg) { SetBkMode((HDC)wParam, TRANSPARENT); SetBkColor((HDC)wParam, RGB(127, 127, 127)); if (sys_console.flashColor) SetTextColor((HDC)wParam, RGB(255, 0, 0)); else SetTextColor((HDC)wParam, RGB(0, 0, 0)); return (LRESULT)sys_console.hBrushMsg; } break; case WM_TIMER: sys_console.flashColor = !sys_console.flashColor; InvalidateRect(sys_console.hWndMsg, NULL, TRUE); break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } /* ================= Sys_ConsoleEditProc ================= */ static LRESULT WINAPI Sys_ConsoleEditProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CHAR: if (hWnd == sys_console.hWndInput) { if (wParam == VK_RETURN) { if (GetWindowText(sys_console.hWndInput, sys_console.cmdBuffer, sizeof(sys_console.cmdBuffer))){ SetWindowText(sys_console.hWndInput, ""); Com_Printf("]%s\n", sys_console.cmdBuffer); } return 0; // Keep it from beeping } } else if (hWnd == sys_console.hWndOutput) return 0; // Read only break; case WM_VSCROLL: if (LOWORD(wParam) == SB_THUMBTRACK) return 0; break; } if (hWnd == sys_console.hWndOutput) return CallWindowProc(sys_console.defOutputProc, hWnd, uMsg, wParam, lParam); else if (hWnd == sys_console.hWndInput) return CallWindowProc(sys_console.defInputProc, hWnd, uMsg, wParam, lParam); return 0; } /* ================= Sys_ShutdownConsole ================= */ void Sys_ShutdownConsole (void) { if (sys_console.timerActive) { KillTimer(sys_console.hWnd, 1); } if (sys_console.hLogoImage) { DeleteObject(sys_console.hLogoImage); } if (sys_console.hBrushMsg) { DeleteObject(sys_console.hBrushMsg); } if (sys_console.hBrushOutput) { DeleteObject(sys_console.hBrushOutput); } if (sys_console.hBrushInput) { DeleteObject(sys_console.hBrushInput); } if (sys_console.hFont) { DeleteObject(sys_console.hFont); } if (sys_console.hFontBold) { DeleteObject(sys_console.hFontBold); } if (sys_console.defOutputProc) { SetWindowLongPtr(sys_console.hWndOutput, GWLP_WNDPROC, (LONG_PTR)sys_console.defOutputProc); } if (sys_console.defInputProc) { SetWindowLongPtr(sys_console.hWndInput, GWLP_WNDPROC, (LONG_PTR)sys_console.defInputProc); } ShowWindow(sys_console.hWnd, SW_HIDE); DestroyWindow(sys_console.hWnd); UnregisterClass(CONSOLE_WINDOW_CLASS_NAME, global_hInstance); } #define LOGO_OFFSET 125 /* ================= Sys_InitDedConsole ================= */ void Sys_InitDedConsole (void) { WNDCLASSEX wc; HDC hDC; RECT r; int x, y, w, h; // Center the window in the desktop hDC = GetDC(0); w = GetDeviceCaps(hDC, HORZRES); h = GetDeviceCaps(hDC, VERTRES); ReleaseDC(0, hDC); r.left = (w - 540) / 2; r.top = (h - 455) / 2; r.right = r.left + 540; r.bottom = r.top + 455+LOGO_OFFSET; AdjustWindowRect(&r, CONSOLE_WINDOW_STYLE, FALSE); x = r.left; y = r.top; w = r.right - r.left; h = r.bottom - r.top; wc.style = 0; wc.lpfnWndProc = (WNDPROC)Sys_ConsoleProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = global_hInstance; wc.hIcon = LoadIcon(global_hInstance, MAKEINTRESOURCE(IDI_ICON1)); wc.hIconSm = 0; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszMenuName = 0; wc.lpszClassName = CONSOLE_WINDOW_CLASS_NAME; wc.cbSize = sizeof(WNDCLASSEX); if (!RegisterClassEx(&wc)) { MessageBox(NULL, "Could not register console window class", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL); exit(0); } sys_console.hWnd = CreateWindowEx(0, CONSOLE_WINDOW_CLASS_NAME, CONSOLE_WINDOW_NAME, CONSOLE_WINDOW_STYLE, x, y, w, h, NULL, NULL, global_hInstance, NULL); if (!sys_console.hWnd) { UnregisterClass(CONSOLE_WINDOW_CLASS_NAME, global_hInstance); MessageBox(NULL, "Could not create console window", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL); exit(0); } sys_console.hWndMsg = CreateWindowEx(0, "STATIC", "", WS_CHILD | SS_SUNKEN, 5, 65, 530, 30, sys_console.hWnd, NULL, global_hInstance, NULL); // was 5, 5, 530, 30 sys_console.hWndOutput = CreateWindowEx(0, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_MULTILINE, 5, 40+LOGO_OFFSET, 530, 350, sys_console.hWnd, NULL, global_hInstance, NULL); sys_console.hWndInput = CreateWindowEx(0, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 5, 395+LOGO_OFFSET, 530, 20, sys_console.hWnd, NULL, global_hInstance, NULL); sys_console.hWndCopy = CreateWindowEx(0, "BUTTON", "Copy", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 5, 425+LOGO_OFFSET, 70, 25, sys_console.hWnd, NULL, global_hInstance, NULL); sys_console.hWndClear = CreateWindowEx(0, "BUTTON", "Clear", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 80, 425+LOGO_OFFSET, 70, 25, sys_console.hWnd, NULL, global_hInstance, NULL); sys_console.hWndQuit = CreateWindowEx(0, "BUTTON", "Quit", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 465, 425+LOGO_OFFSET, 70, 25, sys_console.hWnd, NULL, global_hInstance, NULL); // splash logo sys_console.hWndLogo = CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_CENTERIMAGE, 0, 0, 540, 160, sys_console.hWnd, NULL, global_hInstance, NULL); sys_console.hLogoImage = (HBITMAP)LoadImage(global_hInstance, MAKEINTRESOURCE(IDB_BITMAP2),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS); // Create and set fonts sys_console.hFont = CreateFont(14, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "Courier New"); sys_console.hFontBold = CreateFont(20, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "System"); SendMessage(sys_console.hWndMsg, WM_SETFONT, (WPARAM)sys_console.hFont, FALSE); SendMessage(sys_console.hWndOutput, WM_SETFONT, (WPARAM)sys_console.hFont, FALSE); SendMessage(sys_console.hWndInput, WM_SETFONT, (WPARAM)sys_console.hFont, FALSE); SendMessage(sys_console.hWndCopy, WM_SETFONT, (WPARAM)sys_console.hFontBold, FALSE); SendMessage(sys_console.hWndClear, WM_SETFONT, (WPARAM)sys_console.hFontBold, FALSE); SendMessage(sys_console.hWndQuit, WM_SETFONT, (WPARAM)sys_console.hFontBold, FALSE); SendMessage(sys_console.hWndLogo, STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)sys_console.hLogoImage); // Create brushes sys_console.hBrushMsg = CreateSolidBrush(RGB(127, 127, 127)); sys_console.hBrushOutput = CreateSolidBrush(RGB(39, 115, 102)); sys_console.hBrushInput = CreateSolidBrush(RGB(255, 255, 255)); // Subclass edit boxes sys_console.defOutputProc = (WNDPROC)SetWindowLongPtr(sys_console.hWndOutput, GWLP_WNDPROC, (LONG_PTR)Sys_ConsoleEditProc); sys_console.defInputProc = (WNDPROC)SetWindowLongPtr(sys_console.hWndInput, GWLP_WNDPROC, (LONG_PTR)Sys_ConsoleEditProc); // Set text limit for input edit box SendMessage(sys_console.hWndInput, EM_SETLIMITTEXT, (WPARAM)(MAX_INPUT-1), 0); // Hide it to start Sys_ShowConsole(true); } #endif // NEW_DED_CONSOLE