1 // Copyright (C) 2014-2018 Manuel Schneider
2
3 #include <QApplication>
4 #include <QCommandLineParser>
5 #include <QDebug>
6 #include <QDesktopServices>
7 #include <QDir>
8 #include <QMenu>
9 #include <QMessageBox>
10 #include <QSettings>
11 #include <QSqlDatabase>
12 #include <QSqlDriver>
13 #include <QSqlError>
14 #include <QSqlQuery>
15 #include <QSqlRecord>
16 #include <QStandardPaths>
17 #include <QTime>
18 #include <QTimer>
19 #include <QUrl>
20 #include <QtNetwork/QLocalServer>
21 #include <QtNetwork/QLocalSocket>
22 #include <csignal>
23 #include <functional>
24 #include "globalshortcut/hotkeymanager.h"
25 #include "xdg/iconlookup.h"
26 #include "extensionmanager.h"
27 #include "albert/frontend.h"
28 #include "frontendmanager.h"
29 #include "pluginspec.h"
30 #include "querymanager.h"
31 #include "settingswidget/settingswidget.h"
32 #include "telemetry.h"
33 #include "trayicon.h"
34 using namespace Core;
35 using namespace GlobalShortcut;
36
37 static void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &message);
38 static void dispatchMessage();
39 static void printReport();
40
41 // Core components
42 static QApplication *app;
43 static ExtensionManager *extensionManager;
44 static FrontendManager *frontendManager;
45 static QueryManager *queryManager;
46 static HotkeyManager *hotkeyManager;
47 static SettingsWidget *settingsWidget;
48 static TrayIcon *trayIcon;
49 static Telemetry *telemetry;
50 static QMenu *trayIconMenu;
51 static QLocalServer *localServer;
52
53
main(int argc,char ** argv)54 int main(int argc, char **argv) {
55
56 // Parse commandline
57 QCommandLineParser parser;
58 parser.setApplicationDescription("Albert is still in alpha. These options may change in future versions.");
59 parser.addHelpOption();
60 parser.addVersionOption();
61 parser.addOption(QCommandLineOption({"k", "hotkey"}, "Overwrite the hotkey to use.", "hotkey"));
62 parser.addOption(QCommandLineOption({"p", "plugin-dirs"}, "Set the plugin dirs to use. Comma separated.", "directory"));
63 parser.addOption(QCommandLineOption({"r", "report"}, "Print issue report."));
64 parser.addPositionalArgument("command", "Command to send to a running instance, if any. (show, hide, toggle)", "[command]");
65
66 /*
67 * IPC/SINGLETON MECHANISM (Client)
68 * For performance purposes this has been optimized by using a QCoreApp
69 */
70 QString socketPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+"/socket";
71 {
72 QCoreApplication *capp = new QCoreApplication(argc, argv);
73 capp->setApplicationName("albert");
74 capp->setApplicationVersion(ALBERT_VERSION);
75 parser.process(*capp);
76
77 if (parser.isSet("report")){
78 printReport();
79 return EXIT_SUCCESS;
80 }
81
82 const QStringList args = parser.positionalArguments();
83 QLocalSocket socket;
84 socket.connectToServer(socketPath);
85 if ( socket.waitForConnected(500) ) { // Should connect instantly
86 // If there is a command send it
87 if ( args.count() != 0 ){
88 socket.write(args.join(' ').toUtf8());
89 socket.flush();
90 socket.waitForReadyRead(500);
91 if (socket.bytesAvailable())
92 qInfo().noquote() << socket.readAll();
93 }
94 else
95 qInfo("There is another instance of albert running.");
96 socket.close();
97 ::exit(EXIT_SUCCESS);
98 } else if ( args.count() == 1 ) {
99 qInfo("There is no other instance of albert running.");
100 ::exit(EXIT_FAILURE);
101 }
102
103 delete capp;
104 }
105
106
107 /*
108 * INITIALIZE APPLICATION
109 */
110 {
111 QSettings::setPath(QSettings::defaultFormat(), QSettings::UserScope,
112 QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
113 QSettings settings(qApp->applicationName());
114
115 qInstallMessageHandler(myMessageOutput);
116
117 qDebug() << "Initializing application";
118 #if QT_VERSION >= 0x050600 // TODO: Remove when 18.04 is released
119 if (!qEnvironmentVariableIsSet("QT_DEVICE_PIXEL_RATIO")
120 && !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR")
121 && !qEnvironmentVariableIsSet("QT_SCALE_FACTOR")
122 && !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS"))
123 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
124 QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
125 #endif
126 app = new QApplication(argc, argv);
127 app->setApplicationName("albert");
128 app->setApplicationDisplayName("Albert");
129 app->setApplicationVersion(ALBERT_VERSION);
130 app->setQuitOnLastWindowClosed(false);
131 QString icon = XDG::IconLookup::iconPath("albert");
132 if ( icon.isEmpty() ) icon = ":app_icon";
133 app->setWindowIcon(QIcon(icon));
134
135
136
137 /*
138 * IPC/SINGLETON MECHANISM (Server)
139 */
140
141 // Remove pipes potentially leftover after crash
142 QLocalServer::removeServer(socketPath);
143
144 // Create server and handle messages
145 qDebug() << "Creating IPC server";
146 localServer = new QLocalServer;
147 if ( !localServer->listen(socketPath) )
148 qWarning() << "Local server could not be created. IPC will not work! Reason:"
149 << localServer->errorString();
150
151 // Handle incoming messages
152 QObject::connect(localServer, &QLocalServer::newConnection, dispatchMessage);
153
154
155
156 /*
157 * INITIALIZE PATHS
158 */
159
160 // Make sure data, cache and config dir exists
161 qDebug() << "Initializing mandatory paths";
162 QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
163 QString cacheLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
164 QString configLocation = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
165 for ( const QString &location : {dataLocation, cacheLocation, configLocation} )
166 if (!QDir(location).mkpath("."))
167 qFatal("Could not create dir: %s", qPrintable(location));
168
169
170
171 /*
172 * ADJUST PATHS OF FILES OF OLDER VERSIONS
173 */
174
175 // If there is a firstRun file, rename it to lastVersion (since v0.11)
176 if ( QFile::exists(QString("%1/firstrun").arg(dataLocation)) ) {
177 qDebug() << "Renaming 'firstrun' to 'last_used_version'";
178 QFile::rename(QString("%1/firstrun").arg(dataLocation),
179 QString("%1/last_used_version").arg(dataLocation));
180 }
181
182 // Move old config for user convenience (since v0.13)
183 QFileInfo oldcfg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/albert.conf");
184 QFileInfo newcfg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/albert/albert.conf");
185 if (oldcfg.exists()){
186 if (newcfg.exists())
187 QFile::remove(newcfg.filePath());
188 QFile::rename(oldcfg.filePath(), newcfg.filePath());
189 }
190
191 // If there is a lastVersion file move it to config (since v0.13)
192 if ( QFile::exists(QString("%1/last_used_version").arg(dataLocation)) ) {
193 qDebug() << "Moving 'last_used_version' to config path";
194 QFile::rename(QString("%1/last_used_version").arg(dataLocation),
195 QString("%1/last_used_version").arg(configLocation));
196 }
197
198 // If move database from old location in cache to config (since v0.14.7)
199 if ( QFile::exists(QString("%1/core.db").arg(cacheLocation)) ){
200 qInfo() << "Moving 'core.db' to config path";
201 QFile::rename(QString("%1/core.db").arg(cacheLocation),
202 QString("%1/core.db").arg(configLocation));
203 }
204
205
206 /*
207 * MISC
208 */
209
210 // Quit gracefully on unix signals
211 qDebug() << "Setup signal handlers";
212 for ( int sig : { SIGINT, SIGTERM, SIGHUP, SIGPIPE } ) {
213 signal(sig, [](int){
214 QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection);
215 });
216 }
217
218 // Print a message if the app was not terminated graciously
219 qDebug() << "Creating running indicator file";
220 QString filePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+"/running";
221 if (QFile::exists(filePath)){
222 qWarning() << "Application has not been terminated graciously.";
223 } else {
224 // Create the running indicator file
225 QFile file(filePath);
226 if (!file.open(QIODevice::WriteOnly))
227 qWarning() << "Could not create file:" << filePath;
228 file.close();
229 }
230
231
232
233 /*
234 * INITIALIZE CORE DATABASE
235 */
236
237
238 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
239 if ( !db.isValid() )
240 qFatal("No sqlite available");
241 if (!db.driver()->hasFeature(QSqlDriver::Transactions))
242 qFatal("QSqlDriver::Transactions not available.");
243 db.setDatabaseName(QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)).filePath("core.db"));
244 if (!db.open())
245 qFatal("Unable to establish a database connection.");
246
247 db.transaction();
248
249 QSqlQuery q(db);
250 if (!q.exec("CREATE TABLE IF NOT EXISTS query_handler ( "
251 " id INTEGER PRIMARY KEY NOT NULL, "
252 " string_id TEXT UNIQUE NOT NULL "
253 "); "))
254 qFatal("Unable to create table 'query_handler': %s", q.lastError().text().toUtf8().constData());
255
256 if (!q.exec("CREATE TABLE IF NOT EXISTS query ( "
257 " id INTEGER PRIMARY KEY, "
258 " input TEXT NOT NULL, "
259 " cancelled INTEGER NOT NULL, "
260 " runtime INTEGER NOT NULL, "
261 " timestamp INTEGER DEFAULT CURRENT_TIMESTAMP "
262 "); "))
263 qFatal("Unable to create table 'query': %s", q.lastError().text().toUtf8().constData());
264
265 if (!q.exec("CREATE TABLE IF NOT EXISTS execution ( "
266 " query_id INTEGER NOT NULL REFERENCES query(id) ON UPDATE CASCADE, "
267 " handler_id INTEGER NOT NULL REFERENCES query_handler(id) ON UPDATE CASCADE, "
268 " runtime INTEGER NOT NULL, "
269 " PRIMARY KEY (query_id, handler_id) "
270 ") WITHOUT ROWID; "))
271 qFatal("Unable to create table 'execution': %s", q.lastError().text().toUtf8().constData());
272
273 if (!q.exec("CREATE TABLE IF NOT EXISTS activation ( "
274 " query_id INTEGER PRIMARY KEY NOT NULL REFERENCES query(id) ON UPDATE CASCADE, "
275 " item_id TEXT NOT NULL "
276 "); "))
277 qFatal("Unable to create table 'activation': %s", q.lastError().text().toUtf8().constData());
278
279 if (!q.exec("DELETE FROM query WHERE julianday('now')-julianday(timestamp)>30; "))
280 qWarning("Unable to cleanup 'query' table.");
281
282 if (!q.exec("CREATE TABLE IF NOT EXISTS conf(key TEXT UNIQUE, value TEXT); "))
283 qFatal("Unable to create table 'conf': %s", q.lastError().text().toUtf8().constData());
284
285 db.commit();
286
287
288 /*
289 * INITIALIZE APPLICATION COMPONENTS
290 */
291
292 qDebug() << "Initializing core components";
293
294 // Define plugindirs
295 QStringList pluginDirs;
296 if ( parser.isSet("plugin-dirs") )
297 pluginDirs = parser.value("plugin-dirs").split(',');
298 else {
299 #if defined __linux__ || defined __FreeBSD__
300 QStringList dirs = {
301 #if defined MULTIARCH_TUPLE
302 QFileInfo("/usr/lib/" MULTIARCH_TUPLE).canonicalFilePath(),
303 #endif
304 #if defined __linux__
305 QFileInfo("/usr/lib/").canonicalFilePath(),
306 QFileInfo("/usr/lib64/").canonicalFilePath(),
307 QFileInfo("/usr/local/lib/").canonicalFilePath(),
308 QFileInfo("/usr/local/lib64/").canonicalFilePath(),
309 #endif
310 #if defined __FreeBSD__
311 QFileInfo("/usr/lib/").canonicalFilePath(),
312 QFileInfo("/usr/local/lib/").canonicalFilePath(),
313 #endif
314 QDir::home().filePath(".local/lib/"),
315 QDir::home().filePath(".local/lib64/")
316 };
317
318 dirs.removeDuplicates();
319
320 for ( const QString& dir : dirs ) {
321 QFileInfo fileInfo = QFileInfo(QDir(dir).filePath("albert/plugins"));
322 if ( fileInfo.isDir() )
323 pluginDirs.push_back(fileInfo.canonicalFilePath());
324 }
325 #elif defined __APPLE__
326 throw "Not implemented";
327 #elif defined _WIN32
328 throw "Not implemented";
329 #endif
330 }
331
332 frontendManager = new FrontendManager(pluginDirs);
333 extensionManager = new ExtensionManager(pluginDirs);
334 extensionManager->reloadExtensions();
335 hotkeyManager = new HotkeyManager;
336 if ( parser.isSet("hotkey") ) {
337 QString hotkey = parser.value("hotkey");
338 if ( !hotkeyManager->registerHotkey(hotkey) )
339 qFatal("Failed to set hotkey to %s.", hotkey.toLocal8Bit().constData());
340 } else if ( settings.contains("hotkey") ) {
341 QString hotkey = settings.value("hotkey").toString();
342 if ( !hotkeyManager->registerHotkey(hotkey) )
343 qFatal("Failed to set hotkey to %s.", hotkey.toLocal8Bit().constData());
344 }
345 queryManager = new QueryManager(extensionManager);
346 telemetry = new Telemetry;
347 trayIcon = new TrayIcon;
348 trayIconMenu = new QMenu;
349 settingsWidget = new SettingsWidget(extensionManager,
350 frontendManager,
351 queryManager,
352 hotkeyManager,
353 trayIcon,
354 telemetry);
355
356 QAction* showAction = new QAction("Show", trayIconMenu);
357 showAction->setIcon(app->style()->standardIcon(QStyle::SP_TitleBarMaxButton));
358 trayIconMenu->addAction(showAction);
359
360 QAction* settingsAction = new QAction("Settings", trayIconMenu);
361 settingsAction->setIcon(app->style()->standardIcon(QStyle::SP_FileDialogDetailedView));
362 trayIconMenu->addAction(settingsAction);
363 QObject::connect(settingsAction, &QAction::triggered, [](){
364 settingsWidget->show();
365 settingsWidget->raise();
366 });
367
368 QAction* docsAction = new QAction("Open docs", trayIconMenu);
369 docsAction->setIcon(app->style()->standardIcon(QStyle::SP_DialogHelpButton));
370 trayIconMenu->addAction(docsAction);
371 QObject::connect(docsAction, &QAction::triggered, [](){
372 QDesktopServices::openUrl(QUrl("https://albertlauncher.github.io/docs/"));
373 });
374
375 trayIconMenu->addSeparator();
376 QAction* quitAction = new QAction("Quit", trayIconMenu);
377 quitAction->setIcon(app->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
378 trayIconMenu->addAction(quitAction);
379 QObject::connect(quitAction, &QAction::triggered, app, &QApplication::quit);
380
381 trayIcon->setContextMenu(trayIconMenu);
382
383
384
385 /*
386 * DETECT FIRST RUN AND VERSION CHANGE
387 */
388
389 qDebug() << "Checking last used version";
390 QFile file(QString("%1/last_used_version").arg(configLocation));
391 if ( file.exists() ) {
392 // Read last used version
393 if ( file.open(QIODevice::ReadOnly|QIODevice::Text) ) {
394 QString lastUsedVersion;
395 QTextStream(&file) >> lastUsedVersion;
396 file.close();
397
398 // Show newsbox in case of major version change
399 if ( app->applicationVersion().section('.', 1, 1) != lastUsedVersion.section('.', 1, 1) ){
400 // Do whatever is neccessary on first run
401 QMessageBox(QMessageBox::Information, "Major version changed",
402 QString("You are now using Albert %1. Albert is still in the alpha "
403 "stage. This means things may change unexpectedly. Check "
404 "the <a href=\"https://albertlauncher.github.io/news/\">"
405 "news</a> to read about the things that changed.")
406 .arg(app->applicationVersion())).exec();
407 }
408 }
409 else
410 qCritical() << qPrintable(QString("Could not open file %1: %2,. Config migration may fail.")
411 .arg(file.fileName(), file.errorString()));
412 } else {
413 // Do whatever is neccessary on first run
414 QMessageBox(QMessageBox::Information, "First run",
415 "Seems like this is the first time you run Albert. Albert is "
416 "standalone, free and open source software. Note that Albert is not "
417 "related to or affiliated with any other projects or corporations.\n\n"
418 "You should set a hotkey and enable some extensions.").exec();
419 settingsWidget->show();
420 }
421
422 // Write the current version into the file
423 if ( file.open(QIODevice::WriteOnly|QIODevice::Text) ) {
424 QTextStream out(&file);
425 out << app->applicationVersion();
426 file.close();
427 } else
428 qCritical() << qPrintable(QString("Could not open file %1: %2").arg(file.fileName(), file.errorString()));
429
430
431
432 /*
433 * SIGNALING
434 */
435
436 // Define a lambda that connects a new frontend
437 auto connectFrontend = [&](Frontend *f){
438
439 QObject::connect(hotkeyManager, &HotkeyManager::hotKeyPressed,
440 f, &Frontend::toggleVisibility);
441
442 QObject::connect(queryManager, &QueryManager::resultsReady,
443 f, &Frontend::setModel);
444
445 QObject::connect(showAction, &QAction::triggered,
446 f, &Frontend::setVisible);
447
448 QObject::connect(trayIcon, &TrayIcon::activated,
449 f, [=](QSystemTrayIcon::ActivationReason reason){
450 if( reason == QSystemTrayIcon::ActivationReason::Trigger)
451 f->toggleVisibility();
452 });
453
454 QObject::connect(f, &Frontend::settingsWidgetRequested, [](){
455 settingsWidget->show();
456 settingsWidget->raise();
457 settingsWidget->activateWindow();
458 });
459
460 QObject::connect(f, &Frontend::widgetShown, [f](){
461 queryManager->setupSession();
462 queryManager->startQuery(f->input());
463 });
464
465 QObject::connect(f, &Frontend::widgetHidden,
466 queryManager, &QueryManager::teardownSession);
467
468 QObject::connect(f, &Frontend::inputChanged,
469 queryManager, &QueryManager::startQuery);
470 };
471
472 // Connect the current frontend
473 connectFrontend(frontendManager->currentFrontend());
474
475 // Connect new frontends
476 QObject::connect(frontendManager, &FrontendManager::frontendChanged, connectFrontend);
477 }
478
479
480 /*
481 * ENTER EVENTLOOP
482 */
483
484 qDebug() << "Entering eventloop";
485 int retval = app->exec();
486
487
488 /*
489 * FINALIZE APPLICATION
490 */
491
492 qDebug() << "Cleaning up core components";
493 delete settingsWidget;
494 delete trayIconMenu;
495 delete trayIcon;
496 delete queryManager;
497 delete hotkeyManager;
498 delete extensionManager;
499 delete frontendManager;
500
501 qDebug() << "Shutting down IPC server";
502 localServer->close();
503
504 // Delete the running indicator file
505 qDebug() << "Deleting running indicator file";
506 QFile::remove(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)+"/running");
507
508 qDebug() << "Quit";
509 return retval;
510 }
511
512
513 /** ***************************************************************************/
myMessageOutput(QtMsgType type,const QMessageLogContext & context,const QString & message)514 void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &message) {
515 switch (type) {
516 case QtDebugMsg:
517 fprintf(stdout, "%s \x1b[34;1m[DEBG:%s]\x1b[0m \x1b[3m%s\x1b[0m\n",
518 QTime::currentTime().toString().toLocal8Bit().constData(),
519 context.category,
520 message.toLocal8Bit().constData());
521 break;
522 case QtInfoMsg:
523 fprintf(stdout, "%s \x1b[32;1m[INFO:%s]\x1b[0m %s\n",
524 QTime::currentTime().toString().toLocal8Bit().constData(),
525 context.category,
526 message.toLocal8Bit().constData());
527 break;
528 case QtWarningMsg:
529 fprintf(stdout, "%s \x1b[33;1m[WARN:%s]\x1b[0;1m %s\x1b[0m\n",
530 QTime::currentTime().toString().toLocal8Bit().constData(),
531 context.category,
532 message.toLocal8Bit().constData());
533 break;
534 case QtCriticalMsg:
535 fprintf(stdout, "%s \x1b[31;1m[CRIT:%s]\x1b[0;1m %s\x1b[0m\n",
536 QTime::currentTime().toString().toLocal8Bit().constData(),
537 context.category,
538 message.toLocal8Bit().constData());
539 break;
540 case QtFatalMsg:
541 fprintf(stderr, "%s \x1b[41;30;4m[FATAL:%s]\x1b[0;1m %s -- [%s]\x1b[0m\n",
542 QTime::currentTime().toString().toLocal8Bit().constData(),
543 context.category,
544 message.toLocal8Bit().constData(),
545 context.function);
546 exit(1);
547 }
548 fflush(stdout);
549 }
550
551
552 /** ***************************************************************************/
dispatchMessage()553 void dispatchMessage() {
554 QLocalSocket* socket = localServer->nextPendingConnection(); // Should be safe
555 socket->waitForReadyRead(500);
556 if (socket->bytesAvailable()) {
557 QString msg = QString::fromLocal8Bit(socket->readAll());
558 if ( msg.startsWith("show")) {
559 if (msg.size() > 5) {
560 QString input = msg.mid(5);
561 frontendManager->currentFrontend()->setInput(input);
562 }
563 frontendManager->currentFrontend()->setVisible(true);
564 socket->write("Application set visible.");
565 } else if ( msg == "hide") {
566 frontendManager->currentFrontend()->setVisible(false);
567 socket->write("Application set invisible.");
568 } else if ( msg == "toggle") {
569 frontendManager->currentFrontend()->toggleVisibility();
570 socket->write("Visibility toggled.");
571 } else
572 socket->write("Command not supported.");
573 }
574 socket->flush();
575 socket->close();
576 socket->deleteLater();
577 }
578
579
580 /** ***************************************************************************/
printReport()581 static void printReport()
582 {
583 const uint8_t w = 22;
584 qInfo().noquote() << QString("%1: %2").arg("Albert version", w).arg(qApp->applicationVersion());
585 qInfo().noquote() << QString("%1: %2").arg("Build date", w).arg(__DATE__ " " __TIME__);
586
587 qInfo().noquote() << QString("%1: %2").arg("Qt version", w).arg(qVersion());
588 qInfo().noquote() << QString("%1: %2").arg("QT_QPA_PLATFORMTHEME", w).arg(QString::fromLocal8Bit(qgetenv("QT_QPA_PLATFORMTHEME")));
589
590 qInfo().noquote() << QString("%1: %2").arg("Binary location", w).arg(qApp->applicationFilePath());
591
592 qInfo().noquote() << QString("%1: %2").arg("PWD", w).arg(QString::fromLocal8Bit(qgetenv("PWD")));
593 qInfo().noquote() << QString("%1: %2").arg("SHELL", w).arg(QString::fromLocal8Bit(qgetenv("SHELL")));
594 qInfo().noquote() << QString("%1: %2").arg("LANG", w).arg(QString::fromLocal8Bit(qgetenv("LANG")));
595
596 qInfo().noquote() << QString("%1: %2").arg("XDG_SESSION_TYPE", w).arg(QString::fromLocal8Bit(qgetenv("XDG_SESSION_TYPE")));
597 qInfo().noquote() << QString("%1: %2").arg("XDG_CURRENT_DESKTOP", w).arg(QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")));
598 qInfo().noquote() << QString("%1: %2").arg("DESKTOP_SESSION", w).arg(QString::fromLocal8Bit(qgetenv("DESKTOP_SESSION")));
599 qInfo().noquote() << QString("%1: %2").arg("XDG_SESSION_DESKTOP", w).arg(QString::fromLocal8Bit(qgetenv("XDG_SESSION_DESKTOP")));
600
601 qInfo().noquote() << QString("%1: %2").arg("OS", w).arg(QSysInfo::prettyProductName());
602 qInfo().noquote() << QString("%1: %2/%3").arg("OS (type/version)", w).arg(QSysInfo::productType(), QSysInfo::productVersion());
603
604 qInfo().noquote() << QString("%1: %2").arg("Build ABI", w).arg(QSysInfo::buildAbi());
605 qInfo().noquote() << QString("%1: %2/%3").arg("Arch (build/current)", w).arg(QSysInfo::buildCpuArchitecture(), QSysInfo::currentCpuArchitecture());
606
607 qInfo().noquote() << QString("%1: %2/%3").arg("Kernel (type/version)", w).arg(QSysInfo::kernelType(), QSysInfo::kernelVersion());
608 }
609