kumir2-launcher.cpp 12.3 KB
Newer Older
Alexander A. Maly's avatar
Alexander A. Maly committed
1 2 3 4 5 6 7 8 9
#include <QDateTime>
#include <QDebug>
#include <QMessageBox>
#include <QTranslator>
#include <QDir>
#include <QIcon>
#include <QPainter>
#include <QSplashScreen>
#include <QApplication>
10
#include <QTextCodec>
victor's avatar
 
victor committed
11

12 13
#include <kumir2-libs/extensionsystem/pluginmanager.h>
#include <kumir2-libs/extensionsystem/logger.h>
victor's avatar
 
victor committed
14

15 16 17 18 19 20 21 22 23 24

static bool guiMode = false, pipeMode = false;
static bool versionMode = false, helpMode = false;

#ifdef CONFIGURATION_TEMPLATE
static QByteArray currentTemplate = CONFIGURATION_TEMPLATE;
#else
#error No default configuration passed to GCC
#endif

25
static QString resolvePath(const char *what)
26
{
27 28 29 30 31 32 33 34 35 36 37 38
	static QString ExecDir = QString::fromLatin1(KUMIR2_EXEC_DIR);
	static QString MyDir = QCoreApplication::applicationDirPath();
	static int RootDistLevelUp = ExecDir.isEmpty()
		? 0
		: ExecDir.count("/") + 1;
	QString result = MyDir;
	for (int i = 0; i < RootDistLevelUp; ++i) {
		result += "/../";
	}
	result += QString::fromLatin1(what);
	result = QDir::cleanPath(result);
	return result;
39
}
victor's avatar
 
victor committed
40

Victor Yacovlev's avatar
Victor Yacovlev committed
41
#if QT_VERSION < 0x050000
42 43 44
static void LoggerMessageOutput(QtMsgType type, const char *msga)
{
	QString msg(QString::fromLocal8Bit(msga));
Victor Yacovlev's avatar
Victor Yacovlev committed
45
#else
46
static void LoggerMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg)
victor's avatar
 
victor committed
47
{
48
#endif
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
	ExtensionSystem::Logger *logger = ExtensionSystem::Logger::instance();
	switch (type) {
	case QtDebugMsg:
		logger->debug(msg);
		break;
	case QtWarningMsg:
		logger->warning(msg);
		break;
	case QtCriticalMsg:
		logger->critical(msg);
		break;
	case QtFatalMsg:
		logger->fatal(msg);
		abort();
	default:
		break;
	}
victor's avatar
 
victor committed
66 67
}

68
#if QT_VERSION < 0x050000
69 70 71
static void ConsoleMessageOutput(QtMsgType type, const char *msga)
{
	QString msg(QString::fromLocal8Bit(msga));
72 73
#else
static void ConsoleMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg)
victor's avatar
 
victor committed
74
{
Alexander A. Maly's avatar
Alexander A. Maly committed
75
#endif
76 77
	switch (type) {
	case QtDebugMsg:
victor's avatar
 
victor committed
78
//        fprintf(stderr, "Debug: %s\n", msg);
79 80
		break;
	case QtWarningMsg:
victor's avatar
 
victor committed
81
//        fprintf(stderr, "Warning: %s\n", msg);
82 83
		break;
	case QtCriticalMsg:
84
		fprintf(stderr, "Critical: %s\n", msg.toLocal8Bit().data());
85 86
		break;
	case QtFatalMsg:
87
		fprintf(stderr, "Fatal: %s\n", msg.toLocal8Bit().data());
88 89 90 91
		abort();
	default:
		break;
	}
victor's avatar
 
victor committed
92 93
}

94
static void showErrorMessage(const QString &text)
victor's avatar
 
victor committed
95
{
96
	if (guiMode) {
97 98
		QMessageBox::critical(0, "Kumir 2 Launcher", text);
	} else {
99
		fprintf(stderr, "%s\n", qPrintable(text));
100
	}
victor's avatar
 
victor committed
101 102
}

103
static QString getLanguage()
victor's avatar
 
victor committed
104
{
105
	return "ru"; // TODO implement sometime in future...
victor's avatar
 
victor committed
106 107
}

108 109 110
static
QList<QDir> translationsDirs()
{
111
	QList<QDir> result;
112

113
	// Translations dir from base distribution
114
	QString sharePath = resolvePath(KUMIR2_RESOURCES_DIR);
115 116 117 118
	QDir baseTranslationsDir(sharePath + "/translations");
	if (baseTranslationsDir.exists()) {
		result.append(baseTranslationsDir);
	}
119 120

#ifdef Q_OS_LINUX
121
	// Search additional paths
122 123
	QString homePath = QString::fromLocal8Bit(::getenv("HOME"));
	QStringList extraPaths = QStringList()
124 125 126 127 128 129 130 131 132 133 134 135 136 137
		<< "/usr/share/kumir2/translations"
		<< "/usr/local/share/kumir2/translations"
		<< "/opt/kumir2/share/translations"
		<< "/opt/kumir/share/translations"
		<< homePath + "/.local/share/kumir2/translations"
		<< QDir::currentPath() + "/translations"
		;

	Q_FOREACH (const QString &path, extraPaths) {
		QDir candidate(path);
		if (candidate.exists()) {
			result.append(candidate);
		}
	}
138 139
#endif

140
	return result;
141
}
victor's avatar
 
