Browse Source

Extracted window management from CMT to new class

Ivan Savenko 2 years ago
parent
commit
c688411bab

+ 18 - 490
client/CMT.cpp

@@ -7,49 +7,34 @@
  * Full text of license available in license.txt file, in main folder
  * Full text of license available in license.txt file, in main folder
  *
  *
  */
  */
+
 // CMT.cpp : Defines the entry point for the console application.
 // CMT.cpp : Defines the entry point for the console application.
-//
 #include "StdInc.h"
 #include "StdInc.h"
+#include "CMT.h"
 
 
 #include "CGameInfo.h"
 #include "CGameInfo.h"
 #include "mainmenu/CMainMenu.h"
 #include "mainmenu/CMainMenu.h"
-#include "lobby/CSelectionBase.h"
-#include "windows/CCastleInterface.h"
+#include "mainmenu/CPrologEpilogVideo.h"
 #include "gui/CursorHandler.h"
 #include "gui/CursorHandler.h"
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
 #include "CVideoHandler.h"
 #include "CVideoHandler.h"
 #include "CMusicHandler.h"
 #include "CMusicHandler.h"
-#include "Client.h"
 #include "gui/CGuiHandler.h"
 #include "gui/CGuiHandler.h"
 #include "CServerHandler.h"
 #include "CServerHandler.h"
 #include "gui/NotificationHandler.h"
 #include "gui/NotificationHandler.h"
 #include "ClientCommandManager.h"
 #include "ClientCommandManager.h"
 #include "windows/CMessage.h"
 #include "windows/CMessage.h"
-#include "renderSDL/SDL_Extensions.h"
+#include "renderSDL/WindowHandler.h"
 
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/FileStream.h"
 #include "../lib/CConsoleHandler.h"
 #include "../lib/CConsoleHandler.h"
-#include "../lib/CGameState.h"
-#include "../lib/CBuildingHandler.h"
-#include "../CCallback.h"
-#include "../lib/CHeroHandler.h"
-#include "../lib/spells/CSpellHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/serializer/BinaryDeserializer.h"
-#include "../lib/serializer/BinarySerializer.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/NetPacks.h"
-#include "../lib/CModHandler.h"
-#include "../lib/CTownHandler.h"
+#include "../lib/mapping/CCampaignHandler.h"
+
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
-#include "../lib/CPlayerState.h"
-#include "../lib/serializer/Connection.h"
 
 
-#include <boost/asio.hpp>
 #include <boost/program_options.hpp>
 #include <boost/program_options.hpp>
-
-#include "mainmenu/CPrologEpilogVideo.h"
 #include <vstd/StringUtils.h>
 #include <vstd/StringUtils.h>
 #include <SDL.h>
 #include <SDL.h>
 
 
@@ -60,8 +45,6 @@
 #include "../lib/CAndroidVMHelper.h"
 #include "../lib/CAndroidVMHelper.h"
 #endif
 #endif
 
 
-#include "CMT.h"
-
 #if __MINGW32__
 #if __MINGW32__
 #undef main
 #undef main
 #endif
 #endif
@@ -70,11 +53,8 @@ namespace po = boost::program_options;
 namespace po_style = boost::program_options::command_line_style;
 namespace po_style = boost::program_options::command_line_style;
 namespace bfs = boost::filesystem;
 namespace bfs = boost::filesystem;
 
 
-std::string NAME_AFFIX = "client";
-std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 CGuiHandler GH;
 CGuiHandler GH;
 
 
-int preferredDriverIndex = -1;
 SDL_Window * mainWindow = nullptr;
 SDL_Window * mainWindow = nullptr;
 SDL_Renderer * mainRenderer = nullptr;
 SDL_Renderer * mainRenderer = nullptr;
 SDL_Texture * screenTexture = nullptr;
 SDL_Texture * screenTexture = nullptr;
@@ -95,19 +75,11 @@ static po::variables_map vm;
 #ifndef VCMI_IOS
 #ifndef VCMI_IOS
 void processCommand(const std::string &message);
 void processCommand(const std::string &message);
 #endif
 #endif
-static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
 void playIntro();
 void playIntro();
 static void mainLoop();
 static void mainLoop();
 
 
 static CBasicLogConfigurator *logConfig;
 static CBasicLogConfigurator *logConfig;
 
 
-#ifndef VCMI_WINDOWS
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#include <getopt.h>
-#endif
-
 void init()
 void init()
 {
 {
 	CStopWatch tmh;
 	CStopWatch tmh;
@@ -139,17 +111,6 @@ static void prog_help(const po::options_description &opts)
 	std::cout << opts;
 	std::cout << opts;
 }
 }
 
 
-static void SDLLogCallback(void*           userdata,
-						   int             category,
-						   SDL_LogPriority priority,
-						   const char*     message)
-{
-	//todo: convert SDL log priority to vcmi log priority
-	//todo: make separate log domain for SDL
-
-	logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
-}
-
 #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
 #if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
 int wmain(int argc, wchar_t* argv[])
 int wmain(int argc, wchar_t* argv[])
 #elif defined(VCMI_MOBILE)
 #elif defined(VCMI_MOBILE)
