KontactInterface

uniqueapphandler.cpp
1/*
2 This file is part of the KDE Kontact Plugin Interface Library.
3
4 SPDX-FileCopyrightText: 2003, 2008 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "uniqueapphandler.h"
10using namespace Qt::Literals::StringLiterals;
11
12#include "core.h"
13
14#include "processes.h"
15
16#include "kontactinterface_debug.h"
17#include <kwindowsystem.h>
18
19#include <QDBusConnection>
20#include <QDBusConnectionInterface>
21
22#include <QCommandLineParser>
23
24#include <config-kontactinterface.h>
25#if KONTACTINTERFACE_HAVE_X11
26#include <KStartupInfo>
27#endif
28
29#ifdef Q_OS_WIN
30#include <process.h>
31#endif
32
33/*
34 Test plan for the various cases of interaction between standalone apps and kontact:
35
36 1) start kontact, select "Mail".
37 1a) type "korganizer" -> it switches to korganizer
38 1b) type "kmail" -> it switches to kmail
39 1c) type "kaddressbook" -> it switches to kaddressbook
40 1d) type "kmail foo@kde.org" -> it opens a kmail composer, without switching
41 1e) type "knode" -> it switches to knode [unless configured to be external]
42 1f) type "kaddressbook --new-contact" -> it opens a kaddressbook contact window
43
44 2) close kontact. Launch kmail. Launch kontact again.
45 2a) click "Mail" icon -> kontact doesn't load a part, but activates the kmail window
46 2b) type "kmail foo@kde.org" -> standalone kmail opens composer.
47 2c) close kmail, click "Mail" icon -> kontact loads the kmail part.
48 2d) type "kmail" -> kontact is brought to front
49
50 3) close kontact. Launch korganizer, then kontact.
51 3a) both Todo and Calendar activate the running korganizer.
52 3b) type "korganizer" -> standalone korganizer is brought to front
53 3c) close korganizer, click Calendar or Todo -> kontact loads part.
54 3d) type "korganizer" -> kontact is brought to front
55
56 4) close kontact. Launch kaddressbook, then kontact.
57 4a) "Contacts" icon activate the running kaddressbook.
58 4b) type "kaddressbook" -> standalone kaddressbook is brought to front
59 4c) close kaddressbook, type "kaddressbook -a foo@kde.org" -> kontact loads part and opens editor
60 4d) type "kaddressbook" -> kontact is brought to front
61
62 5) start "kontact --module summaryplugin"
63 5a) type "qdbus org.kde.kmail /kmail_PimApplication newInstance '' ''" ->
64 kontact switches to kmail (#103775)
65 5b) type "kmail" -> kontact is brought to front
66 5c) type "kontact" -> kontact is brought to front
67 5d) type "kontact --module summaryplugin" -> kontact switches to summary
68
69*/
70
71using namespace KontactInterface;
72
73//@cond PRIVATE
74class UniqueAppHandler::UniqueAppHandlerPrivate
75{
76public:
77 Plugin *mPlugin = nullptr;
78};
79//@endcond
80
81UniqueAppHandler::UniqueAppHandler(Plugin *plugin)
82 : QObject(plugin)
83 , d(new UniqueAppHandlerPrivate)
84{
85 qCDebug(KONTACTINTERFACE_LOG) << "plugin->objectName():" << plugin->objectName();
86
87 d->mPlugin = plugin;
89 const QString appName = plugin->objectName();
90 session.registerService("org.kde."_L1 + appName);
91 const QString objectName = QLatin1Char('/') + appName + "_PimApplication"_L1;
92 session.registerObject(objectName, this, QDBusConnection::ExportAllSlots);
93}
94
95UniqueAppHandler::~UniqueAppHandler()
96{
98 const QString appName = parent()->objectName();
99 session.unregisterService("org.kde."_L1 + appName);
100}
101
102// DBUS call
103int UniqueAppHandler::newInstance(const QByteArray &startupId, const QStringList &args, const QString &workingDirectory)
104{
106#if KONTACTINTERFACE_HAVE_X11
108#endif
111 }
112
113 QCommandLineParser parser;
114 loadCommandLineOptions(&parser); // implemented by plugin
115 parser.process(args);
116
117 return activate(args, workingDirectory);
118}
119
120static QWidget *s_mainWidget = nullptr;
121
122// Plugin-specific newInstance implementation, called by above method
123int KontactInterface::UniqueAppHandler::activate(const QStringList &args, const QString &workingDirectory)
124{
125 Q_UNUSED(args)
126 Q_UNUSED(workingDirectory)
127
128 if (s_mainWidget) {
129 s_mainWidget->show();
131#if KONTACTINTERFACE_HAVE_X11
133#endif
134 }
135
136 // Then ensure the part appears in kontact
137 d->mPlugin->core()->selectPlugin(d->mPlugin);
138 return 0;
139}
140
141Plugin *UniqueAppHandler::plugin() const
142{
143 return d->mPlugin;
144}
145
146bool KontactInterface::UniqueAppHandler::load()
147{
148 (void)d->mPlugin->part(); // load the part without bringing it to front
149 return true;
150}
151
152//@cond PRIVATE
153class Q_DECL_HIDDEN UniqueAppWatcher::UniqueAppWatcherPrivate
154{
155public:
156 UniqueAppHandlerFactoryBase *mFactory = nullptr;
157 Plugin *mPlugin = nullptr;
158 bool mRunningStandalone;
159};
160//@endcond
161
163 : QObject(plugin)
164 , d(new UniqueAppWatcherPrivate)
165{
166 d->mFactory = factory;
167 d->mPlugin = plugin;
168
169 // The app is running standalone if 1) that name is known to D-Bus
170 const QString serviceName = "org.kde."_L1 + plugin->objectName();
171 // Needed for wince build
172#undef interface
173 d->mRunningStandalone = QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName);
174#ifdef Q_OS_WIN
175 if (d->mRunningStandalone) {
176 QList<int> pids;
177 getProcessesIdForName(plugin->objectName(), pids);
178 const int mypid = getpid();
179 bool processExits = false;
180 for (int pid : std::as_const(pids)) {
181 if (mypid != pid) {
182 processExits = true;
183 break;
184 }
185 }
186 if (!processExits) {
187 d->mRunningStandalone = false;
188 }
189 }
190#endif
191
193 if (d->mRunningStandalone && (owner == QDBusConnection::sessionBus().baseService())) {
194 d->mRunningStandalone = false;
195 }
196
197 qCDebug(KONTACTINTERFACE_LOG) << " plugin->objectName()=" << plugin->objectName() << " running standalone:" << d->mRunningStandalone;
198
199 if (d->mRunningStandalone) {
202 this,
203 &UniqueAppWatcher::slotApplicationRemoved);
204 } else {
205 d->mFactory->createHandler(d->mPlugin);
206 }
207}
208
209UniqueAppWatcher::~UniqueAppWatcher()
210{
211 delete d->mFactory;
212}
213
214bool UniqueAppWatcher::isRunningStandalone() const
215{
216 return d->mRunningStandalone;
217}
218
219void KontactInterface::UniqueAppWatcher::slotApplicationRemoved(const QString &name, const QString &oldOwner, const QString &newOwner)
220{
221 if (oldOwner.isEmpty() || !newOwner.isEmpty()) {
222 return;
223 }
224
225 const QString serviceName = "org.kde."_L1 + d->mPlugin->objectName();
226 if (name == serviceName && d->mRunningStandalone) {
227 d->mFactory->createHandler(d->mPlugin);
228 d->mRunningStandalone = false;
229 }
230}
231
233{
234 s_mainWidget = widget;
235}
236
238{
239 return s_mainWidget;
240}
241
242#include "moc_uniqueapphandler.cpp"
static void appStarted()
static void setStartupId(const QByteArray &startup_id)
static Q_INVOKABLE void activateWindow(QWindow *window, long time=0)
static bool isPlatformX11()
static Q_INVOKABLE void setCurrentXdgActivationToken(const QString &token)
static bool isPlatformWayland()
Base class for all Plugins in Kontact.
Definition plugin.h:66
virtual void loadCommandLineOptions(QCommandLineParser *parser)=0
This must be reimplemented so that app-specific command line options can be parsed.
static void setMainWidget(QWidget *widget)
Sets the main QWidget widget associated with this application.
QWidget * mainWidget()
Returns the main widget, which will zero if setMainWidget() has not be called yet.
If the standalone application is running by itself, we need to watch for when the user closes it,...
UniqueAppWatcher(UniqueAppHandlerFactoryBase *factory, Plugin *plugin)
Create an instance of UniqueAppWatcher, which does everything necessary for the "unique application" ...
QCA_EXPORT QString appName()
This file is part of the kpimutils library.
void process(const QCoreApplication &app)
QString baseService() const const
QDBusConnectionInterface * interface() const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool registerService(const QString &serviceName)
QDBusConnection sessionBus()
bool unregisterService(const QString &serviceName)
void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
QDBusReply< QString > serviceOwner(const QString &name) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
void show()
QWindow * windowHandle() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 11:49:18 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.