victor committed
142

143
class Application : QObject
Victor Yacovlev's avatar
Victor Yacovlev committed
144 145
{
public:
Alexander A. Maly's avatar
Alexander A. Maly committed
146
	explicit Application(int &argc, char **argv, bool gui)
147 148 149 150 151
		: QObject()
		, _qApp(0)
		, _timerId(-1)
		, _splashScreen(nullptr)
		, _started(false)
152
		, _gui(gui)
153
	{
154
		if (_gui) {
155 156 157 158 159 160 161
			_qApp = new QApplication(argc, argv);
		} else {
			_qApp = new QCoreApplication(argc, argv);
		}
		_qApp->installEventFilter(this);
	}

Alexander A. Maly's avatar
Alexander A. Maly committed
162
	void setSplashScreen(QSplashScreen *s)
163 164 165 166
	{
		_splashScreen = s;
	}

Alexander A. Maly's avatar
Alexander A. Maly committed
167
	void initialize()
168
	{
Alexander A. Maly's avatar
Alexander A. Maly committed
169
		QStringList arguments = _qApp->arguments();
170 171 172 173 174 175 176 177 178 179 180 181 182 183
		qDebug() << "Arguments: " << arguments;

		const QList<QDir> tsDirs = translationsDirs();
		Q_FOREACH (const QDir &translationsDir, tsDirs) {
			QStringList ts_files = translationsDir.entryList(QStringList() << "*_" + getLanguage() + ".qm");
			foreach (QString tsname, ts_files) {
				tsname = tsname.mid(0, tsname.length() - 3);
				QTranslator *tr = new QTranslator(_qApp);
				if (tr->load(tsname, translationsDir.absolutePath())) {
					_qApp->installTranslator(tr);
				}
			}
		}

184
		QString sharePath = resolvePath(KUMIR2_RESOURCES_DIR);
185 186 187 188 189 190 191 192 193 194
		_qApp->setProperty("sharePath", sharePath);

		QSettings::setDefaultFormat(QSettings::IniFormat);
		_qApp->addLibraryPath(resolvePath(KUMIR2_LIBS_DIR));
		_qApp->addLibraryPath(resolvePath(KUMIR2_PLUGINS_DIR));
		ExtensionSystem::PluginManager *manager = ExtensionSystem::PluginManager::instance();
		manager->setPluginPath(resolvePath(KUMIR2_PLUGINS_DIR));
		manager->setSharePath(sharePath);
		QString error;
		qDebug() << "Initialized plugin manager";
195 196

		QByteArray templ = currentTemplate;
197 198 199 200 201 202 203 204
		for (int i = 1; i < arguments.size(); i++) {
			QByteArray arg = arguments[i].toLatin1();
			if (arg.startsWith("[") && arg.endsWith("]")) {
				templ = arg.mid(1, arg.length() - 2);
			}
		}
		qDebug() << "Loading plugins by template: " << templ;
		error = manager->loadPluginsByTemplate(templ);
205
		if (!_gui && manager->isGuiRequired()) {
206 207 208 209 210
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
			showErrorMessage("Requires X11 session to run this configuration");
			_qApp->exit(1);
211
			return;
212
		}
213

214 215 216 217 218 219
		if (!error.isEmpty()) {
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
			showErrorMessage(error);
			_qApp->exit(1);
220
			return;
221 222 223 224
		}

		qDebug() << "Done loading all plugins by template";

225
		if (helpMode) {
226 227 228
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
229
			QString msg = manager->commandLineHelp();
230
#ifdef Q_OS_WIN32
231 232
			QTextCodec *codec = QTextCodec::codecForName("CP866");
			fprintf(stderr, "%s", codec->fromUnicode(msg).constData());
233
#else
234
			fprintf(stderr, "%s", msg.toLocal8Bit().data());
235
#endif
236 237 238 239
			_qApp->exit(0);
			return;
		}

240 241 242 243
		if (versionMode) {
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
244 245 246 247
			fprintf(stderr, "%s\n", qPrintable(_qApp->applicationVersion()));
			_qApp->exit(0);
			return;
		}
248

249 250 251 252 253 254 255 256 257
		qDebug() << "Begin plugins initialization";
		error = manager->initializePlugins();
		if (!error.isEmpty()) {
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
			showErrorMessage(error);
			_qApp->exit(_qApp->property("returnCode").isValid()
				? _qApp->property("returnCode").toInt() : 1);
258
			return;
259
		}
260

261 262 263
		// GUI requirement may be changed as result of plugins initialization,
		// so check it again
		qDebug() << "Plugins initialization done";
264 265 266 267
		if (!_gui && manager->isGuiRequired()) {
			if (_splashScreen) {
				_splashScreen->finish(0);
			}
268 269 270
			showErrorMessage("Requires X11 session to run this configuration");
			_qApp->exit(_qApp->property("returnCode").isValid()
				? _qApp->property("returnCode").toInt() : 1);
271
			return;
272
		}
273

274 275
		if (_splashScreen) {
			_splashScreen->finish(0);
276
			_splashScreen = 0;
277
		}
278

279 280 281 282 283 284 285
		qDebug() << "Starting entry point plugin";
		error = manager->start();
		if (!error.isEmpty()) {
			showErrorMessage(error);
			_qApp->exit(_qApp->property("returnCode").isValid()
				? _qApp->property("returnCode").toInt() : 1);
		}
286

287 288 289 290 291
		if (!manager->isGuiRequired()) {
			_qApp->quit();
		}
	}

Alexander A. Maly's avatar
Alexander A. Maly committed
292
	bool eventFilter(QObject *obj, QEvent *event)
293 294 295 296 297 298 299 300 301 302 303 304 305
	{
		if (event->type() == QEvent::Timer && obj == _qApp && !_started) {
			_started = true;
			_qApp->killTimer(_timerId);
			qDebug() << "Begin initialization";
			initialize();
			qDebug() << "Initialization done";
			return true;
		} else {
			return QObject::eventFilter(obj, event);
		}
	}

Alexander A. Maly's avatar
Alexander A. Maly committed
306
	int main()
307
	{
308 309
		int dt = _splashScreen ? 250 : 50;
		_timerId = _qApp->startTimer(dt);
310 311 312 313 314 315 316 317
		int ret = _qApp->exec();
		if (ret == 0) {
			return _qApp->property("returnCode").isValid()
				? _qApp->property("returnCode").toInt() : 0;
		} else {
			return ret;
		}
	}
318

319
private:
320 321 322
	QCoreApplication *_qApp;
	int _timerId;
	QSplashScreen *_splashScreen;
323
	bool _started, _gui;
Victor Yacovlev's avatar
Victor Yacovlev committed
324 325
};