@@ -255,7 +216,7 @@ int main(int argc, char * argv[])
 	const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
 	const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
 	logConfig = new CBasicLogConfigurator(logPath, console);
 	logConfig = new CBasicLogConfigurator(logPath, console);
 	logConfig->configureDefault();
 	logConfig->configureDefault();
-	logGlobal->info(NAME);
+	logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION);
 	logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
 	logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
 	logGlobal->info("The log file will be saved to %s", logPath);
 	logGlobal->info("The log file will be saved to %s", logPath);
 
 
@@ -340,68 +301,9 @@ int main(int argc, char * argv[])
 
 
 	srand ( (unsigned int)time(nullptr) );
 	srand ( (unsigned int)time(nullptr) );
 
 
-
-	const JsonNode& video = settings["video"];
-	const JsonNode& res = video["screenRes"];
-
-	//something is really wrong...
-	if (res["width"].Float() < 100 || res["height"].Float() < 100)
-	{
-		logGlobal->error("Fatal error: failed to load settings!");
-		logGlobal->error("Possible reasons:");
-		logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath());
-		logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath());
-		logGlobal->error("VCMI will now exit...");
-		exit(EXIT_FAILURE);
-	}
-
 	if(!settings["session"]["headless"].Bool())
 	if(!settings["session"]["headless"].Bool())
-	{
-		if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
-		{
-			logGlobal->error("Something was wrong: %s", SDL_GetError());
-			exit(-1);
-		}
-
-		#ifdef VCMI_ANDROID
-		// manually setting egl pixel format, as a possible solution for sdl2<->android problem
-		// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
-		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
-		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
-		#endif // VCMI_ANDROID
-
-		//(!)init here AFTER SDL_Init() while using SDL for FPS management
 		GH.init();
 		GH.init();
 
 
-		SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
-
-		int driversCount = SDL_GetNumRenderDrivers();
-		std::string preferredDriverName = video["driver"].String();
-
-		logGlobal->info("Found %d render drivers", driversCount);
-
-		for(int it = 0; it < driversCount; it++)
-		{
-			SDL_RendererInfo info;
-			SDL_GetRenderDriverInfo(it,&info);
-
-			std::string driverName(info.name);
-
-			if(!preferredDriverName.empty() && driverName == preferredDriverName)
-			{
-				preferredDriverIndex = it;
-				logGlobal->info("\t%s (active)", driverName);
-			}
-			else
-				logGlobal->info("\t%s", driverName);
-		}
-
-		setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float());
-		logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff());
-	}
-
 	CCS = new CClientState();
 	CCS = new CClientState();
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
 	CSH = new CServerHandler();
 	CSH = new CServerHandler();
@@ -487,7 +389,6 @@ int main(int argc, char * argv[])
 		CCS->curh->show();
 		CCS->curh->show();
 	}
 	}
 
 
-
 	logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
 	logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
 
 
 	session["autoSkip"].Bool()  = vm.count("autoSkip");
 	session["autoSkip"].Bool()  = vm.count("autoSkip");
@@ -577,361 +478,6 @@ void playIntro()
 	}
 	}
 }
 }
 
 
