1 /* This file is part of the KDE project
2 
3    SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org>
4 
5    SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include "pimuniqueapplication.h"
9 #include "config-kontactinterface.h"
10 #include "kontactinterface_debug.h"
11 
12 #include <KAboutData>
13 #include <KStartupInfo>
14 #include <KWindowSystem>
15 
16 #include <QCommandLineParser>
17 #include <QDir>
18 
19 #include <QMainWindow>
20 #include <QWidget>
21 
22 #include <QDBusConnectionInterface>
23 #include <QDBusInterface>
24 
25 using namespace KontactInterface;
26 
27 namespace
28 {
29 const char kChromiumFlagsEnv[] = "QTWEBENGINE_CHROMIUM_FLAGS";
30 const char kDisableInProcessStackTraces[] = "--disable-in-process-stack-traces";
31 
32 }
33 
34 //@cond PRIVATE
35 class Q_DECL_HIDDEN KontactInterface::PimUniqueApplication::PimUniqueApplicationPrivate
36 {
37 public:
PimUniqueApplicationPrivate()38     PimUniqueApplicationPrivate()
39         : cmdArgs(new QCommandLineParser())
40     {
41     }
42 
~PimUniqueApplicationPrivate()43     ~PimUniqueApplicationPrivate()
44     {
45         delete cmdArgs;
46     }
47 
disableChromiumCrashHandler()48     static void disableChromiumCrashHandler()
49     {
50         // Disable Chromium's own crash handler, which overrides DrKonqi.
51         auto flags = qgetenv(kChromiumFlagsEnv);
52         if (!flags.contains(kDisableInProcessStackTraces)) {
53             qputenv(kChromiumFlagsEnv, flags + " " + kDisableInProcessStackTraces);
54         }
55     }
56 
57     QCommandLineParser *const cmdArgs;
58 };
59 //@endcond
60 
PimUniqueApplication(int & argc,char ** argv[])61 PimUniqueApplication::PimUniqueApplication(int &argc, char **argv[])
62     : QApplication(argc, *argv)
63     , d(new PimUniqueApplicationPrivate())
64 {
65 }
66 
67 PimUniqueApplication::~PimUniqueApplication() = default;
68 
cmdArgs() const69 QCommandLineParser *PimUniqueApplication::cmdArgs() const
70 {
71     return d->cmdArgs;
72 }
73 
setAboutData(KAboutData & aboutData)74 void PimUniqueApplication::setAboutData(KAboutData &aboutData)
75 {
76     KAboutData::setApplicationData(aboutData);
77     aboutData.setupCommandLine(d->cmdArgs);
78     // This object name is used in start(), and also in kontact's UniqueAppHandler.
79     const QString objectName = QLatin1Char('/') + QApplication::applicationName() + QLatin1String("_PimApplication");
80     QDBusConnection::sessionBus().registerObject(objectName,
81                                                  this,
82                                                  QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties
83                                                      | QDBusConnection::ExportAdaptors);
84 }
85 
callNewInstance(const QString & appName,const QString & serviceName,const QByteArray & asn_id,const QStringList & arguments)86 static bool callNewInstance(const QString &appName, const QString &serviceName, const QByteArray &asn_id, const QStringList &arguments)
87 {
88     const QString objectName = QLatin1Char('/') + appName + QLatin1String("_PimApplication");
89     QDBusInterface iface(serviceName, objectName, QStringLiteral("org.kde.PIMUniqueApplication"), QDBusConnection::sessionBus());
90     if (iface.isValid()) {
91         QDBusReply<int> reply = iface.call(QStringLiteral("newInstance"), asn_id, arguments, QDir::currentPath());
92         if (reply.isValid()) {
93             return true;
94         }
95     }
96     return false;
97 }
98 
newInstance()99 int PimUniqueApplication::newInstance()
100 {
101     return newInstance(KStartupInfo::startupId(), QStringList() << QApplication::applicationName(), QDir::currentPath());
102 }
103 
start(const QStringList & arguments)104 bool PimUniqueApplication::start(const QStringList &arguments)
105 {
106     const QString appName = QApplication::applicationName();
107 
108     // Try talking to /appName_PimApplication in org.kde.appName,
109     // (which could be kontact or the standalone application),
110     // otherwise the current app being started will register to DBus.
111 
112     const QString serviceName = QLatin1String("org.kde.") + appName;
113     if (QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName)) {
114         QByteArray new_asn_id;
115 #if KONTACTINTERFACE_HAVE_X11
116         KStartupInfoId id;
117         if (!KStartupInfo::startupId().isEmpty()) {
118             id.initId(KStartupInfo::startupId());
119         } else {
120             id = KStartupInfo::currentStartupIdEnv();
121         }
122         if (!id.isNull()) {
123             new_asn_id = id.id();
124         }
125 #endif
126 
127         KWindowSystem::allowExternalProcessWindowActivation();
128 
129         if (callNewInstance(appName, serviceName, new_asn_id, arguments)) {
130             return false; // success means that main() can exit now.
131         }
132     }
133 
134     qCDebug(KONTACTINTERFACE_LOG) << "kontact not running -- start standalone application";
135 
136     QDBusConnection::sessionBus().registerService(serviceName);
137 
138     // Make sure we have DrKonqi
139     PimUniqueApplicationPrivate::disableChromiumCrashHandler();
140 
141     static_cast<PimUniqueApplication *>(qApp)->activate(arguments, QDir::currentPath());
142     return true;
143 }
144 
activateApplication(const QString & appName,const QStringList & additionalArguments)145 bool PimUniqueApplication::activateApplication(const QString &appName, const QStringList &additionalArguments)
146 {
147     const QString serviceName = QLatin1String("org.kde.") + appName;
148     QStringList arguments{appName};
149     arguments += additionalArguments;
150     // Start it standalone if not already running (if kontact is running, then this will do nothing)
151     QDBusConnection::sessionBus().interface()->startService(serviceName);
152     return callNewInstance(appName, serviceName, KStartupInfo::createNewStartupId(), arguments);
153 }
154 
155 // This is called via DBus either by another instance that has just been
156 // started or by Kontact when the module is activated
newInstance(const QByteArray & startupId,const QStringList & arguments,const QString & workingDirectory)157 int PimUniqueApplication::newInstance(const QByteArray &startupId, const QStringList &arguments, const QString &workingDirectory)
158 {
159     KStartupInfo::setStartupId(startupId);
160 
161     const QWidgetList tlws = topLevelWidgets();
162     for (QWidget *win : tlws) {
163         if (qobject_cast<QMainWindow *>(win)) {
164             win->show();
165             win->setAttribute(Qt::WA_NativeWindow, true);
166             KStartupInfo::setNewStartupId(win->windowHandle(), startupId); // this moves 'win' to the current desktop
167 #ifdef Q_OS_WIN
168             KWindowSystem::forceActiveWindow(win->winId());
169 #endif
170             break;
171         }
172     }
173 
174     activate(arguments, workingDirectory);
175     return 0;
176 }
177 
activate(const QStringList & arguments,const QString & workingDirectory)178 int PimUniqueApplication::activate(const QStringList &arguments, const QString &workingDirectory)
179 {
180     Q_UNUSED(arguments)
181     Q_UNUSED(workingDirectory)
182     return 0;
183 }
184