forked from Mirrors/openclonk
417 lines
10 KiB
C++
417 lines
10 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2005-2007 Sven Eberhardt
|
|
* Copyright (c) 2005, 2007, 2009 Peter Wortmann
|
|
* Copyright (c) 2005-2006, 2008-2009 Günther Brammer
|
|
* Copyright (c) 2006 Armin Burgmeier
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
|
|
/* A wrapper class to OS dependent event and window interfaces, WIN32 version */
|
|
|
|
#include "C4Include.h"
|
|
#include <Standard.h>
|
|
#include <StdRegistry.h>
|
|
#ifdef USE_GL
|
|
#include <StdGL.h>
|
|
#endif
|
|
#ifdef USE_DIRECTX
|
|
#include <StdD3D.h>
|
|
#endif
|
|
#include <StdWindow.h>
|
|
#include <mmsystem.h>
|
|
#include <stdio.h>
|
|
#include <io.h>
|
|
#include <ctype.h>
|
|
#include <conio.h>
|
|
|
|
// multimon.h comes with DirectX, some people don't have DirectX.
|
|
#ifdef HAVE_MULTIMON_H
|
|
|
|
// Lets try this unconditionally so that older windowses get the benefit
|
|
// even if the engine was compiled with a newer sdk. Or something.
|
|
#define COMPILE_MULTIMON_STUBS
|
|
#include <multimon.h>
|
|
|
|
#endif
|
|
|
|
#include "resource.h"
|
|
#include "C4Version.h"
|
|
|
|
#define C4FullScreenClassName "C4FullScreen"
|
|
LRESULT APIENTRY FullScreenWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
CStdWindow::CStdWindow (): Active(false), hWindow(0) {
|
|
}
|
|
CStdWindow::~CStdWindow () {
|
|
}
|
|
|
|
BOOL CStdWindow::RegisterWindowClass(HINSTANCE hInst) {
|
|
WNDCLASSEX WndClass;
|
|
WndClass.cbSize=sizeof(WNDCLASSEX);
|
|
WndClass.style = CS_DBLCLKS;
|
|
WndClass.lpfnWndProc = FullScreenWinProc;
|
|
WndClass.cbClsExtra = 0;
|
|
WndClass.cbWndExtra = 0;
|
|
WndClass.hInstance = hInst;
|
|
WndClass.hCursor = NULL;
|
|
WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
|
|
WndClass.lpszMenuName = NULL;
|
|
WndClass.lpszClassName = C4FullScreenClassName;
|
|
WndClass.hIcon = LoadIcon (hInst, MAKEINTRESOURCE (IDI_00_C4X) );
|
|
WndClass.hIconSm = LoadIcon (hInst, MAKEINTRESOURCE (IDI_00_C4X) );
|
|
return RegisterClassEx(&WndClass);
|
|
}
|
|
|
|
CStdWindow * CStdWindow::Init(CStdApp * pApp) {
|
|
Active = true;
|
|
|
|
// Register window class
|
|
if (!RegisterWindowClass(pApp->hInstance)) return NULL;
|
|
|
|
// Create window
|
|
hWindow = CreateWindowEx (
|
|
0,
|
|
C4FullScreenClassName,
|
|
C4ENGINENAME,
|
|
WS_POPUP,
|
|
CW_USEDEFAULT,CW_USEDEFAULT,0,0,
|
|
NULL,NULL,pApp->hInstance,NULL);
|
|
|
|
#ifndef USE_CONSOLE
|
|
// Show & focus
|
|
ShowWindow(hWindow,SW_SHOWNORMAL);
|
|
SetFocus(hWindow);
|
|
ShowCursor(false);
|
|
#endif
|
|
|
|
return this;
|
|
}
|
|
|
|
void CStdWindow::Clear() {
|
|
// Destroy window
|
|
if (hWindow) DestroyWindow(hWindow);
|
|
hWindow = NULL;
|
|
}
|
|
|
|
bool CStdWindow::StorePosition(const char *szWindowName, const char *szSubKey, bool fStoreSize) {
|
|
return StoreWindowPosition(hWindow, szWindowName, szSubKey, fStoreSize) != 0;
|
|
}
|
|
|
|
bool CStdWindow::RestorePosition(const char *szWindowName, const char *szSubKey, bool fHidden) {
|
|
if (!RestoreWindowPosition(hWindow, szWindowName, szSubKey, fHidden))
|
|
ShowWindow(hWindow,SW_SHOWNORMAL);
|
|
return true;
|
|
}
|
|
|
|
void CStdWindow::SetTitle(const char *szToTitle) {
|
|
if (hWindow) SetWindowText(hWindow, szToTitle ? szToTitle : "");
|
|
}
|
|
|
|
bool CStdWindow::GetSize(RECT * pRect) {
|
|
if (!(hWindow && GetClientRect(hWindow,pRect))) return false;
|
|
return true;
|
|
}
|
|
|
|
void CStdWindow::SetSize(unsigned int cx, unsigned int cy) {
|
|
// resize
|
|
if (hWindow) {
|
|
::SetWindowPos(hWindow, NULL, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
void CStdWindow::FlashWindow()
|
|
{
|
|
// please activate me!
|
|
if (hWindow)
|
|
::FlashWindow(hWindow, FLASHW_ALL | FLASHW_TIMERNOFG);
|
|
}
|
|
|
|
/* CStdTimerProc */
|
|
|
|
int CStdMultimediaTimerProc::iTimePeriod = 0;
|
|
|
|
CStdMultimediaTimerProc::CStdMultimediaTimerProc(uint32_t iDelay) :
|
|
idCriticalTimer(0),
|
|
uCriticalTimerDelay(28),
|
|
uCriticalTimerResolution(5),
|
|
Event(true)
|
|
{
|
|
|
|
if(!iTimePeriod)
|
|
{
|
|
// Get resolution caps
|
|
TIMECAPS tc;
|
|
timeGetDevCaps(&tc, sizeof(tc));
|
|
// Establish minimum resolution
|
|
uCriticalTimerResolution = BoundBy(uCriticalTimerResolution, tc.wPeriodMin, tc.wPeriodMax);
|
|
timeBeginPeriod(uCriticalTimerResolution);
|
|
}
|
|
iTimePeriod++;
|
|
|
|
SetDelay(iDelay);
|
|
|
|
}
|
|
|
|
CStdMultimediaTimerProc::~CStdMultimediaTimerProc()
|
|
{
|
|
if (idCriticalTimer)
|
|
{
|
|
timeKillEvent(idCriticalTimer);
|
|
idCriticalTimer = 0;
|
|
|
|
iTimePeriod--;
|
|
if (!iTimePeriod)
|
|
timeEndPeriod(uCriticalTimerResolution);
|
|
}
|
|
}
|
|
|
|
void CStdMultimediaTimerProc::SetDelay(uint32_t iDelay)
|
|
{
|
|
|
|
// Kill old timer (of any)
|
|
if(idCriticalTimer)
|
|
timeKillEvent(idCriticalTimer);
|
|
|
|
// Set critical timer
|
|
idCriticalTimer=timeSetEvent(
|
|
uCriticalTimerDelay,uCriticalTimerResolution,
|
|
(LPTIMECALLBACK) Event.GetEvent(),0,TIME_PERIODIC | TIME_CALLBACK_EVENT_SET);
|
|
|
|
}
|
|
|
|
bool CStdMultimediaTimerProc::CheckAndReset()
|
|
{
|
|
if(!Check()) return false;
|
|
Event.Reset();
|
|
return true;
|
|
}
|
|
|
|
/* CStdMessageProc */
|
|
|
|
bool CStdMessageProc::Execute(int iTimeout, pollfd *)
|
|
{
|
|
// Peek messages
|
|
MSG msg;
|
|
while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
|
|
{
|
|
// quit?
|
|
if(msg.message == WM_QUIT)
|
|
{
|
|
pApp->fQuitMsgReceived = true;
|
|
return false;
|
|
}
|
|
// Dialog message transfer
|
|
if (!pApp->DialogMessageHandling(&msg))
|
|
{
|
|
TranslateMessage(&msg); DispatchMessage(&msg);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* CStdApp */
|
|
|
|
CStdApp::CStdApp() :
|
|
Active(false), hInstance(NULL), fQuitMsgReceived(false),
|
|
fDspModeSet(false)
|
|
{
|
|
ZeroMemory(&pfd, sizeof(pfd)); pfd.nSize = sizeof(pfd);
|
|
ZeroMemory(&dspMode, sizeof(dspMode)); dspMode.dmSize = sizeof(dspMode);
|
|
ZeroMemory(&OldDspMode, sizeof(OldDspMode)); OldDspMode.dmSize = sizeof(OldDspMode);
|
|
hMainThread = NULL;
|
|
#ifdef _WIN32
|
|
MessageProc.SetApp(this);
|
|
Add(&MessageProc);
|
|
#endif
|
|
}
|
|
|
|
CStdApp::~CStdApp()
|
|
{
|
|
}
|
|
|
|
const char *LoadResStr(const char *id);
|
|
|
|
bool CStdApp::Init(HINSTANCE hInst, int nCmdShow, char *szCmdLine) {
|
|
// Set instance vars
|
|
hInstance = hInst;
|
|
this->szCmdLine = szCmdLine;
|
|
hMainThread = ::GetCurrentThread();
|
|
// Custom initialization
|
|
return DoInit ();
|
|
}
|
|
|
|
void CStdApp::Clear() {
|
|
hMainThread = NULL;
|
|
}
|
|
|
|
void CStdApp::Quit() {
|
|
PostQuitMessage(0);
|
|
}
|
|
|
|
bool CStdApp::FlushMessages() {
|
|
|
|
// Always fail after quit message
|
|
if(fQuitMsgReceived)
|
|
return false;
|
|
|
|
return MessageProc.Execute(0);
|
|
}
|
|
|
|
int GLMonitorInfoEnumCount;
|
|
|
|
BOOL CALLBACK GLMonitorInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
|
|
{
|
|
// get to indexed monitor
|
|
if (GLMonitorInfoEnumCount--) return true;
|
|
// store it
|
|
CStdApp *pApp = (CStdApp *) dwData;
|
|
pApp->hMon = hMonitor;
|
|
pApp->MonitorRect = *lprcMonitor;
|
|
return true;
|
|
}
|
|
|
|
bool CStdApp::GetIndexedDisplayMode(int32_t iIndex, int32_t *piXRes, int32_t *piYRes, int32_t *piBitDepth, uint32_t iMonitor)
|
|
{
|
|
// prepare search struct
|
|
DEVMODE dmode;
|
|
ZeroMemory(&dmode, sizeof(dmode)); dmode.dmSize = sizeof(dmode);
|
|
StdStrBuf Mon;
|
|
if (iMonitor)
|
|
Mon.Format("\\\\.\\Display%d", iMonitor+1);
|
|
// check if indexed mode exists
|
|
if (!EnumDisplaySettings(Mon.getData(), iIndex, &dmode)) return false;
|
|
// mode exists; return it
|
|
if (piXRes) *piXRes = dmode.dmPelsWidth;
|
|
if (piYRes) *piYRes = dmode.dmPelsHeight;
|
|
if (piBitDepth) *piBitDepth = dmode.dmBitsPerPel;
|
|
return true;
|
|
}
|
|
|
|
void CStdApp::RestoreVideoMode()
|
|
{
|
|
}
|
|
|
|
bool CStdApp::SetVideoMode(unsigned int iXRes, unsigned int iYRes, unsigned int iColorDepth, unsigned int iMonitor, bool fFullScreen)
|
|
{
|
|
#ifdef USE_DIRECTX
|
|
if (pD3D)
|
|
{
|
|
if(!pD3D->SetVideoMode(iXRes, iYRes, iColorDepth, iMonitor, fFullScreen))
|
|
return false;
|
|
OnResolutionChanged(iXRes, iYRes);
|
|
return true;
|
|
}
|
|
#endif
|
|
bool fFound=false;
|
|
DEVMODE dmode;
|
|
// if a monitor is given, search on that instead
|
|
// get monitor infos
|
|
GLMonitorInfoEnumCount = iMonitor;
|
|
hMon = NULL;
|
|
EnumDisplayMonitors(NULL, NULL, GLMonitorInfoEnumProc, (LPARAM) this);
|
|
// no monitor assigned?
|
|
if (!hMon)
|
|
{
|
|
// Okay for primary; then just use a default
|
|
if (!iMonitor)
|
|
{
|
|
MonitorRect.left = MonitorRect.top = 0;
|
|
MonitorRect.right = iXRes; MonitorRect.bottom = iYRes;
|
|
}
|
|
else return false;
|
|
}
|
|
StdStrBuf Mon;
|
|
if (iMonitor)
|
|
Mon.Format("\\\\.\\Display%d", iMonitor+1);
|
|
// enumerate modes
|
|
int i=0;
|
|
ZeroMemory(&dmode, sizeof(dmode)); dmode.dmSize = sizeof(dmode);
|
|
while (EnumDisplaySettings(Mon.getData(), i++, &dmode))
|
|
// size and bit depth is OK?
|
|
if (dmode.dmPelsWidth==iXRes && dmode.dmPelsHeight==iYRes && dmode.dmBitsPerPel==iColorDepth)
|
|
{
|
|
// compare with found one
|
|
if (fFound)
|
|
// try getting a mode that is close to 85Hz, rather than taking the one with highest refresh rate
|
|
// (which may set absurd modes on some devices)
|
|
if (Abs<int>(85-dmode.dmDisplayFrequency)>Abs<int>(85-dspMode.dmDisplayFrequency))
|
|
// the previous one was better
|
|
continue;
|
|
// choose this one
|
|
fFound=true;
|
|
dspMode=dmode;
|
|
}
|
|
|
|
// change mode
|
|
if (fFullScreen == fDspModeSet) return true;
|
|
#ifdef _DEBUG
|
|
SetWindowPos(pWindow->hWindow, HWND_TOP, MonitorRect.left, MonitorRect.top, dspMode.dmPelsWidth, dspMode.dmPelsHeight, SWP_NOOWNERZORDER | SWP_SHOWWINDOW);
|
|
SetWindowLong(pWindow->hWindow, GWL_STYLE, (WS_VISIBLE | WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX));
|
|
return true;
|
|
#else
|
|
if (!fFullScreen)
|
|
{
|
|
ChangeDisplaySettings(NULL, CDS_RESET);
|
|
fDspModeSet = false;
|
|
OnResolutionChanged(iXRes, iYRes);
|
|
return true;
|
|
}
|
|
// save original display mode
|
|
fDspModeSet=true;
|
|
// if a monitor is given, use that
|
|
if (iMonitor)
|
|
{
|
|
dspMode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
|
if (ChangeDisplaySettingsEx(Mon.getData(), &dspMode, NULL, CDS_FULLSCREEN, NULL) != DISP_CHANGE_SUCCESSFUL)
|
|
{
|
|
fDspModeSet = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ChangeDisplaySettings(&dspMode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
|
|
{
|
|
fDspModeSet = false;
|
|
}
|
|
}
|
|
SetWindowPos(pWindow->hWindow, 0, MonitorRect.left, MonitorRect.top, dspMode.dmPelsWidth, dspMode.dmPelsHeight, 0);
|
|
if (fDspModeSet)
|
|
OnResolutionChanged(iXRes, iYRes);
|
|
return fDspModeSet;
|
|
#endif
|
|
}
|
|
|
|
bool CStdApp::ReadStdInCommand()
|
|
{
|
|
while(_kbhit())
|
|
{
|
|
// Surely not the most efficient way to do it, but we won't have to read much data anyway.
|
|
char c = getch();
|
|
if(c == '\r') {
|
|
if(!CmdBuf.isNull()) {
|
|
OnCommand(CmdBuf.getData()); CmdBuf.Clear();
|
|
}
|
|
} else if(isprint((unsigned char)c))
|
|
CmdBuf.AppendChar(c);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CStdApp::MessageDialog(const char * message)
|
|
{
|
|
MessageBox(0, message, C4ENGINECAPTION, MB_ICONERROR);
|
|
}
|