-#if !defined(VCMI_MOBILE)
-static bool checkVideoMode(int monitorIndex, int w, int h)
-{
-	//we only check that our desired window size fits on screen
-	SDL_DisplayMode mode;
-
-	if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode))
-	{
-		logGlobal->error("SDL_GetDesktopDisplayMode failed");
-		logGlobal->error(SDL_GetError());
-		return false;
-	}
-
-	logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h);
-
-	if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h))
-	{
-		return true;
-	}
-
-	return false;
-}
-#endif
-
-static void cleanupRenderer()
-{
-	screenBuf = nullptr; //it`s a link - just nullify
-
-	if(nullptr != screen2)
-	{
-		SDL_FreeSurface(screen2);
-		screen2 = nullptr;
-	}
-
-	if(nullptr != screen)
-	{
-		SDL_FreeSurface(screen);
-		screen = nullptr;
-	}
-
-	if(nullptr != screenTexture)
-	{
-		SDL_DestroyTexture(screenTexture);
-		screenTexture = nullptr;
-	}
-}
-
-static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex)
-{
-	// VCMI will only work with 2 or 4 bytes per pixel
-	vstd::amax(bpp, 16);
-	vstd::amin(bpp, 32);
-	if(bpp>16)
-		bpp = 32;
-
-	if(displayIndex < 0)
-	{
-		if (mainWindow != nullptr)
-			displayIndex = SDL_GetWindowDisplayIndex(mainWindow);
-		if (displayIndex < 0)
-			displayIndex = 0;
-	}
-
-#if defined(VCMI_MOBILE)
-	SDL_GetWindowSize(mainWindow, &w, &h);
-#else
-	if(!checkVideoMode(displayIndex, w, h))
-	{
-		logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
-	}
-#endif
-
-	bool bufOnScreen = (screenBuf == screen);
-	bool realFullscreen = settings["video"]["realFullscreen"].Bool();
-
-	/* match best rendering resolution */
-	int renderWidth = 0, renderHeight = 0;
-	auto aspectRatio = (float)w / (float)h;
-	auto minDiff = 10.f;
-
-	// TODO: CONFIGURABLE ADVMAP
-	static const std::vector<Point> supportedResolutions = {
-		{ 1280, 720 }
-	};
-
-
-	for (const auto& pair : supportedResolutions)
-	{
-		int pWidth = pair.x;
-		int pHeight = pair.y;
-
-		/* filter out resolution which is larger than window */
-		if (pWidth > w || pHeight > h)
-		{
-			continue;
-		}
-		auto ratio = (float)pWidth / (float)pHeight;
-		auto diff = fabs(aspectRatio - ratio);
-		/* select closest aspect ratio */
-		if (diff < minDiff)
-		{
-			renderWidth = pWidth;
-			renderHeight = pHeight;
-			minDiff = diff;
-		}
-		/* select largest resolution meets prior conditions.
-		 * since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */
-		else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth)
-		{
-			renderWidth = pWidth;
-			renderHeight = pHeight;
-		}
-	}
-	if (renderWidth == 0)
-	{
-		// no matching resolution for upscaling - complain & fallback to default resolution.
-		logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h);
-		Settings newRes = settings.write["video"]["screenRes"];
-
-		w = supportedResolutions.front().x;
-		h = supportedResolutions.front().y;
-
-		newRes["width"].Float() = w;
-		newRes["height"].Float() = h;
-
-		logGlobal->error("Falling back to %dx%d", w, h);
-		renderWidth = w;
-		renderHeight = h;
-	}
-	else
-	{
-		logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight);
-	}
-
-	cleanupRenderer();
-
-	if(nullptr == mainWindow)
-	{
-#if defined(VCMI_MOBILE)
-		auto createWindow = [displayIndex](uint32_t extraFlags) -> bool {
-			mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags);
-			return mainWindow != nullptr;
-		};
-
-# ifdef VCMI_IOS
-		SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
-		SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
-		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-
-		uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
-		if(!createWindow(windowFlags | SDL_WINDOW_METAL))
-		{
-			logGlobal->warn("Metal unavailable, using OpenGLES");
-			createWindow(windowFlags);
-		}
-# else
-		createWindow(0);
-# endif // VCMI_IOS
-
-		// SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate
-		// That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate
-
-		SDL_Rect screenRect;
-
-		if(SDL_GetDisplayBounds(0, &screenRect) == 0)
-		{
-			const auto screenWidth = screenRect.w;
-			const auto screenHeight = screenRect.h;
-
-			const auto aspect = static_cast<double>(screenWidth) / screenHeight;
-
-			logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect);
-
-			if((double)w / aspect > (double)h)
-			{
-				h = (int)round((double)w / aspect);
-			}
-			else
-			{
-				w = (int)round((double)h * aspect);
-			}
-
-			logGlobal->info("Changing logical screen size to %dx%d", w, h);
-		}
-		else
-		{
-			logGlobal->error("Can't fix aspect ratio for screen");
-		}
-#else
-		if(fullscreen)
-		{
-			if(realFullscreen)
-				mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN);
-			else //in windowed full-screen mode use desktop resolution
-				mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
-			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-		}
-		else
-		{
-			mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
-		}
-#endif // defined(VCMI_MOBILE)
-
-		if(nullptr == mainWindow)
-		{
-			throw std::runtime_error("Unable to create window\n");
-		}
-
-		//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
-		mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
-
-		if(nullptr == mainRenderer)
-		{
-			throw std::runtime_error("Unable to create renderer\n");
-		}
-
-
-		SDL_RendererInfo info;
-		SDL_GetRendererInfo(mainRenderer, &info);
-		logGlobal->info("Created renderer %s", info.name);
-
-	}
-	else
-	{
-#if !defined(VCMI_MOBILE)
-
-		if(fullscreen)
-		{
-			if(realFullscreen)
-			{
-				SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
-
-				SDL_DisplayMode mode;
-				SDL_GetDesktopDisplayMode(displayIndex, &mode);
-				mode.w = renderWidth;
-				mode.h = renderHeight;
-
-				SDL_SetWindowDisplayMode(mainWindow, &mode);
-			}
-			else
-			{
-				SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
-			}
-
-			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
-
-			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
-		}
-		else
-		{
-			SDL_SetWindowFullscreen(mainWindow, 0);
-			SDL_SetWindowSize(mainWindow, w, h);
-			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
-		}
-#endif
-	}
-
-	if(!(fullscreen && realFullscreen))
-	{
-		SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight);
-
-//following line is bugged not only on android, do not re-enable without checking
-//#ifndef VCMI_ANDROID
-//		// on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
-//		SDL_RenderSetViewport(mainRenderer, nullptr);
-//#endif
-
-	}
-
-
-	#ifdef VCMI_ENDIAN_BIG
-		int bmask = 0xff000000;
-		int gmask = 0x00ff0000;
-		int rmask = 0x0000ff00;
-		int amask = 0x000000ff;
-	#else
-		int bmask = 0x000000ff;
-		int gmask = 0x0000ff00;
-		int rmask = 0x00ff0000;
-		int amask = 0xFF000000;
-	#endif
-
-	screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask);
-	if(nullptr == screen)
-	{
-		logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError());
-		throw std::runtime_error("Unable to create surface");
-	}
-	//No blending for screen itself. Required for proper cursor rendering.
-	SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
-
-	screenTexture = SDL_CreateTexture(mainRenderer,
-											SDL_PIXELFORMAT_ARGB8888,
-											SDL_TEXTUREACCESS_STREAMING,
-											renderWidth, renderHeight);
-
-	if(nullptr == screenTexture)
-	{
-		logGlobal->error("Unable to create screen texture");
-		logGlobal->error(SDL_GetError());
-		throw std::runtime_error("Unable to create screen texture");
-	}
-
-	screen2 = CSDL_Ext::copySurface(screen);
-
-
-	if(nullptr == screen2)
-	{
-		throw std::runtime_error("Unable to copy surface\n");
-	}
-
-	screenBuf = bufOnScreen ? screen : screen2;
-
-	SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
-	SDL_RenderClear(mainRenderer);
-	SDL_RenderPresent(mainRenderer);
-
-	if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
-	{
-		NotificationHandler::init(mainWindow);
-	}
-
-	return true;
-}
-
-//used only once during initialization
-static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo)
-{
-	if(!recreateWindow(w, h, bpp, fullscreen, displayIndex))
-	{
-		throw std::runtime_error("Requested screen resolution is not available\n");
-	}
-}
-
-static void fullScreenChanged()
-{
-	boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-
-	Settings full = settings.write["video"]["fullscreen"];
-	const bool toFullscreen = full->Bool();
-
-	auto bitsPerPixel = screen->format->BitsPerPixel;
-
-	auto w = screen->w;
-	auto h = screen->h;
-
-	if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1))
-	{
-		//will return false and report error if video mode is not supported
-		return;
-	}
-
-	GH.totalRedraw();
-}
-
 static void handleEvent(SDL_Event & ev)
 static void handleEvent(SDL_Event & ev)
 {
 {
 	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
 	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
@@ -1009,8 +555,11 @@ static void handleEvent(SDL_Event & ev)
 			CMM->menu->switchToTab("load");
 			CMM->menu->switchToTab("load");
 			break;
 			break;
 		case EUserEvent::FULLSCREEN_TOGGLED:
 		case EUserEvent::FULLSCREEN_TOGGLED:
-			fullScreenChanged();
-			break;
+			{
+				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+				GH.windowHandler().onFullscreenChanged();
+				break;
+			}
 		default:
 		default:
 			logGlobal->error("Unknown user event. Code %d", ev.user.code);
 			logGlobal->error("Unknown user event. Code %d", ev.user.code);
 			break;
 			break;
@@ -1023,7 +572,10 @@ static void handleEvent(SDL_Event & ev)
 		switch (ev.window.event) {
 		switch (ev.window.event) {
 		case SDL_WINDOWEVENT_RESTORED:
 		case SDL_WINDOWEVENT_RESTORED:
 #ifndef VCMI_IOS
 #ifndef VCMI_IOS
-			fullScreenChanged();
+			{
+				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+				GH.windowHandler().onFullscreenChanged();
+			}
 #endif
 #endif
 			break;
 			break;
 		}
 		}
@@ -1047,10 +599,8 @@ static void handleEvent(SDL_Event & ev)
 		boost::unique_lock<boost::mutex> lock(eventsM);
 		boost::unique_lock<boost::mutex> lock(eventsM);
 		SDLEventsQueue.push(ev);
 		SDLEventsQueue.push(ev);
 	}
 	}
