KDELibs4Support

kuniqueapplication.cpp
1 /* This file is part of the KDE libraries
2  Copyright (c) 1999 Preston Brown <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kuniqueapplication.h"
21 #include "kuniqueapplication_p.h"
22 #include <kmainwindow.h>
23 
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 
27 #include <assert.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 
32 #include <QFile>
33 #include <QList>
34 #include <QTimer>
35 #include <QDBusConnection>
36 #include <QDBusConnectionInterface>
37 #include <QDBusError>
38 #include <QDBusReply>
39 
40 #include <kcmdlineargs.h>
41 #include <klocalizedstring.h>
42 #include <k4aboutdata.h>
43 #include <kconfiggroup.h>
44 #include <kwindowsystem.h>
45 
46 #include <config-kdelibs4support.h>
47 #if HAVE_X11
48 #include <kstartupinfo.h>
49 #include <netwm.h>
50 #include <X11/Xlib.h>
51 #include <fixx11h.h>
52 #endif
53 
54 /* I don't know why, but I end up with complaints about
55  a forward-declaration of QWidget in the activeWidow()->show
56  call below on Qt/Mac if I don't include this here... */
57 #include <QWidget>
58 
59 #include <kconfig.h>
60 #include "kdebug.h"
61 
62 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
63 #include <kkernel_mac.h>
64 #endif
65 
66 bool KUniqueApplication::Private::s_startOwnInstance = false;
67 bool KUniqueApplication::Private::s_multipleInstances = false;
68 #ifdef Q_OS_WIN
69 /* private helpers from kapplication_win.cpp */
70 #ifndef _WIN32_WCE
71 void KApplication_activateWindowForProcess(const QString &executableName);
72 #endif
73 #endif
74 
75 void
77 {
78  KCmdLineOptions kunique_options;
79  kunique_options.add("nofork", ki18n("Do not run in the background."));
80 #ifdef Q_OS_MAC
81  kunique_options.add("psn", ki18n("Internally added if launched from Finder"));
82 #endif
83  KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde");
84 }
85 
86 static QDBusConnectionInterface *tryToInitDBusConnection()
87 {
88  // Check the D-Bus connection health
89  QDBusConnectionInterface *dbusService = nullptr;
91  if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface())) {
92  kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << sessionBus.lastError().message() << endl;
93  ::exit(255);
94  }
95  return dbusService;
96 }
97 
99 {
100  return start(StartFlags());
101 }
102 
103 bool
105 {
106  extern bool s_kuniqueapplication_startCalled;
107  if (s_kuniqueapplication_startCalled) {
108  return true;
109  }
110  s_kuniqueapplication_startCalled = true;
111 
112  addCmdLineOptions(); // Make sure to add cmd line options
113 #if defined(Q_OS_WIN) || defined(Q_OS_MACX)
114  Private::s_startOwnInstance = true;
115 #else
116  KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp");
117  Private::s_startOwnInstance = !args->isSet("fork");
118 #endif
119 
122  if (parts.isEmpty()) {
123  appName.prepend(QLatin1String("local."));
124  } else
125  foreach (const QString &s, parts) {
127  appName.prepend(s);
128  }
129 
130  bool forceNewProcess = Private::s_multipleInstances || flags & NonUniqueInstance;
131 
132  if (Private::s_startOwnInstance) {
133 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
134  mac_initialize_dbus();
135 #endif
136 
137  QDBusConnectionInterface *dbusService = tryToInitDBusConnection();
138 
139  QString pid = QString::number(getpid());
140  if (forceNewProcess) {
141  appName = appName + '-' + pid;
142  }
143 
144  // Check to make sure that we're actually able to register with the D-Bus session
145  // server.
146  bool registered = dbusService->registerService(appName) == QDBusConnectionInterface::ServiceRegistered;
147  if (!registered) {
148  kError() << "KUniqueApplication: Can't setup D-Bus service. Probably already running."
149  << endl;
150 #if defined(Q_OS_WIN) && !defined(_WIN32_WCE)
151  KApplication_activateWindowForProcess(KCmdLineArgs::aboutData()->appName());
152 #endif
153  ::exit(255);
154  }
155 
156  // We'll call newInstance in the constructor. Do nothing here.
157  return true;
158 
159 #if defined(Q_OS_DARWIN) || defined (Q_OS_MAC)
160  } else {
161  mac_fork_and_reexec_self();
162 #endif
163 
164  }
165 
166 #ifndef Q_OS_WIN
167  int fd[2];
168  signed char result;
169  // We use result to talk between child and parent. It can be
170  // 0: App already running please call newInstance on it
171  // 1: App was not running, child will call newInstance on itself
172  // -1: Error, Can't start DBus service
173  if (0 > pipe(fd)) {
174  kError() << "KUniqueApplication: pipe() failed!" << endl;
175  ::exit(255);
176  }
177  int fork_result = fork();
178  switch (fork_result) {
179  case -1:
180  kError() << "KUniqueApplication: fork() failed!" << endl;
181  ::exit(255);
182  break;
183  case 0: {
184  // Child
185 
186  QDBusConnectionInterface *dbusService = tryToInitDBusConnection();
187  ::close(fd[0]);
188  if (forceNewProcess) {
189  appName.append("-").append(QString::number(getpid()));
190  }
191 
193  dbusService->registerService(appName);
194  if (!reply.isValid()) {
195  kError() << "KUniqueApplication: Can't setup D-Bus service." << endl;
196  result = -1;
197  ::write(fd[1], &result, 1);
198  ::exit(255);
199  }
201  // Already running. Ok.
202  result = 0;
203  ::write(fd[1], &result, 1);
204  ::close(fd[1]);
205  return false;
206  }
207 
208 #if HAVE_X11
209  KStartupInfoId id;
210  if (kapp != nullptr) { // KApplication constructor unsets the env. variable
211  id.initId(kapp->startupId());
212  } else {
214  }
215  if (!id.isNull()) {
216  // notice about pid change
217  int screen = 0;
218  xcb_connection_t *connection = xcb_connect(nullptr, &screen);
219  if (connection != nullptr) { // use extra X connection
220  KStartupInfoData data;
221  data.addPid(getpid());
222  KStartupInfo::sendChangeXcb(connection, screen, id, data);
223  xcb_disconnect(connection);
224  }
225  }
226 #endif
227  }
228  result = 1;
229  ::write(fd[1], &result, 1);
230  ::close(fd[1]);
231  Private::s_startOwnInstance = true;
232  return true; // Finished.
233  default:
234  // Parent
235 
236  if (forceNewProcess) {
237  appName.append("-").append(QString::number(fork_result));
238  }
239  ::close(fd[1]);
240 
241  Q_FOREVER {
242  int n = ::read(fd[0], &result, 1);
243  if (n == 1)
244  {
245  break;
246  }
247  if (n == 0)
248  {
249  kError() << "KUniqueApplication: Pipe closed unexpectedly." << endl;
250  ::exit(255);
251  }
252  if (errno != EINTR)
253  {
254  kError() << "KUniqueApplication: Error reading from pipe." << endl;
255  ::exit(255);
256  }
257  }
258  ::close(fd[0]);
259 
260  if (result != 0) {
261  // Only -1 is actually an error
262  ::exit(result == -1 ? -1 : 0);
263  }
264 
265 #endif
266  QDBusConnectionInterface *dbusService = tryToInitDBusConnection();
267  if (!dbusService->isServiceRegistered(appName)) {
268  kError() << "KUniqueApplication: Registering failed!" << endl;
269  }
270 
271  QByteArray saved_args;
272  QDataStream ds(&saved_args, QIODevice::WriteOnly);
274 
275  QByteArray new_asn_id;
276 #if HAVE_X11
277  KStartupInfoId id;
278  if (kapp != nullptr) { // KApplication constructor unsets the env. variable
279  id.initId(kapp->startupId());
280  } else {
282  }
283  if (!id.isNull()) {
284  new_asn_id = id.id();
285  }
286 #endif
287 
288  QDBusMessage msg = QDBusMessage::createMethodCall(appName, "/MainApplication", "org.kde.KUniqueApplication",
289  "newInstance");
290  msg << new_asn_id << saved_args;
292 
293  if (!reply.isValid()) {
294  QDBusError err = reply.error();
295  kError() << "Communication problem with " << KCmdLineArgs::aboutData()->appName() << ", it probably crashed." << endl
296  << "Error message was: " << err.name() << ": \"" << err.message() << "\"" << endl;
297  ::exit(255);
298  }
299 #ifndef Q_OS_WIN
300  ::exit(reply);
301  break;
302  }
303 #endif
304  return false; // make insure++ happy
305 }
306 
307 KUniqueApplication::KUniqueApplication(bool GUIenabled, bool configUnique)
308  : KApplication(GUIenabled, Private::initHack(configUnique)),
309  d(new Private(this))
310 {
311  d->processingRequest = false;
312  d->firstInstance = true;
313 
314  // the sanity checking happened in initHack
315  new KUniqueApplicationAdaptor(this);
316 
317  if (Private::s_startOwnInstance)
318  // Can't call newInstance directly from the constructor since it's virtual...
319  {
320  QTimer::singleShot(0, this, SLOT(_k_newInstanceNoFork()));
321  }
322 }
323 
325 {
326  delete d;
327 }
328 
329 // this gets called before even entering QApplication::QApplication()
330 KComponentData KUniqueApplication::Private::initHack(bool configUnique)
331 {
333  if (configUnique) {
334  KConfigGroup cg(cData.config(), "KDE");
335  s_multipleInstances = cg.readEntry("MultipleInstances", false);
336  }
337  if (!KUniqueApplication::start())
338  // Already running
339  {
340  ::exit(0);
341  }
342  return cData;
343 }
344 
345 void KUniqueApplication::Private::_k_newInstanceNoFork()
346 {
347  q->newInstance();
348  firstInstance = false;
349 }
350 
352 {
353  return d->firstInstance && isSessionRestored();
354 }
355 
357 {
358  if (!d->firstInstance) {
360  if (!allWindows.isEmpty()) {
361  // This method is documented to only work for applications
362  // with only one mainwindow.
363  KMainWindow *mainWindow = allWindows.first();
364  if (mainWindow) {
365  mainWindow->show();
366 #if HAVE_X11
367  mainWindow->setAttribute(Qt::WA_NativeWindow, true);
368  // This is the line that handles window activation if necessary,
369  // and what's important, it does it properly. If you reimplement newInstance(),
370  // and don't call the inherited one, use this (but NOT when newInstance()
371  // is called for the first time, like here).
373 #endif
374 #ifdef Q_OS_WIN
376 #endif
377 
378  }
379  }
380  }
381  return 0; // do nothing in default implementation
382 }
383 
384 #ifndef KDELIBS4SUPPORT_NO_DEPRECATED
386 {
387 }
388 #endif
389 
390 ////
391 
392 int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args)
393 {
394  if (!asn_id.isEmpty()) {
395  parent()->setStartupId(asn_id);
396  }
397 
398  const int index = parent()->metaObject()->indexOfMethod("loadCommandLineOptionsForNewInstance");
399  if (index != -1) {
400  // This hook allows the application to set up KCmdLineArgs using addCmdLineOptions
401  // before we load the app args. Normally not necessary, but needed by kontact
402  // since it switches to other sets of options when called as e.g. kmail or korganizer
403  QMetaObject::invokeMethod(parent(), "loadCommandLineOptionsForNewInstance");
404  }
405 
406  QDataStream ds(args);
408 
409  int ret = parent()->newInstance();
410  // Must be done out of the newInstance code, in case it is overloaded
411  parent()->d->firstInstance = false;
412  return ret;
413 }
414 
415 #include "moc_kuniqueapplication.cpp"
416 #include "moc_kuniqueapplication_p.cpp"
Controls and provides information to all KDE applications.
Definition: kapplication.h:76
QString appName() const
Returns the application's internal name.
QString number(int n, int base)
static void addCmdLineOptions(const KCmdLineOptions &options, const KLocalizedString &name=KLocalizedString(), const QByteArray &id=QByteArray(), const QByteArray &afterId=QByteArray())
Add options to your application.
QString organizationDomain() const
Returns the domain name of the organization that wrote this application.
static QList< KMainWindow * > memberList()
bool restoringSession()
Returns whether newInstance() is being called while session restoration is in progress.
QString message() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool isValid() const const
QString & prepend(QChar ch)
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const const
static void addCmdLineOptions()
Adds command line options specific for KUniqueApplication.
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
static KCmdLineArgs * parsedArgs(const QByteArray &id=QByteArray())
Access parsed arguments.
static KStartupInfoId currentStartupIdEnv()
@ NonUniqueInstance
Create a new instance of the application in a new process and do not attempt to re-use an existing pr...
bool isSet(const QByteArray &option) const
Read out a boolean option or check for the presence of string option.
static bool sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id, const KStartupInfoData &data)
void exit(int returnCode)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
QDBusError lastError() const const
void addPid(pid_t pid)
const QDBusError & error()
static void forceActiveWindow(WId win, long time=0)
QDBusConnection sessionBus()
A class for command-line argument handling.
Definition: kcmdlineargs.h:286
~KUniqueApplication() override
Destructor.
KUniqueApplication(bool GUIenabled=true, bool configUnique=false)
Constructor.
QCA_EXPORT QString appName()
virtual int newInstance()
Creates a new "instance" of the application.
bool isEmpty() const const
static void setNewStartupId(QWidget *window, const QByteArray &startup_id)
static void saveAppArgs(QDataStream &)
KLocalizedString KI18N_EXPORT ki18n(const char *text)
Per component data.
bool isConnected() const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
QDBusConnectionInterface * interface() const const
void show()
QWindowList allWindows()
QWindow * windowHandle() const const
bool isEmpty() const const
static void setHandleAutoStarted()
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
KCmdLineOptions & add(const QByteArray &name, const KLocalizedString &description=KLocalizedString(), const QByteArray &defaultValue=QByteArray())
Add command line option, by providing its name, description, and possibly a default value.
WId winId() const const
void initId(const QByteArray &id="")
bool isSessionRestored() const const
Class that holds command line options.
Definition: kcmdlineargs.h:53
QString name() const const
static bool start(StartFlags flags)
Forks and registers with D-Bus.
static void loadAppArgs(QDataStream &)
Load arguments from a stream.
WA_NativeWindow
QString & append(QChar ch)
QDBusReply< QDBusConnectionInterface::RegisterServiceReply > registerService(const QString &serviceName, QDBusConnectionInterface::ServiceQueueOptions qoption, QDBusConnectionInterface::ServiceReplacementOptions roption)
QByteArray startupId() const
static const K4AboutData * aboutData()
Returns the K4AboutData for consumption by KComponentData.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 16 2022 03:55:22 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.