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