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