326

327 328
static bool
setup_custom_vendor_information()
329
{
330 331 332 333 334 335
	QString appName;
	QString appVendor;
	QString appVersion;
	QString appLicenseFileName;
	QString appAboutFileName;
	bool result = false;
336
#ifdef APP_NAME
337 338
	appName = QString::fromLatin1(APP_NAME);
	result = true;
339 340
#endif
#ifdef APP_NAME_ru
341 342
	appName = QString::fromUtf8(APP_NAME_ru);
	result = true;
343 344
#endif
#ifdef APP_VENDOR
345
	appVendor = QString::fromLatin1(APP_VENDOR);
346 347
#endif
#ifdef APP_VENDOR_RU
348
	appVendor = QString::fromUtf8(APP_VENDOR_ru);
349 350
#endif
#ifdef APP_VERSION
351
	appVersion = QString::fromLatin1(APP_VERSION);
352 353
#endif
#ifdef APP_LICENSE
354
	appLicenseFileName = ":/kumir2-launcher/" + QString::fromLatin1(APP_LICENSE);
355 356
#endif
#ifdef APP_LICENSE_ru
357
	appLicenseFileName = ":/kumir2-launcher/" + QString::fromLatin1(APP_LICENSE_ru);
358 359
#endif
#ifdef APP_ABOUT
360
	appAboutFileName = ":/kumir2-launcher/" + QString::fromLatin1(APP_ABOUT);
361 362
#endif
#ifdef APP_ABOUT_ru
363
	appAboutFileName = ":/kumir2-launcher/" + QString::fromLatin1(APP_ABOUT_ru);
364
#endif
365
	if (appName.length() > 0) {
366
		qApp->setProperty("customAppName", appName);
367 368
	}
	if (appVendor.length() > 0) {
369
		qApp->setProperty("customAppVendor", appVendor);
370 371
	}
	if (appVersion.length() > 0) {
372
		qApp->setProperty("customAppVersion", appVersion);
373 374
	}
	if (appLicenseFileName.length() > 0) {
375
		qApp->setProperty("customAppLicense", appLicenseFileName);
376 377
	}
	if (appAboutFileName.length() > 0) {
378
		qApp->setProperty("customAppAbout", appAboutFileName);
379 380
	}
	return result;
381 382
}

