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(nullptr);
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) {
126  appName.prepend(QLatin1Char('.'));
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  }
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 
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"
QString organizationDomain() const
Returns the domain name of the organization that wrote this application.
Controls and provides information to all KDE applications.
Definition: kapplication.h:76
static void addCmdLineOptions(const KCmdLineOptions &options, const KLocalizedString &name=KLocalizedString(), const QByteArray &id=QByteArray(), const QByteArray &afterId=QByteArray())
Add options to your application.
QString & append(QChar ch)
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...
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const const
int indexOfMethod(const char *method) const const
static bool start(StartFlags flags)
Forks and registers with D-Bus.
static KCmdLineArgs * parsedArgs(const QByteArray &id=QByteArray())
Access parsed arguments.
QString & prepend(QChar ch)
QDBusConnectionInterface * interface() const const
A class for command-line argument handling.
Definition: kcmdlineargs.h:286
QString name() const const
bool isSessionRestored() const const
bool isEmpty() const const
virtual const QMetaObject * metaObject() const const
QString message() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
bool isValid() const const
QDBusConnection sessionBus()
QString appName() const
Returns the application&#39;s internal name.
static void setHandleAutoStarted()
static void forceActiveWindow(WId win, long time=0)
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
virtual ~KUniqueApplication()
Destructor.
bool isSet(const QByteArray &option) const
Read out a boolean option or check for the presence of string option.
static void loadAppArgs(QDataStream &)
Load arguments from a stream.
void exit(int returnCode)
static void setNewStartupId(QWindow *window, const QByteArray &startup_id)
const KSharedConfig::Ptr & config() const
Returns the general config object ("appnamerc").
QString number(int n, int base)
bool restoringSession()
Returns whether newInstance() is being called while session restoration is in progress.
QByteArray startupId() const
bool isEmpty() const const
static const K4AboutData * aboutData()
Returns the K4AboutData for consumption by KComponentData.
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QWindowList allWindows()
virtual int newInstance()
Creates a new "instance" of the application.
WId winId() const const
T & first()
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)
QWindow * windowHandle() const const
const QDBusError & error()
WA_NativeWindow
static void addCmdLineOptions()
Adds command line options specific for KUniqueApplication.
static void saveAppArgs(QDataStream &)
static KStartupInfoId currentStartupIdEnv()
bool isConnected() const const
Create a new instance of the application in a new process and do not attempt to re-use an existing pr...
static bool sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id, const KStartupInfoData &data)
KLocalizedString KI18N_EXPORT ki18n(const char *text)
KUniqueApplication(bool GUIenabled=true, bool configUnique=false)
Constructor.
QDBusReply< QDBusConnectionInterface::RegisterServiceReply > registerService(const QString &serviceName, QDBusConnectionInterface::ServiceQueueOptions qoption, QDBusConnectionInterface::ServiceReplacementOptions roption)
void show()
QDBusError lastError() const const
QObject * parent() const const
void initId(const QByteArray &id="")
Class that holds command line options.
Definition: kcmdlineargs.h:53
T readEntry(const QString &key, const T &aDefault) const
void addPid(pid_t pid)
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
Per component data.
static QList< KMainWindow * > memberList()
QCA_EXPORT QString appName()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Aug 7 2020 22:56:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.