File indexing completed on 2024-03-24 17:07:31

0001 /*
0002 *  Copyright 2016  Smith AR <audoban@openmailbox.org>
0003 *                  Michail Vourlakos <mvourlakos@gmail.com>
0004 *
0005 *  This file is part of Latte-Dock
0006 *
0007 *  Latte-Dock is free software; you can redistribute it and/or
0008 *  modify it under the terms of the GNU General Public License as
0009 *  published by the Free Software Foundation; either version 2 of
0010 *  the License, or (at your option) any later version.
0011 *
0012 *  Latte-Dock is distributed in the hope that it will be useful,
0013 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015 *  GNU General Public License for more details.
0016 *
0017 *  You should have received a copy of the GNU General Public License
0018 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 // local
0022 #include "config-latte.h"
0023 #include "lattecorona.h"
0024 #include "layouts/importer.h"
0025 #include "../liblatte2/types.h"
0026 
0027 // C++
0028 #include <memory>
0029 #include <csignal>
0030 
0031 // Qt
0032 #include <QApplication>
0033 #include <QDebug>
0034 #include <QQuickWindow>
0035 #include <QCommandLineParser>
0036 #include <QCommandLineOption>
0037 #include <QDir>
0038 #include <QLockFile>
0039 #include <QSharedMemory>
0040 
0041 // KDE
0042 #include <KCrash>
0043 #include <KLocalizedString>
0044 #include <KAboutData>
0045 #include <KDBusService>
0046 #include <KQuickAddons/QtQuickSettings>
0047 
0048 //! COLORS
0049 #define CNORMAL  "\e[0m"
0050 #define CIGREEN  "\e[1;32m"
0051 #define CGREEN   "\e[0;32m"
0052 #define CICYAN   "\e[1;36m"
0053 #define CCYAN    "\e[0;36m"
0054 #define CIRED    "\e[1;31m"
0055 #define CRED     "\e[0;31m"
0056 
0057 inline void configureAboutData();
0058 inline void detectPlatform(int argc, char **argv);
0059 
0060 int main(int argc, char **argv)
0061 {
0062     //Plasma scales itself to font DPI
0063     //on X, where we don't have compositor scaling, this generally works fine.
0064     //also there are bugs on older Qt, especially when it comes to fractional scaling
0065     //there's advantages to disabling, and (other than small context menu icons) few advantages in enabling
0066 
0067     //On wayland, it's different. Everything is simpler as all co-ordinates are in the same co-ordinate system
0068     //we don't have fractional scaling on the client so don't hit most the remaining bugs and
0069     //even if we don't use Qt scaling the compositor will try to scale us anyway so we have no choice
0070     if (!qEnvironmentVariableIsSet("PLASMA_USE_QT_SCALING")) {
0071         qunsetenv("QT_DEVICE_PIXEL_RATIO");
0072         QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
0073     } else {
0074         QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
0075     }
0076 
0077     QQuickWindow::setDefaultAlphaBuffer(true);
0078 
0079     const bool qpaVariable = qEnvironmentVariableIsSet("QT_QPA_PLATFORM");
0080     detectPlatform(argc, argv);
0081     QApplication app(argc, argv);
0082 
0083     if (!qpaVariable) {
0084         // don't leak the env variable to processes we start
0085         qunsetenv("QT_QPA_PLATFORM");
0086     }
0087 
0088     KQuickAddons::QtQuickSettings::init();
0089 
0090     KLocalizedString::setApplicationDomain("latte-dock");
0091     app.setWindowIcon(QIcon::fromTheme(QStringLiteral("latte-dock")));
0092     //protect from closing app when changing to "alternative session" and back
0093     app.setQuitOnLastWindowClosed(false);
0094 
0095     configureAboutData();
0096 
0097     QCommandLineParser parser;
0098     parser.addHelpOption();
0099     parser.addVersionOption();
0100     parser.addOptions({
0101         {{"r", "replace"}, i18nc("command line", "Replace the current Latte instance.")}
0102         , {{"d", "debug"}, i18nc("command line", "Show the debugging messages on stdout.")}
0103         , {{"cc", "clear-cache"}, i18nc("command line", "Clear qml cache. It can be useful after system upgrades.")}
0104         , {"default-layout", i18nc("command line", "Import and load default layout on startup.")}
0105         , {"available-layouts", i18nc("command line", "Print available layouts")}
0106         , {"layout", i18nc("command line", "Load specific layout on startup."), i18nc("command line: load", "layout_name")}
0107         , {"import-layout", i18nc("command line", "Import and load a layout."), i18nc("command line: import", "file_name")}
0108         , {"import-full", i18nc("command line", "Import full configuration."), i18nc("command line: import", "file_name")}
0109         , {"single", i18nc("command line", "Single layout memory mode. Only one layout is active at any case.")}
0110         , {"multiple", i18nc("command line", "Multiple layouts memory mode. Multiple layouts can be active at any time based on Activities running.")}
0111     });
0112 
0113     //! START: Hidden options for Developer and Debugging usage
0114     QCommandLineOption graphicsOption(QStringList() << QStringLiteral("graphics"));
0115     graphicsOption.setDescription(QStringLiteral("Draw boxes around of the applets."));
0116     graphicsOption.setFlags(QCommandLineOption::HiddenFromHelp);
0117     parser.addOption(graphicsOption);
0118 
0119     QCommandLineOption withWindowOption(QStringList() << QStringLiteral("with-window"));
0120     withWindowOption.setDescription(QStringLiteral("Open a window with much debug information"));
0121     withWindowOption.setFlags(QCommandLineOption::HiddenFromHelp);
0122     parser.addOption(withWindowOption);
0123 
0124     QCommandLineOption maskOption(QStringList() << QStringLiteral("mask"));
0125     maskOption.setDescription(QStringLiteral("Show messages of debugging for the mask (Only useful to devs)."));
0126     maskOption.setFlags(QCommandLineOption::HiddenFromHelp);
0127     parser.addOption(maskOption);
0128 
0129     QCommandLineOption timersOption(QStringList() << QStringLiteral("timers"));
0130     timersOption.setDescription(QStringLiteral("Show messages for debugging the timers (Only useful to devs)."));
0131     timersOption.setFlags(QCommandLineOption::HiddenFromHelp);
0132     parser.addOption(timersOption);
0133 
0134     QCommandLineOption spacersOption(QStringList() << QStringLiteral("spacers"));
0135     spacersOption.setDescription(QStringLiteral("Show visual indicators for debugging spacers (Only useful to devs)."));
0136     spacersOption.setFlags(QCommandLineOption::HiddenFromHelp);
0137     parser.addOption(spacersOption);
0138 
0139     QCommandLineOption overloadedIconsOption(QStringList() << QStringLiteral("overloaded-icons"));
0140     overloadedIconsOption.setDescription(QStringLiteral("Show visual indicators for debugging overloaded applets icons (Only useful to devs)."));
0141     overloadedIconsOption.setFlags(QCommandLineOption::HiddenFromHelp);
0142     parser.addOption(overloadedIconsOption);
0143 
0144     QCommandLineOption edgesOption(QStringList() << QStringLiteral("kwinedges"));
0145     graphicsOption.setDescription(QStringLiteral("Show visual window indicators for hidden screen edge windows."));
0146     graphicsOption.setFlags(QCommandLineOption::HiddenFromHelp);
0147     parser.addOption(edgesOption);
0148     //! END: Hidden options
0149 
0150     parser.process(app);
0151 
0152     //! print available-layouts
0153     if (parser.isSet(QStringLiteral("available-layouts"))) {
0154         QStringList layouts = Latte::Layouts::Importer::availableLayouts();
0155 
0156         if (layouts.count() > 0) {
0157             qInfo() << i18n("Available layouts that can be used to start Latte:");
0158 
0159             for (const auto &layout : layouts) {
0160                 qInfo() << "     " << layout;
0161             }
0162         } else {
0163             qInfo() << i18n("There are no available layouts, during startup Default will be used.");
0164         }
0165 
0166         qGuiApp->exit();
0167         return 0;
0168     }
0169 
0170     bool defaultLayoutOnStartup = false;
0171     int memoryUsage = -1;
0172     QString layoutNameOnStartup = "";
0173 
0174     //! --default-layout option
0175     if (parser.isSet(QStringLiteral("default-layout"))) {
0176         defaultLayoutOnStartup = true;
0177     } else if (parser.isSet(QStringLiteral("layout"))) {
0178         layoutNameOnStartup = parser.value(QStringLiteral("layout"));
0179 
0180         if (!Latte::Layouts::Importer::layoutExists(layoutNameOnStartup)) {
0181             qInfo() << i18nc("layout missing", "This layout doesn't exist in the system.");
0182             qGuiApp->exit();
0183             return 0;
0184         }
0185     }
0186 
0187     //! --replace option
0188     QString username = qgetenv("USER");
0189 
0190     if (username.isEmpty())
0191         username = qgetenv("USERNAME");
0192 
0193     QLockFile lockFile {QDir::tempPath() + "/latte-dock." + username + ".lock"};
0194 
0195     int timeout {100};
0196 
0197     if (parser.isSet(QStringLiteral("replace")) || parser.isSet(QStringLiteral("import-full"))) {
0198         qint64 pid{ -1};
0199 
0200         if (lockFile.getLockInfo(&pid, nullptr, nullptr)) {
0201             kill(static_cast<pid_t>(pid), SIGINT);
0202             timeout = -1;
0203         }
0204     }
0205 
0206     if (!lockFile.tryLock(timeout)) {
0207         qInfo() << i18n("An instance is already running!, use --replace to restart Latte");
0208         qGuiApp->exit();
0209         return 0;
0210     }
0211 
0212     //! clear-cache option
0213     if (parser.isSet(QStringLiteral("clear-cache"))) {
0214         QDir cacheDir(QDir::homePath() + "/.cache/lattedock/qmlcache");
0215 
0216         if (cacheDir.exists()) {
0217             cacheDir.removeRecursively();
0218             qDebug() << "Cache directory found and cleared...";
0219         }
0220     }
0221 
0222     //! import-full option
0223     if (parser.isSet(QStringLiteral("import-full"))) {
0224         bool imported = Latte::Layouts::Importer::importHelper(parser.value(QStringLiteral("import-full")));
0225 
0226         if (!imported) {
0227             qInfo() << i18n("The configuration cannot be imported");
0228             qGuiApp->exit();
0229             return 0;
0230         }
0231     }
0232 
0233     //! import-layout option
0234     if (parser.isSet(QStringLiteral("import-layout"))) {
0235         QString importedLayout = Latte::Layouts::Importer::importLayoutHelper(parser.value(QStringLiteral("import-layout")));
0236 
0237         if (importedLayout.isEmpty()) {
0238             qInfo() << i18n("The layout cannot be imported");
0239             qGuiApp->exit();
0240             return 0;
0241         } else {
0242             layoutNameOnStartup = importedLayout;
0243         }
0244     }
0245 
0246     //! memory usage option
0247     if (parser.isSet(QStringLiteral("multiple"))) {
0248         memoryUsage = (int)(Latte::Types::MultipleLayouts);
0249     } else if (parser.isSet(QStringLiteral("single"))) {
0250         memoryUsage = (int)(Latte::Types::SingleLayout);
0251     }
0252 
0253     //! debug/mask options
0254     if (parser.isSet(QStringLiteral("debug")) || parser.isSet(QStringLiteral("mask"))) {
0255         //! set pattern for debug messages
0256         //! [%{type}] [%{function}:%{line}] - %{message} [%{backtrace}]
0257 
0258         qSetMessagePattern(QStringLiteral(
0259                                CIGREEN "[%{type} " CGREEN "%{time h:mm:ss.zz}" CIGREEN "]" CNORMAL
0260 #ifndef QT_NO_DEBUG
0261                                CIRED " [" CCYAN "%{function}" CIRED ":" CCYAN "%{line}" CIRED "]"
0262 #endif
0263                                CICYAN " - " CNORMAL "%{message}"
0264                                CIRED "%{if-fatal}\n%{backtrace depth=8 separator=\"\n\"}%{endif}"
0265                                "%{if-critical}\n%{backtrace depth=8 separator=\"\n\"}%{endif}" CNORMAL));
0266     } else {
0267         const auto noMessageOutput = [](QtMsgType, const QMessageLogContext &, const QString &) {};
0268         qInstallMessageHandler(noMessageOutput);
0269     }
0270 
0271 
0272     auto signal_handler = [](int) {
0273         qGuiApp->exit();
0274     };
0275 
0276     std::signal(SIGKILL, signal_handler);
0277     std::signal(SIGINT, signal_handler);
0278 
0279     KCrash::setDrKonqiEnabled(true);
0280     KCrash::setFlags(KCrash::AutoRestart | KCrash::AlwaysDirectly);
0281 
0282     Latte::Corona corona(defaultLayoutOnStartup, layoutNameOnStartup, memoryUsage);
0283     KDBusService service(KDBusService::Unique);
0284 
0285     return app.exec();
0286 }
0287 
0288 inline void configureAboutData()
0289 {
0290     KAboutData about(QStringLiteral("lattedock")
0291                      , QStringLiteral("Latte Dock")
0292                      , QStringLiteral(VERSION)
0293                      , i18n("Latte is a dock based on plasma frameworks that provides an elegant and "
0294                             "intuitive experience for your tasks and plasmoids. It animates its contents "
0295                             "by using parabolic zoom effect and tries to be there only when it is needed."
0296                             "\n\n\"Art in Coffee\"")
0297                      , KAboutLicense::GPL_V2
0298                      , QStringLiteral("\251 2016-2017 Michail Vourlakos, Smith AR"));
0299 
0300     about.setHomepage(WEBSITE);
0301     about.setProgramLogo(QIcon::fromTheme(QStringLiteral("latte-dock")));
0302     about.setDesktopFileName(QStringLiteral("latte-dock"));
0303 
0304     // Authors
0305     about.addAuthor(QStringLiteral("Michail Vourlakos"), QString(), QStringLiteral("mvourlakos@gmail.com"));
0306     about.addAuthor(QStringLiteral("Smith AR"), QString(), QStringLiteral("audoban@openmailbox.org"));
0307 
0308     KAboutData::setApplicationData(about);
0309 }
0310 
0311 //! used the version provided by PW:KWorkspace
0312 inline void detectPlatform(int argc, char **argv)
0313 {
0314     if (qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
0315         return;
0316     }
0317 
0318     for (int i = 0; i < argc; i++) {
0319         if (qstrcmp(argv[i], "-platform") == 0 ||
0320             qstrcmp(argv[i], "--platform") == 0 ||
0321             QByteArray(argv[i]).startsWith("-platform=") ||
0322             QByteArray(argv[i]).startsWith("--platform=")) {
0323             return;
0324         }
0325     }
0326 
0327     const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE");
0328 
0329     if (sessionType.isEmpty()) {
0330         return;
0331     }
0332 
0333     if (qstrcmp(sessionType, "wayland") == 0) {
0334         qputenv("QT_QPA_PLATFORM", "wayland");
0335     } else if (qstrcmp(sessionType, "x11") == 0) {
0336         qputenv("QT_QPA_PLATFORM", "xcb");
0337     }
0338 }