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"
10#include "core.h"
11
12#include "processes.h"
13
14#include "kontactinterface_debug.h"
15#include <kwindowsystem.h>
16
17#include <QDBusConnection>
18#include <QDBusConnectionInterface>
19
20#include <QCommandLineParser>
21
22#include <config-kontactinterface.h>
23#if KONTACTINTERFACE_HAVE_X11
24#include <KStartupInfo>
25#endif
26
27#ifdef Q_OS_WIN
28#include <process.h>
29#endif
30
31/*
32 Test plan for the various cases of interaction between standalone apps and kontact:
33
34 1) start kontact, select "Mail".
35 1a) type "korganizer" -> it switches to korganizer
36 1b) type "kmail" -> it switches to kmail
37 1c) type "kaddressbook" -> it switches to kaddressbook
38 1d) type "kmail foo@kde.org" -> it opens a kmail composer, without switching
39 1e) type "knode" -> it switches to knode [unless configured to be external]
40 1f) type "kaddressbook --new-contact" -> it opens a kaddressbook contact window
41
42 2) close kontact. Launch kmail. Launch kontact again.
43 2a) click "Mail" icon -> kontact doesn't load a part, but activates the kmail window
44 2b) type "kmail foo@kde.org" -> standalone kmail opens composer.
45 2c) close kmail, click "Mail" icon -> kontact loads the kmail part.
46 2d) type "kmail" -> kontact is brought to front
47
48 3) close kontact. Launch korganizer, then kontact.
49 3a) both Todo and Calendar activate the running korganizer.
50 3b) type "korganizer" -> standalone korganizer is brought to front
51 3c) close korganizer, click Calendar or Todo -> kontact loads part.
52 3d) type "korganizer" -> kontact is brought to front
53
54 4) close kontact. Launch kaddressbook, then kontact.
55 4a) "Contacts" icon activate the running kaddressbook.
56 4b) type "kaddressbook" -> standalone kaddressbook is brought to front
57 4c) close kaddressbook, type "kaddressbook -a foo@kde.org" -> kontact loads part and opens editor
58 4d) type "kaddressbook" -> kontact is brought to front
59
60 5) start "kontact --module summaryplugin"
61 5a) type "qdbus org.kde.kmail /kmail_PimApplication newInstance '' ''" ->
62 kontact switches to kmail (#103775)
63 5b) type "kmail" -> kontact is brought to front
64 5c) type "kontact" -> kontact is brought to front
65 5d) type "kontact --module summaryplugin" -> kontact switches to summary
66
67*/
68
69using namespace KontactInterface;
70
71//@cond PRIVATE
72class UniqueAppHandler::UniqueAppHandlerPrivate
73{
74public:
75 Plugin *mPlugin = nullptr;
76};
77//@endcond
78
79UniqueAppHandler::UniqueAppHandler(Plugin *plugin)
80 : QObject(plugin)
81 , d(new UniqueAppHandlerPrivate)
82{
83 qCDebug(KONTACTINTERFACE_LOG) << "plugin->objectName():" << plugin->objectName();
84
85 d->mPlugin = plugin;
87 const QString appName = plugin->objectName();
88 session.registerService(QLatin1StringView("org.kde.") + appName);
89 const QString objectName = QLatin1Char('/') + appName + QLatin1StringView("_PimApplication");
90 session.registerObject(objectName, this, QDBusConnection::ExportAllSlots);
91}
92
93UniqueAppHandler::~UniqueAppHandler()
94{
96 const QString appName = parent()->objectName();
97 session.unregisterService(QLatin1StringView("org.kde.") + appName);
98}
99
100// DBUS call
101int UniqueAppHandler::newInstance(const QByteArray &startupId, const QStringList &args, const QString &workingDirectory)
102{
104#if KONTACTINTERFACE_HAVE_X11
106#endif
109 }
110
111 QCommandLineParser parser;
112 loadCommandLineOptions(&parser); // implemented by plugin
113 parser.process(args);
114
115 return activate(args, workingDirectory);
116}
117
118static QWidget *s_mainWidget = nullptr;
119
120// Plugin-specific newInstance implementation, called by above method
121int KontactInterface::UniqueAppHandler::activate(const QStringList &args, const QString &workingDirectory)
122{
123 Q_UNUSED(args)
124 Q_UNUSED(workingDirectory)
125
126 if (s_mainWidget) {
127 s_mainWidget->show();
129#if KONTACTINTERFACE_HAVE_X11
131#endif
132 }
133
134 // Then ensure the part appears in kontact
135 d->mPlugin->core()->selectPlugin(d->mPlugin);
136 return 0;
137}
138
139Plugin *UniqueAppHandler::plugin() const
140{
141 return d->mPlugin;
142}
143
144bool KontactInterface::UniqueAppHandler::load()
145{
146 (void)d->mPlugin->part(); // load the part without bringing it to front
147 return true;
148}
149
150//@cond PRIVATE
151class Q_DECL_HIDDEN UniqueAppWatcher::UniqueAppWatcherPrivate
152{
153public:
154 UniqueAppHandlerFactoryBase *mFactory = nullptr;
155 Plugin *mPlugin = nullptr;
156 bool mRunningStandalone;
157};
158//@endcond
159
161 : QObject(plugin)
162 , d(new UniqueAppWatcherPrivate)
163{
164 d->mFactory = factory;
165 d->mPlugin = plugin;
166
167 // The app is running standalone if 1) that name is known to D-Bus
168 const QString serviceName = QLatin1StringView("org.kde.") + plugin->objectName();
169 // Needed for wince build
170#undef interface
171 d->mRunningStandalone = QDBusConnection::sessionBus().interface()->isServiceRegistered(serviceName);
172#ifdef Q_OS_WIN
173 if (d->mRunningStandalone) {
174 QList<int> pids;
175 getProcessesIdForName(plugin->objectName(), pids);
176 const int mypid = getpid();
177 bool processExits = false;
178 for (int pid : std::as_const(pids)) {
179 if (mypid != pid) {
180 processExits = true;
181 break;
182 }
183 }
184 if (!processExits) {
185 d->mRunningStandalone = false;
186 }
187 }
188#endif
189
191 if (d->mRunningStandalone && (owner == QDBusConnection::sessionBus().baseService())) {
192 d->mRunningStandalone = false;
193 }
194
195 qCDebug(KONTACTINTERFACE_LOG) << " plugin->objectName()=" << plugin->objectName() << " running standalone:" << d->mRunningStandalone;
196
197 if (d->mRunningStandalone) {
200 this,
201 &UniqueAppWatcher::slotApplicationRemoved);
202 } else {
203 d->mFactory->createHandler(d->mPlugin);
204 }
205}
206
207UniqueAppWatcher::~UniqueAppWatcher()
208{
209 delete d->mFactory;
210}
211
212bool UniqueAppWatcher::isRunningStandalone() const
213{
214 return d->mRunningStandalone;
215}
216
217void KontactInterface::UniqueAppWatcher::slotApplicationRemoved(const QString &name, const QString &oldOwner, const QString &newOwner)
218{
219 if (oldOwner.isEmpty() || !newOwner.isEmpty()) {
220 return;
221 }
222
223 const QString serviceName = QLatin1StringView("org.kde.") + d->mPlugin->objectName();
224 if (name == serviceName && d->mRunningStandalone) {
225 d->mFactory->createHandler(d->mPlugin);
226 d->mRunningStandalone = false;
227 }
228}
229
231{
232 s_mainWidget = widget;
233}
234
236{
237 return s_mainWidget;
238}
239
240#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 Tue Mar 26 2024 11:21:21 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.