-
 }
 }
 
 
-
 static void mainLoop()
 static void mainLoop()
 {
 {
 	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
 	SettingsListener resChanged = settings.listen["video"]["fullscreen"];
@@ -1071,7 +621,6 @@ static void mainLoop()
 
 
 		CSH->applyPacksOnLobbyScreen();
 		CSH->applyPacksOnLobbyScreen();
 		GH.renderFrame();
 		GH.renderFrame();
-
 	}
 	}
 }
 }
 
 
@@ -1108,29 +657,9 @@ static void quitApplication()
 	vstd::clear_pointer(console);// should be removed after everything else since used by logging
 	vstd::clear_pointer(console);// should be removed after everything else since used by logging
 
 
 	boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
 	boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
-	if(!settings["session"]["headless"].Bool())
-	{
-		if(settings["general"]["notifications"].Bool())
-		{
-			NotificationHandler::destroy();
-		}
-
-		cleanupRenderer();
-
-		if(nullptr != mainRenderer)
-		{
-			SDL_DestroyRenderer(mainRenderer);
-			mainRenderer = nullptr;
-		}
 
 
-		if(nullptr != mainWindow)
-		{
-			SDL_DestroyWindow(mainWindow);
-			mainWindow = nullptr;
-		}
-
-		SDL_Quit();
-	}
+	if(!settings["session"]["headless"].Bool())
+		GH.windowHandler().close();
 
 
 	if(logConfig != nullptr)
 	if(logConfig != nullptr)
 	{
 	{
@@ -1145,7 +674,6 @@ static void quitApplication()
 
 
 void handleQuit(bool ask)
 void handleQuit(bool ask)
 {
 {
-
 	if(CSH->client && LOCPLINT && ask)
 	if(CSH->client && LOCPLINT && ask)
 	{
 	{
 		CCS->curh->set(Cursor::Map::POINTER);
 		CCS->curh->set(Cursor::Map::POINTER);

+ 2 - 0
client/CMakeLists.txt

@@ -75,6 +75,7 @@ set(client_SRCS
 	renderSDL/SDLImage.cpp
 	renderSDL/SDLImage.cpp
 	renderSDL/SDLImageLoader.cpp
 	renderSDL/SDLImageLoader.cpp
 	renderSDL/SDLRWwrapper.cpp
 	renderSDL/SDLRWwrapper.cpp
+	renderSDL/WindowHandler.cpp
 	renderSDL/SDL_Extensions.cpp
 	renderSDL/SDL_Extensions.cpp
 
 
 	widgets/Buttons.cpp
 	widgets/Buttons.cpp
@@ -211,6 +212,7 @@ set(client_HEADERS
 	renderSDL/SDLImage.h
 	renderSDL/SDLImage.h
 	renderSDL/SDLImageLoader.h
 	renderSDL/SDLImageLoader.h
 	renderSDL/SDLRWwrapper.h
 	renderSDL/SDLRWwrapper.h
+	renderSDL/WindowHandler.h
 	renderSDL/SDL_Extensions.h
 	renderSDL/SDL_Extensions.h
 	renderSDL/SDL_PixelAccess.h
 	renderSDL/SDL_PixelAccess.h
 
 

+ 2 - 1
client/CServerHandler.cpp

@@ -123,7 +123,8 @@ public:
 	}
 	}
 };
 };
 
 
-extern std::string NAME;
+static const std::string NAME_AFFIX = "client";
+static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 
 
 CServerHandler::CServerHandler()
 CServerHandler::CServerHandler()
 	: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
 	: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)

+ 13 - 4
client/gui/CGuiHandler.cpp

@@ -18,6 +18,7 @@
 #include "../CGameInfo.h"
 #include "../CGameInfo.h"
 #include "../render/Colors.h"
 #include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 #include "../renderSDL/SDL_Extensions.h"
+#include "../renderSDL/WindowHandler.h"
 #include "../CMT.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterface.h"
@@ -95,9 +96,10 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
 
 
 void CGuiHandler::init()
 void CGuiHandler::init()
 {
 {
+	windowHandlerInstance = std::make_unique<WindowHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
-	mainFPSmng = new CFramerateManager();
-	mainFPSmng->init(settings["video"]["targetfps"].Integer());
+	mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer());
+
 	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
 	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
 	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
 	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
 }
 }
@@ -803,7 +805,12 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
 	SDL_PushEvent(&event);
 	SDL_PushEvent(&event);
 }
 }
 
 
