1 /*
2  * Stellarium
3  * Copyright (C) 2002 Fabien Chereau
4  * Copyright (C) 2012 Timothy Reaves
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19  */
20 
21 #include "StelMainView.hpp"
22 #include "StelSplashScreen.hpp"
23 #include "StelTranslator.hpp"
24 #include "StelLogger.hpp"
25 #include "StelFileMgr.hpp"
26 #include "CLIProcessor.hpp"
27 #include "StelIniParser.hpp"
28 #include "StelUtils.hpp"
29 #ifdef ENABLE_SCRIPTING
30 #include "StelScriptOutput.hpp"
31 #endif
32 
33 #include <QDebug>
34 
35 #ifndef USE_QUICKVIEW
36 	#include <QApplication>
37 	#include <QMessageBox>
38 	#include <QStyleFactory>
39 #else
40 	#include <QGuiApplication>
41 #endif
42 #include <QCoreApplication>
43 #include <QDir>
44 #include <QFile>
45 #include <QFileInfo>
46 #include <QFontDatabase>
47 #include <QGuiApplication>
48 #include <QSettings>
49 #include <QString>
50 #include <QStringList>
51 #include <QTextStream>
52 #include <QTranslator>
53 #include <QNetworkDiskCache>
54 #include <QThread>
55 
56 #include <clocale>
57 
58 #ifdef Q_OS_WIN
59 	#include <Windows.h>
60 	//we use WIN32_LEAN_AND_MEAN so this needs to be included
61 	//to use timeBeginPeriod/timeEndPeriod
62 	#include <mmsystem.h>
63 
64 	// Default to High Performance Mode on machines with hybrid graphics
65 	// Details: https://stackoverflow.com/questions/44174859/how-to-give-an-option-to-select-graphics-adapter-in-a-directx-11-application
66 	extern "C"
67 	{
68 	#ifdef _MSC_VER
69 		__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
70 		__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001;
71 	#else
72 		__attribute__((dllexport)) DWORD NvOptimusEnablement = 0x00000001;
73 		__attribute__((dllexport)) int AmdPowerXpressRequestHighPerformance = 0x00000001;
74 	#endif
75 	}
76 #else
77 	extern "C"
78 	{
79 		int NvOptimusEnablement = 1;
80 		int AmdPowerXpressRequestHighPerformance = 1;
81 	}
82 #endif //Q_OS_WIN
83 
84 //! @class CustomQTranslator
85 //! Provides custom i18n support.
86 class CustomQTranslator : public QTranslator
87 {
88 	using QTranslator::translate;
89 public:
isEmpty() const90 	virtual bool isEmpty() const { return false; }
91 
92 	//! Overrides QTranslator::translate().
93 	//! Calls StelTranslator::qtranslate().
94 	//! @param context Qt context string - IGNORED.
95 	//! @param sourceText the source message.
96 	//! @param comment optional parameter
translate(const char * context,const char * sourceText,const char * disambiguation=Q_NULLPTR,int n=-1) const97 	virtual QString translate(const char *context, const char *sourceText, const char *disambiguation = Q_NULLPTR, int n = -1) const
98 	{
99 		Q_UNUSED(context);
100 		Q_UNUSED(n);
101 		return StelTranslator::globalTranslator->qtranslate(sourceText, disambiguation);
102 	}
103 };
104 
105 
106 //! Copies the default configuration file.
107 //! This function copies the default_cfg.ini file to config.ini (or other
108 //! name specified on the command line located in the user data directory.
copyDefaultConfigFile(const QString & newPath)109 void copyDefaultConfigFile(const QString& newPath)
110 {
111 	QString defaultConfigFilePath = StelFileMgr::findFile("data/default_cfg.ini");
112 	if (defaultConfigFilePath.isEmpty())
113 		qFatal("ERROR copyDefaultConfigFile failed to locate data/default_cfg.ini. Please check your installation.");
114 	QFile::copy(defaultConfigFilePath, newPath);
115 	if (!StelFileMgr::exists(newPath))
116 	{
117 		qFatal("ERROR copyDefaultConfigFile failed to copy file %s  to %s. You could try to copy it by hand.",
118 			qPrintable(defaultConfigFilePath), qPrintable(newPath));
119 	}
120 	QFile::setPermissions(newPath, QFile::permissions(newPath) | QFileDevice::WriteOwner);
121 }
122 
123 //! Removes all items from the cache.
clearCache()124 void clearCache()
125 {
126 	QNetworkDiskCache* cacheMgr = new QNetworkDiskCache();
127 	cacheMgr->setCacheDirectory(StelFileMgr::getCacheDir());
128 	cacheMgr->clear(); // Removes all items from the cache.
129 }
130 
131 // Main stellarium procedure
main(int argc,char ** argv)132 int main(int argc, char **argv)
133 {
134 	Q_INIT_RESOURCE(mainRes);
135 	Q_INIT_RESOURCE(guiRes);
136 
137 	// Log command line arguments.
138 	QString argStr;
139 	QStringList argList;
140 	for (int i=0; i<argc; ++i)
141 	{
142 		argList << argv[i];
143 		argStr += QString("%1 ").arg(argv[i]);
144 	}
145 
146 #ifdef Q_OS_WIN
147 	// Fix for the speeding system clock bug on systems that use ACPI
148 	// See http://support.microsoft.com/kb/821893
149 	UINT timerGrain = 1;
150 	if (timeBeginPeriod(timerGrain) == TIMERR_NOCANDO)
151 	{
152 		// If this is too fine a grain, try the lowest value used by a timer
153 		timerGrain = 5;
154 		if (timeBeginPeriod(timerGrain) == TIMERR_NOCANDO)
155 			timerGrain = 0;
156 	}
157 #endif
158 
159 	// Seed the PRNG
160 	qsrand(QDateTime::currentMSecsSinceEpoch());
161 
162 	QCoreApplication::setApplicationName("stellarium");
163 	QCoreApplication::setApplicationVersion(StelUtils::getApplicationVersion());
164 	QCoreApplication::setOrganizationDomain("stellarium.org");
165 	QCoreApplication::setOrganizationName("stellarium");
166 
167 	// Support high DPI pixmaps and fonts
168 	QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
169 	QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
170 	if (argList.contains("--scale-gui")) // Variable QT_SCALE_FACTOR should be defined before app will be created!
171 		qputenv("QT_SCALE_FACTOR", CLIProcessor::argsGetOptionWithArg(argList, "", "--scale-gui", "").toString().toLatin1());
172 
173 	#if defined(Q_OS_MAC)
174 	QFileInfo appInfo(QString::fromUtf8(argv[0]));
175 	QDir appDir(appInfo.absolutePath());
176 	appDir.cdUp();
177 	QCoreApplication::addLibraryPath(appDir.absoluteFilePath("PlugIns"));
178 	#elif defined(Q_OS_WIN)
179 	QFileInfo appInfo(QString::fromUtf8(argv[0]));
180 	QCoreApplication::addLibraryPath(appInfo.absolutePath());
181 	#endif
182 
183 	QGuiApplication::setDesktopSettingsAware(false);
184 
185 #ifndef USE_QUICKVIEW
186 	QApplication::setStyle(QStyleFactory::create("Fusion"));
187 	// The QApplication MUST be created before the StelFileMgr is initialized.
188 	QApplication app(argc, argv);
189 #else
190 	QGuiApplication::setDesktopSettingsAware(false);
191 	QGuiApplication app(argc, argv);
192 #endif
193 
194 	// QApplication sets current locale, but
195 	// we need scanf()/printf() and friends to always work in the C locale,
196 	// otherwise configuration/INI file parsing will be erroneous.
197 	setlocale(LC_NUMERIC, "C");
198 
199 	// Solution for bug: https://bugs.launchpad.net/stellarium/+bug/1498616
200 	qputenv("QT_HARFBUZZ", "old");
201 
202 	// Init the file manager
203 	StelFileMgr::init();
204 
205 	SplashScreen::present();
206 
207 	// add contents of STEL_OPTS environment variable.
208 	QString envStelOpts(qgetenv("STEL_OPTS").constData());
209 	if (envStelOpts.length()>0)
210 	{
211 		argList+= envStelOpts.split(" ");
212 		argStr += " " + envStelOpts;
213 	}
214 	//save the modified arg list as an app property for later use
215 	qApp->setProperty("stelCommandLine", argList);
216 
217 	// Parse for first set of CLI arguments - stuff we want to process before other
218 	// output, such as --help and --version
219 	CLIProcessor::parseCLIArgsPreConfig(argList);
220 
221 	#ifdef Q_OS_WIN
222 	if (qApp->property("onetime_angle_mode").isValid())
223 	{
224 		app.setAttribute(Qt::AA_UseOpenGLES, true);
225 	}
226 	if (qApp->property("onetime_mesa_mode").isValid())
227 	{
228 		app.setAttribute(Qt::AA_UseSoftwareOpenGL, true);
229 	}
230 	#endif
231 
232 	// Start logging.
233 	StelLogger::init(StelFileMgr::getUserDir()+"/log.txt");
234 	StelLogger::writeLog(argStr);
235 
236 	// OK we start the full program.
237 	// Print the console splash and get on with loading the program
238 	QString versionLine = QString("This is %1 - %2").arg(StelUtils::getApplicationName(), STELLARIUM_URL);
239 	QString copyrightLine = QString("Copyright (C) %1 Fabien Chereau et al.").arg(COPYRIGHT_YEARS);
240 	int maxLength = qMax(versionLine.size(), copyrightLine.size());
241 	qDebug() << qPrintable(QString(" %1").arg(QString().fill('-', maxLength+2)));
242 	qDebug() << qPrintable(QString("[ %1 ]").arg(versionLine.leftJustified(maxLength, ' ')));
243 	qDebug() << qPrintable(QString("[ %1 ]").arg(copyrightLine.leftJustified(maxLength, ' ')));
244 	qDebug() << qPrintable(QString(" %1").arg(QString().fill('-', maxLength+2)));
245 	qDebug() << "Writing log file to:" << QDir::toNativeSeparators(StelLogger::getLogFileName());
246 	qDebug() << "File search paths:";
247 	int n=0;
248 	for (const auto& i : StelFileMgr::getSearchPaths())
249 	{
250 		qDebug() << " " << n << ". " << QDir::toNativeSeparators(i);
251 		++n;
252 	}
253 
254 	// Now manage the loading of the proper config file
255 	QString configName;
256 	try
257 	{
258 		configName = CLIProcessor::argsGetOptionWithArg(argList, "-c", "--config-file", "config.ini").toString();
259 	}
260 	catch (std::runtime_error& e)
261 	{
262 		qWarning() << "WARNING: while looking for --config-file option: " << e.what() << ". Using \"config.ini\"";
263 		configName = "config.ini";
264 	}
265 
266 	QString configFileFullPath = StelFileMgr::findFile(configName, StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::File));
267 	if (configFileFullPath.isEmpty())
268 	{
269 		configFileFullPath = StelFileMgr::findFile(configName, StelFileMgr::New);
270 		if (configFileFullPath.isEmpty())
271 			qFatal("Could not create configuration file %s.", qPrintable(configName));
272 	}
273 
274 	QSettings* confSettings = Q_NULLPTR;
275 	if (StelFileMgr::exists(configFileFullPath))
276 	{
277 		confSettings = new QSettings(configFileFullPath, StelIniFormat, Q_NULLPTR);
278 		// Implement "restore default settings" feature.
279 		bool restoreDefaultConfigFile = false;
280 		if (CLIProcessor::argsGetOption(argList, "", "--restore-defaults"))
281 			restoreDefaultConfigFile=true;
282 		else
283 			restoreDefaultConfigFile = confSettings->value("main/restore_defaults", false).toBool();
284 
285 		if (!restoreDefaultConfigFile)
286 		{
287 			QString version = confSettings->value("main/version", "0.0.0").toString();
288 			if (version!=QString(PACKAGE_VERSION))
289 			{
290 				QTextStream istr(&version);
291 				char tmp;
292 				int v1=0;
293 				int v2=0;
294 				istr >> v1 >> tmp >> v2;
295 				// Config versions less than 0.6.0 are not supported, otherwise we will try to use it
296 				if (v1==0 && v2<6)
297 				{
298 					// The config file is too old to try an import
299 					qDebug() << "The current config file is from a version too old for parameters to be imported ("
300 							 << (version.isEmpty() ? "<0.6.0" : version) << ").\n"
301 							 << "It will be replaced by the default config file.";
302 					restoreDefaultConfigFile = true;
303 				}
304 				else
305 				{
306 					qDebug() << "Attempting to use an existing older config file.";
307 					confSettings->setValue("main/version", QString(PACKAGE_VERSION)); // Upgrade version of config.ini
308 					clearCache();
309 					qDebug() << "Cleared cache and updated config.ini...";
310 				}
311 			}
312 		}
313 
314 		if (restoreDefaultConfigFile)
315 		{
316 			if (confSettings)
317 				delete confSettings;
318 
319 			QString backupFile(configFileFullPath.left(configFileFullPath.length()-3) + QString("old"));
320 			if (QFileInfo(backupFile).exists())
321 				QFile(backupFile).remove();
322 
323 			QFile(configFileFullPath).rename(backupFile);
324 			copyDefaultConfigFile(configFileFullPath);
325 			confSettings = new QSettings(configFileFullPath, StelIniFormat);
326 			qWarning() << "Resetting defaults config file. Previous config file was backed up in " << QDir::toNativeSeparators(backupFile);
327 			clearCache();
328 		}
329 	}
330 	else
331 	{
332 		qDebug() << "Config file " << QDir::toNativeSeparators(configFileFullPath) << " does not exist. Copying the default file.";
333 		copyDefaultConfigFile(configFileFullPath);
334 		confSettings = new QSettings(configFileFullPath, StelIniFormat);
335 	}
336 
337 	Q_ASSERT(confSettings);
338 	qDebug() << "Config file is: " << QDir::toNativeSeparators(configFileFullPath);
339 
340 	#ifdef ENABLE_SCRIPTING
341 	QString outputFile = StelFileMgr::getUserDir()+"/output.txt";
342 	if (confSettings->value("main/use_separate_output_file", false).toBool())
343 		outputFile = StelFileMgr::getUserDir()+"/output-"+QDateTime::currentDateTime().toString("yyyyMMdd-HHmmss")+".txt";
344 	StelScriptOutput::init(outputFile);
345 	#endif
346 
347 	// Override config file values from CLI.
348 	CLIProcessor::parseCLIArgsPostConfig(argList, confSettings);
349 
350 	// Add the DejaVu font that we use everywhere in the program
351 	const QString& fName = StelFileMgr::findFile("data/DejaVuSans.ttf");
352 	if (!fName.isEmpty())
353 		QFontDatabase::addApplicationFont(fName);
354 
355 	QString fileFont = confSettings->value("gui/base_font_file", "").toString();
356 	if (!fileFont.isEmpty())
357 	{
358 		const QString& afName = StelFileMgr::findFile(QString("data/%1").arg(fileFont));
359 		if (!afName.isEmpty() && !afName.contains("file not found"))
360 			QFontDatabase::addApplicationFont(afName);
361 		else
362 			qWarning() << "ERROR while loading custom font " << QDir::toNativeSeparators(fileFont);
363 	}
364 
365 	// Set the default application font and font size.
366 	// Note that style sheet will possibly override this setting.
367 #ifdef Q_OS_WIN
368 	// Let's try avoid ugly font rendering on Windows.
369 	// Details: https://sourceforge.net/p/stellarium/discussion/278769/thread/810a1e5c/
370 	QString baseFont = confSettings->value("gui/base_font_name", "Verdana").toString();
371 	QFont tmpFont(baseFont);
372 	tmpFont.setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible);
373 #else
374 	QString baseFont = confSettings->value("gui/base_font_name", "DejaVu Sans").toString();
375 	QFont tmpFont(baseFont);
376 #endif
377 	tmpFont.setPixelSize(confSettings->value("gui/gui_font_size", 13).toInt());
378 	QGuiApplication::setFont(tmpFont);
379 
380 	// Initialize translator feature
381 	StelTranslator::init(StelFileMgr::getInstallationDir() + "/data/iso639-1.utf8");
382 
383 	// Use our custom translator for Qt translations as well
384 	CustomQTranslator trans;
385 	app.installTranslator(&trans);
386 
387 	StelMainView mainWin(confSettings);
388 	mainWin.show();
389 	SplashScreen::finish(&mainWin);
390 	app.exec();
391 	mainWin.deinit();
392 
393 	delete confSettings;
394 	StelLogger::deinit();
395 
396 	#ifdef Q_OS_WIN
397 	if(timerGrain)
398 		timeEndPeriod(timerGrain);
399 	#endif //Q_OS_WIN
400 
401 	return 0;
402 }
403 
404