383

victor's avatar
 
victor committed
384
int main(int argc, char **argv)
385
{
386 387 388 389
	guiMode = false;
	pipeMode = false;
	versionMode = false;
	helpMode = false;
390

391 392 393
#if defined(WINDOW_ICON) || defined(SPLASHSCREEN)
	guiMode = true;
#endif
394

395
#ifdef Q_OS_LINUX
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
	guiMode = guiMode && getenv("DISPLAY") != 0;
#endif

	for (int i = 1; i < argc; i++) {
		QString arg = argv[i];
		if (arg == "--help" || arg == "-h" || arg == "/?") {
			helpMode = true;
		} else if (arg == "--version") {
			versionMode = true;
		} else if (arg == "--pipe" || arg == "-p") {
			pipeMode = true;
		} else if (arg == "--nopipe") {
			pipeMode = false;
		} else if (arg == "--gui") {
			guiMode = true;
		} else if (arg == "--nogui") {
			guiMode = false;
		} else if (arg.startsWith("--template=")) {
			currentTemplate = arg.toLatin1().mid(11);
		} else if (!arg.startsWith("-")) {
			break;
		}
	}

#if QT_VERSION < 0x050000
	qInstallMsgHandler((!pipeMode) ? LoggerMessageOutput : ConsoleMessageOutput);
#else
	qInstallMessageHandler((!pipeMode) ? LoggerMessageOutput : ConsoleMessageOutput);
victor's avatar
 
victor committed
424
#endif
425

426
	Application *app = new Application(argc, argv, guiMode);
427

428
#ifdef WINDOW_ICON
429
	if (guiMode) {
430 431 432 433 434
		QApplication *guiApp = qobject_cast<QApplication *>(qApp);
		QString imgPath = ":/kumir2-launcher/" + QString::fromLatin1(WINDOW_ICON);
		QIcon icon(imgPath);
		guiApp->setWindowIcon(icon);
	}
435
#endif
436 437 438 439 440 441 442

	QLocale russian = QLocale("ru_RU");
	QLocale::setDefault(russian);

	qApp->addLibraryPath(resolvePath(KUMIR2_LIBS_DIR));
	qApp->addLibraryPath(resolvePath(KUMIR2_PLUGINS_DIR));

443 444 445 446 447
	QString gitHash = QString::fromLatin1(GIT_HASH);
	QString gitTag = QString::fromLatin1(GIT_TAG);
	QString gitBranch = QString::fromLatin1(GIT_BRANCH);
	QDateTime gitTimeStamp = QDateTime::fromTime_t(QString::fromLatin1(GIT_TIMESTAMP).toUInt());

448 449 450 451
	qApp->setApplicationVersion(gitTag.length() > 0 && gitTag != "unknown"
		? gitTag : gitBranch + "/" + gitHash);
	qApp->setProperty("gitTimeStamp", gitTimeStamp);

452 453
	bool customInfo = setup_custom_vendor_information();
	Q_UNUSED(customInfo);
454

victor's avatar
 
victor committed
455
#ifdef SPLASHSCREEN
456
	QSplashScreen *splashScreen = 0;
457
	if (guiMode && !helpMode && !versionMode) {
458 459 460 461 462 463 464 465 466 467 468 469
		QString imgPath = ":/kumir2-launcher/" + QString::fromLatin1(SPLASHSCREEN);
		splashScreen = new QSplashScreen();
		QImage img(imgPath);
		QPainter p(&img);
		p.setPen(QColor(Qt::black));
		p.setBrush(QColor(Qt::black));
		QFont f = p.font();

		f.setPixelSize(12);
		p.setFont(f);

		QString v;
470
		if (customInfo) {
471 472 473 474 475 476 477
			v = qApp->property("customAppVersion").toString() + " ";
			v += "[based on Kumir ";
		}
		v = qApp->applicationVersion();
		if (qApp->property("gitHash").isValid()) {
			v += " (GIT " + qApp->property("gitHash").toString() + ")";
		}
478
		if (customInfo) {
479 480 481 482 483 484 485 486 487 488 489 490
			v += "]";
		}
		int tw = QFontMetrics(f).width(v);
		int th = QFontMetrics(f).height() * 5;
		int x = img.width() - tw - 8;
		int y = 8;
		p.drawText(x, y, tw, th, 0, v);
		p.end();
		QPixmap px = QPixmap::fromImage(img);
		splashScreen->setPixmap(px);
		splashScreen->show();
		app->setSplashScreen(splashScreen);
491
		qApp->processEvents();
492 493 494 495 496 497 498
	}
#endif

	int ret = app->main();
	ExtensionSystem::PluginManager::destroy();
	delete app;
	return ret;
victor's avatar
 
victor committed
499 500
}