-CFramerateManager::CFramerateManager()
+WindowHandler & CGuiHandler::windowHandler()
+{
+	return *windowHandlerInstance;
+}
+
+CFramerateManager::CFramerateManager(int newRate)
 	: rate(0)
 	: rate(0)
 	, rateticks(0)
 	, rateticks(0)
 	, fps(0)
 	, fps(0)
@@ -811,7 +818,9 @@ CFramerateManager::CFramerateManager()
 	, accumulatedTime(0)
 	, accumulatedTime(0)
 	, lastticks(0)
 	, lastticks(0)
 	, timeElapsed(0)
 	, timeElapsed(0)
-{}
+{
+	init(newRate);
+}
 
 
 void CFramerateManager::init(int newRate)
 void CFramerateManager::init(int newRate)
 {
 {

+ 5 - 1
client/gui/CGuiHandler.h

@@ -29,6 +29,7 @@ class CIntObject;
 class IUpdateable;
 class IUpdateable;
 class IShowActivatable;
 class IShowActivatable;
 class IShowable;
 class IShowable;
+class WindowHandler;
 
 
 // TODO: event handling need refactoring
 // TODO: event handling need refactoring
 enum class EUserEvent
 enum class EUserEvent
@@ -56,7 +57,7 @@ private:
 	ui32 accumulatedFrames;
 	ui32 accumulatedFrames;
 
 
 public:
 public:
-	CFramerateManager(); // initializes the manager with a given fps rate
+	CFramerateManager(int newRate); // initializes the manager with a given fps rate
 	void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
 	void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
 	void framerateDelay(); // needs to be called every game update cycle
 	void framerateDelay(); // needs to be called every game update cycle
 	ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
 	ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
@@ -95,6 +96,7 @@ private:
 	CIntObjectList doubleClickInterested;
 	CIntObjectList doubleClickInterested;
 	CIntObjectList textInterested;
 	CIntObjectList textInterested;
 
 
+	std::unique_ptr<WindowHandler> windowHandlerInstance;
 
 
 	void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
 	void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
@@ -135,6 +137,8 @@ public:
 	/// moves mouse pointer into specified position inside vcmi window
 	/// moves mouse pointer into specified position inside vcmi window
 	void moveCursorToPosition(const Point & position);
 	void moveCursorToPosition(const Point & position);
 
 
+	WindowHandler & windowHandler();
+
 	IUpdateable *curInt;
 	IUpdateable *curInt;
 
 
 	Point lastClick;
 	Point lastClick;

+ 1 - 0
client/lobby/SelectionTab.cpp

@@ -31,6 +31,7 @@
 
 
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CConfigHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/CModHandler.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/GameSettings.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/Filesystem.h"

+ 407 - 0
client/renderSDL/WindowHandler.cpp

@@ -0,0 +1,407 @@
+/*
+ * WindowHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "WindowHandler.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/NotificationHandler.h"
+#include "CMT.h"
+#include "SDL_Extensions.h"
+
+#include <SDL.h>
+
+static const std::string NAME_AFFIX = "client";
+static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
+
+Point WindowHandler::getPreferredLogicalResolution() const
+{
+	// TODO: CONFIGURABLE ADVMAP - IMPLEMENT UI SCALE SETTING
+	return {1280, 720};
+}
+
+Point WindowHandler::getPreferredRenderingResolution() const
+{
+	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_WINDOWED)
+	{
+		SDL_Rect bounds;
+		SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds);
+		return Point(bounds.w, bounds.h);
+	}
+	else
+	{
+		const JsonNode & video = settings["video"];
+		int width = video["screenRes"]["width"].Integer();
+		int height = video["screenRes"]["height"].Integer();
+
+		return Point(width, height);
+	}
+}
+
+int WindowHandler::getPreferredDisplayIndex() const
+{
+#ifdef VCMI_MOBILE
+	// Assuming no multiple screens on Android / ios?
+	return 0;
+#else
+	if (mainWindow != nullptr)
+		return SDL_GetWindowDisplayIndex(mainWindow);
+	else
+		return settings["video"]["displayIndex"].Integer();
+#endif
+}
+
+EWindowMode WindowHandler::getPreferredWindowMode() const
+{
+#ifdef VCMI_MOBILE
+	// On Android / ios game will always render to screen size
+	return EWindowMode::FULLSCREEN_WINDOWED;
+#else
+	const JsonNode & video = settings["video"];
+	bool fullscreen = video["fullscreen"].Bool();
+	bool realFullscreen = settings["video"]["realFullscreen"].Bool();
+
+	if (!fullscreen)
+		return EWindowMode::WINDOWED;
+
+	if (realFullscreen)
+		return EWindowMode::FULLSCREEN_TRUE;
+	else
+		return EWindowMode::FULLSCREEN_WINDOWED;
+#endif
+}
+
+WindowHandler::WindowHandler()
+{
+	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE))
+	{
+		logGlobal->error("Something was wrong: %s", SDL_GetError());
+		exit(-1);
+	}
+
+	const auto & logCallback = [](void * userdata, int category, SDL_LogPriority priority, const char * message)
+	{
+		logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
+	};
+
+	SDL_LogSetOutputFunction(logCallback, nullptr);
+
+#ifdef VCMI_ANDROID
+	// manually setting egl pixel format, as a possible solution for sdl2<->android problem
+	// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
+	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
+	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+#endif // VCMI_ANDROID
+
+	validateSettings();
+	recreateWindow();
+}
+
+bool WindowHandler::recreateWindow()
+{
+	destroyScreen();
+
+	if(mainWindow == nullptr)
+		initializeRenderer();
+	else
+		updateFullscreenState();
+
+	initializeScreen();
+
+	if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
+	{
+		NotificationHandler::init(mainWindow);
+	}
+
+	return true;
+}
+
+void WindowHandler::updateFullscreenState()
+{
+#if !defined(VCMI_MOBILE)
+	int displayIndex = getPreferredDisplayIndex();
+
+	switch(getPreferredWindowMode())
+	{
+		case EWindowMode::FULLSCREEN_TRUE:
+		{
+			SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
+
+			SDL_DisplayMode mode;
+			SDL_GetDesktopDisplayMode(displayIndex, &mode);
+			Point resolution = getPreferredRenderingResolution();
+
+			mode.w = resolution.x;
+			mode.h = resolution.y;
+
+			SDL_SetWindowDisplayMode(mainWindow, &mode);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
+
+			return;
+		}
+		case EWindowMode::FULLSCREEN_WINDOWED:
+		{
+			SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
+			return;
+		}
+		case EWindowMode::WINDOWED:
+		{
+			Point resolution = getPreferredLogicalResolution();
+			SDL_SetWindowFullscreen(mainWindow, 0);
+			SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
+			SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
+			return;
+		}
+	}
+#endif
+}
+
+void WindowHandler::initializeRenderer()
+{
+	mainWindow = createWindow();
+
+	if(mainWindow == nullptr)
+		throw std::runtime_error("Unable to create window\n");
+
+	//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
+	mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0);
+
+	if(mainRenderer == nullptr)
+		throw std::runtime_error("Unable to create renderer\n");
+
+	SDL_RendererInfo info;
+	SDL_GetRendererInfo(mainRenderer, &info);
+	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
+	logGlobal->info("Created renderer %s", info.name);
+}
+
+void WindowHandler::initializeScreen()
+{
+#ifdef VCMI_ENDIAN_BIG
+	int bmask = 0xff000000;
+	int gmask = 0x00ff0000;
+	int rmask = 0x0000ff00;
+	int amask = 0x000000ff;
+#else
+	int bmask = 0x000000ff;
+	int gmask = 0x0000ff00;
+	int rmask = 0x00ff0000;
+	int amask = 0xFF000000;
+#endif
+
+	auto logicalSize = getPreferredLogicalResolution();
+	SDL_RenderSetLogicalSize(mainRenderer, logicalSize.x, logicalSize.y);
+
+	screen = SDL_CreateRGBSurface(0, logicalSize.x, logicalSize.y, 32, rmask, gmask, bmask, amask);
+	if(nullptr == screen)
+	{
+		logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", logicalSize.x, logicalSize.y, 32, SDL_GetError());
+		throw std::runtime_error("Unable to create surface");
+	}
+	//No blending for screen itself. Required for proper cursor rendering.
+	SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
+
+	screenTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, logicalSize.x, logicalSize.y);
+
+	if(nullptr == screenTexture)
+	{
+		logGlobal->error("Unable to create screen texture");
+		logGlobal->error(SDL_GetError());
+		throw std::runtime_error("Unable to create screen texture");
+	}
+
+	screen2 = CSDL_Ext::copySurface(screen);
+
+	if(nullptr == screen2)
+	{
+		throw std::runtime_error("Unable to copy surface\n");
+	}
+
+	screenBuf = screen;
+
+	SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
+	SDL_RenderClear(mainRenderer);
+	SDL_RenderPresent(mainRenderer);
+}
+
+SDL_Window * WindowHandler::createWindowImpl(Point dimensions, int displayIndex, int flags, bool center)
+{
+	int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
+
+	return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags);
+}
+
+SDL_Window * WindowHandler::createWindow()
+{
+#ifndef VCMI_MOBILE
+	const JsonNode & video = settings["video"];
+	int displayIndex = video["displayIndex"].Float();
+	bool fullscreen = video["fullscreen"].Bool();
+	bool realFullscreen = video["realFullscreen"].Bool();
+	Point dimensions = getPreferredRenderingResolution();
+
+	if(!fullscreen)
+		return createWindowImpl(dimensions, displayIndex, 0, true);
+
+	if(realFullscreen)
+		return createWindowImpl(dimensions, displayIndex, SDL_WINDOW_FULLSCREEN, false);
+	else
+		return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN_DESKTOP, false);
+#endif
+
+#ifdef VCMI_IOS
+	SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
+	SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
+	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
+
+	uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
+	SDL_Window * result = createWindowImpl(Point(), displayIndex, windowFlags | SDL_WINDOW_METAL, false);
+
+	if(result != nullptr)
+		return result;
+
+	logGlobal->warn("Metal unavailable, using OpenGLES");
+	return createWindowImpl(Point(), displayIndex, windowFlags, false);
+#endif
+
+#ifdef VCMI_ANDROID
+	return createWindowImpl(Point(), displayIndex, SDL_WINDOW_FULLSCREEN, false);
+#endif
+}
+
+void WindowHandler::onFullscreenChanged()
+{
+	if(!recreateWindow())
+	{
+		//will return false and report error if video mode is not supported
+		return;
+	}
+	GH.totalRedraw();
+}
+
+void WindowHandler::validateSettings()
+{
+#if !defined(VCMI_MOBILE)
+	{
+		int displayIndex = settings["video"]["displayIndex"].Integer();
+		int displaysCount = SDL_GetNumVideoDisplays();
+
+		if (displayIndex >= displaysCount)
+		{
+			Settings writer = settings.write["video"]["displayIndex"];
+			writer->Float() = 0;
+		}
+	}
+
+	if (getPreferredWindowMode() == EWindowMode::WINDOWED)
+	{
+		//we only check that our desired window size fits on screen
+		int displayIndex = getPreferredDisplayIndex();
+		Point resolution = getPreferredRenderingResolution();
+
+		SDL_DisplayMode mode;
+
+		if (SDL_GetDesktopDisplayMode(displayIndex, &mode) == 0)
+		{
+			if(resolution.x > mode.w || resolution.y > mode.h)
+			{
+				Settings writer = settings.write["video"]["screenRes"];
+				writer["width"].Float() = mode.w;
+				writer["height"].Float() = mode.h;
+			}
+		}
+	}
+
+	if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_TRUE)
+	{
+		// TODO: check that display supports selected resolution
+	}
+#endif
+}
+
+int WindowHandler::getPreferredRenderingDriver() const
+{
+	int result = -1;
+	const JsonNode & video = settings["video"];
+
+	int driversCount = SDL_GetNumRenderDrivers();
+	std::string preferredDriverName = video["driver"].String();
+
+	logGlobal->info("Found %d render drivers", driversCount);
+
+	for(int it = 0; it < driversCount; it++)
+	{
+		SDL_RendererInfo info;
+		SDL_GetRenderDriverInfo(it, &info);
+
+		std::string driverName(info.name);
+
+		if(!preferredDriverName.empty() && driverName == preferredDriverName)
+		{
+			result = it;
+			logGlobal->info("\t%s (active)", driverName);
+		}
+		else
+			logGlobal->info("\t%s", driverName);
+	}
+	return result;
+}
+
+void WindowHandler::destroyScreen()
+{
+	screenBuf = nullptr; //it`s a link - just nullify
+
+	if(nullptr != screen2)
+	{
+		SDL_FreeSurface(screen2);
+		screen2 = nullptr;
+	}
+
+	if(nullptr != screen)
+	{
+		SDL_FreeSurface(screen);
+		screen = nullptr;
+	}
+
+	if(nullptr != screenTexture)
+	{
+		SDL_DestroyTexture(screenTexture);
+		screenTexture = nullptr;
+	}
+}
+
+void WindowHandler::destroyWindow()
+{
+	if(nullptr != mainRenderer)
+	{
+		SDL_DestroyRenderer(mainRenderer);
+		mainRenderer = nullptr;
+	}
+
+	if(nullptr != mainWindow)
+	{
+		SDL_DestroyWindow(mainWindow);
+		mainWindow = nullptr;
+	}
+}
+
+void WindowHandler::close()
+{
+	if(settings["general"]["notifications"].Bool())
+		NotificationHandler::destroy();
+
+	destroyScreen();
+	destroyWindow();
+	SDL_Quit();
+}

+ 74 - 0
client/renderSDL/WindowHandler.h

@@ -0,0 +1,74 @@
+/*
+ * WindowHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+struct SDL_Texture;
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Surface;
+
+#include "../../lib/Point.h"
+
+enum class EWindowMode
+{
+	// game runs in a window that covers part of the screen
+	WINDOWED,
+	// game runs in a 'window' that always covers entire screen and uses unmodified desktop resolution
+	// The only mode that is available on mobile devices
+	FULLSCREEN_WINDOWED,
+	// game runs in a fullscreen mode with resolution selected by player
+	FULLSCREEN_TRUE
+};
+
+/// This class is responsible for management of game window and its main rendering surface
+class WindowHandler
+{
+	/// Dimensions of target surfaces/textures, this value is what game logic views as screen size
+	Point getPreferredLogicalResolution() const;
+
+	/// Dimensions of output window, if different from logicalResolution SDL will perform scaling
+	/// This value is what player views as window size
+	Point getPreferredRenderingResolution() const;
+
+	EWindowMode getPreferredWindowMode() const;
+
+	/// Returns index of display on which window should be created
+	int getPreferredDisplayIndex() const;
+
+	/// Returns index of rendering driver preferred by player or -1 if no preference
+	int getPreferredRenderingDriver() const;
+
+	/// Creates SDL window with specified parameters
+	SDL_Window * createWindowImpl(Point dimensions, int displayIndex, int flags, bool center);
+
+	/// Creates SDL window using OS-specific settings & user-specific config
+	SDL_Window * createWindow();
+
+	void initializeRenderer();
+	void initializeScreen();
+	void updateFullscreenState();
+
+	bool recreateWindow();
+	void destroyScreen();
+	void destroyWindow();
+
+	void validateSettings();
+public:
+
+	/// Creates and initializes screen, window and SDL state
+	WindowHandler();
+
+	/// Updates and potentially recreates target screen to match selected fullscreen status
+	void onFullscreenChanged();
+
+	/// De-initializes and destroys screen, window and SDL state
+	void close();
+};

+ 0 - 1
client/windows/GUIClasses.h

@@ -12,7 +12,6 @@
 #include "CWindowObject.h"
 #include "CWindowObject.h"
 #include "../lib/GameConstants.h"
 #include "../lib/GameConstants.h"
 #include "../lib/ResourceSet.h"
 #include "../lib/ResourceSet.h"
-#include "../lib/CConfigHandler.h"
 #include "../lib/int3.h"
 #include "../lib/int3.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CWindowWithArtifacts.h"
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/CGarrisonInt.h"