summaryrefslogtreecommitdiffstats
path: root/kdecore/kuniqueapplication.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kdecore/kuniqueapplication.cpp')
-rw-r--r--kdecore/kuniqueapplication.cpp495
1 files changed, 495 insertions, 0 deletions
diff --git a/kdecore/kuniqueapplication.cpp b/kdecore/kuniqueapplication.cpp
new file mode 100644
index 000000000..1ef94fbbe
--- /dev/null
+++ b/kdecore/kuniqueapplication.cpp
@@ -0,0 +1,495 @@
+/* This file is part of the KDE libraries
+ Copyright (c) 1999 Preston Brown <pbrown@kde.org>
+
+ $Id$
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <qfile.h>
+#include <qptrlist.h>
+#include <qtimer.h>
+
+#include <dcopclient.h>
+#include <kcmdlineargs.h>
+#include <kstandarddirs.h>
+#include <kaboutdata.h>
+
+#if defined Q_WS_X11
+#include <kwin.h>
+#include <kstartupinfo.h>
+#endif
+
+#include <kconfig.h>
+#include "kdebug.h"
+#include "kuniqueapplication.h"
+
+#if defined Q_WS_X11
+#include <netwm.h>
+#include <X11/Xlib.h>
+#define DISPLAY "DISPLAY"
+#else
+# ifdef Q_WS_QWS
+# define DISPLAY "QWS_DISPLAY"
+# else
+# define DISPLAY "DISPLAY"
+# endif
+#endif
+
+bool KUniqueApplication::s_nofork = false;
+bool KUniqueApplication::s_multipleInstances = false;
+bool KUniqueApplication::s_uniqueTestDone = false;
+bool KUniqueApplication::s_handleAutoStarted = false;
+
+static KCmdLineOptions kunique_options[] =
+{
+ { "nofork", "Don't run in the background.", 0 },
+ KCmdLineLastOption
+};
+
+struct DCOPRequest {
+ QCString fun;
+ QByteArray data;
+ DCOPClientTransaction *transaction;
+};
+
+class KUniqueApplicationPrivate {
+public:
+ QPtrList <DCOPRequest> requestList;
+ bool processingRequest;
+ bool firstInstance;
+};
+
+void
+KUniqueApplication::addCmdLineOptions()
+{
+ KCmdLineArgs::addCmdLineOptions(kunique_options, 0, "kuniqueapp", "kde" );
+}
+
+bool
+KUniqueApplication::start()
+{
+ if( s_uniqueTestDone )
+ return true;
+ s_uniqueTestDone = true;
+ addCmdLineOptions(); // Make sure to add cmd line options
+#ifdef Q_WS_WIN
+ s_nofork = true;
+#else
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp");
+ s_nofork = !args->isSet("fork");
+ delete args;
+#endif
+
+ QCString appName = KCmdLineArgs::about->appName();
+
+ if (s_nofork)
+ {
+ if (s_multipleInstances)
+ {
+ QCString pid;
+ pid.setNum(getpid());
+ appName = appName + "-" + pid;
+ }
+
+ // Check to make sure that we're actually able to register with the DCOP
+ // server.
+
+#ifndef Q_WS_WIN //TODO
+ if(dcopClient()->registerAs(appName, false).isEmpty()) {
+ startKdeinit();
+ if(dcopClient()->registerAs(appName, false).isEmpty()) {
+ kdError() << "KUniqueApplication: Can't setup DCOP communication." << endl;
+ ::exit(255);
+ }
+ }
+#endif
+
+ // We'll call newInstance in the constructor. Do nothing here.
+ return true;
+ }
+ DCOPClient *dc;
+ int fd[2];
+ signed char result;
+ if (0 > pipe(fd))
+ {
+ kdError() << "KUniqueApplication: pipe() failed!" << endl;
+ ::exit(255);
+ }
+ int fork_result = fork();
+ switch(fork_result) {
+ case -1:
+ kdError() << "KUniqueApplication: fork() failed!" << endl;
+ ::exit(255);
+ break;
+ case 0:
+ // Child
+ ::close(fd[0]);
+ if (s_multipleInstances)
+ appName.append("-").append(QCString().setNum(getpid()));
+ dc = dcopClient();
+ {
+ QCString regName = dc->registerAs(appName, false);
+ if (regName.isEmpty())
+ {
+ // Check DISPLAY
+ if (QCString(getenv(DISPLAY)).isEmpty())
+ {
+ kdError() << "KUniqueApplication: Can't determine DISPLAY. Aborting." << endl;
+ result = -1; // Error
+ ::write(fd[1], &result, 1);
+ ::exit(255);
+ }
+
+ // Try to launch kdeinit.
+ startKdeinit();
+ regName = dc->registerAs(appName, false);
+ if (regName.isEmpty())
+ {
+ kdError() << "KUniqueApplication: Can't setup DCOP communication." << endl;
+ result = -1;
+ delete dc; // Clean up DCOP commmunication
+ ::write(fd[1], &result, 1);
+ ::exit(255);
+ }
+ }
+ if (regName != appName)
+ {
+ // Already running. Ok.
+ result = 0;
+ delete dc; // Clean up DCOP commmunication
+ ::write(fd[1], &result, 1);
+ ::close(fd[1]);
+#if 0
+#ifdef Q_WS_X11
+ // say we're up and running ( probably no new window will appear )
+ KStartupInfoId id;
+ if( kapp != NULL ) // KApplication constructor unsets the env. variable
+ id.initId( kapp->startupId());
+ else
+ id = KStartupInfo::currentStartupIdEnv();
+ if( !id.none())
+ {
+ Display* disp = XOpenDisplay( NULL );
+ if( disp != NULL ) // use extra X connection
+ {
+ KStartupInfo::sendFinishX( disp, id );
+ XCloseDisplay( disp );
+ }
+ }
+#else //FIXME(E): implement
+#endif
+#endif
+ return false;
+ }
+ dc->setPriorityCall(true);
+ }
+
+ {
+#ifdef Q_WS_X11
+ KStartupInfoId id;
+ if( kapp != NULL ) // KApplication constructor unsets the env. variable
+ id.initId( kapp->startupId());
+ else
+ id = KStartupInfo::currentStartupIdEnv();
+ if( !id.none())
+ { // notice about pid change
+ Display* disp = XOpenDisplay( NULL );
+ if( disp != NULL ) // use extra X connection
+ {
+ KStartupInfoData data;
+ data.addPid( getpid());
+ KStartupInfo::sendChangeX( disp, id, data );
+ XCloseDisplay( disp );
+ }
+ }
+#else //FIXME(E): Implement
+#endif
+ }
+ result = 0;
+ ::write(fd[1], &result, 1);
+ ::close(fd[1]);
+ return true; // Finished.
+ default:
+ // Parent
+// DCOPClient::emergencyClose();
+// dcopClient()->detach();
+ if (s_multipleInstances)
+ appName.append("-").append(QCString().setNum(fork_result));
+ ::close(fd[1]);
+ for(;;)
+ {
+ int n = ::read(fd[0], &result, 1);
+ if (n == 1) break;
+ if (n == 0)
+ {
+ kdError() << "KUniqueApplication: Pipe closed unexpectedly." << endl;
+ ::exit(255);
+ }
+ if (errno != EINTR)
+ {
+ kdError() << "KUniqueApplication: Error reading from pipe." << endl;
+ ::exit(255);
+ }
+ }
+ ::close(fd[0]);
+
+ if (result != 0)
+ ::exit(result); // Error occurred in child.
+
+ dc = new DCOPClient();
+ if (!dc->attach())
+ {
+ kdError() << "KUniqueApplication: Parent process can't attach to DCOP." << endl;
+ delete dc; // Clean up DCOP commmunication
+ ::exit(255);
+ }
+ if (!dc->isApplicationRegistered(appName)) {
+ kdError() << "KUniqueApplication: Registering failed!" << endl;
+ }
+
+ QCString new_asn_id;
+#if defined Q_WS_X11
+ KStartupInfoId id;
+ if( kapp != NULL ) // KApplication constructor unsets the env. variable
+ id.initId( kapp->startupId());
+ else
+ id = KStartupInfo::currentStartupIdEnv();
+ if( !id.none())
+ new_asn_id = id.id();
+#endif
+
+ QByteArray data, reply;
+ QDataStream ds(data, IO_WriteOnly);
+
+ KCmdLineArgs::saveAppArgs(ds);
+ ds << new_asn_id;
+
+ dc->setPriorityCall(true);
+ QCString replyType;
+ if (!dc->call(appName, KCmdLineArgs::about->appName(), "newInstance()", data, replyType, reply))
+ {
+ kdError() << "Communication problem with " << KCmdLineArgs::about->appName() << ", it probably crashed." << endl;
+ delete dc; // Clean up DCOP commmunication
+ ::exit(255);
+ }
+ dc->setPriorityCall(false);
+ if (replyType != "int")
+ {
+ kdError() << "KUniqueApplication: DCOP communication error!" << endl;
+ delete dc; // Clean up DCOP commmunication
+ ::exit(255);
+ }
+ QDataStream rs(reply, IO_ReadOnly);
+ int exitCode;
+ rs >> exitCode;
+ delete dc; // Clean up DCOP commmunication
+ ::exit(exitCode);
+ break;
+ }
+ return false; // make insure++ happy
+}
+
+
+KUniqueApplication::KUniqueApplication(bool allowStyles, bool GUIenabled, bool configUnique)
+ : KApplication( allowStyles, GUIenabled, initHack( configUnique )),
+ DCOPObject(KCmdLineArgs::about->appName())
+{
+ d = new KUniqueApplicationPrivate;
+ d->processingRequest = false;
+ d->firstInstance = true;
+
+ if (s_nofork)
+ // Can't call newInstance directly from the constructor since it's virtual...
+ QTimer::singleShot( 0, this, SLOT(newInstanceNoFork()) );
+}
+
+
+#ifdef Q_WS_X11
+KUniqueApplication::KUniqueApplication(Display *display, Qt::HANDLE visual,
+ Qt::HANDLE colormap, bool allowStyles, bool configUnique)
+ : KApplication( display, visual, colormap, allowStyles, initHack( configUnique )),
+ DCOPObject(KCmdLineArgs::about->appName())
+{
+ d = new KUniqueApplicationPrivate;
+ d->processingRequest = false;
+ d->firstInstance = true;
+
+ if (s_nofork)
+ // Can't call newInstance directly from the constructor since it's virtual...
+ QTimer::singleShot( 0, this, SLOT(newInstanceNoFork()) );
+}
+#endif
+
+
+KUniqueApplication::~KUniqueApplication()
+{
+ delete d;
+}
+
+// this gets called before even entering QApplication::QApplication()
+KInstance* KUniqueApplication::initHack( bool configUnique )
+{
+ KInstance* inst = new KInstance( KCmdLineArgs::about );
+ if (configUnique)
+ {
+ KConfigGroupSaver saver( inst->config(), "KDE" );
+ s_multipleInstances = inst->config()->readBoolEntry("MultipleInstances", false);
+ }
+ if( !start())
+ // Already running
+ ::exit( 0 );
+ return inst;
+}
+
+void KUniqueApplication::newInstanceNoFork()
+{
+ if (dcopClient()->isSuspended())
+ {
+ // Try again later.
+ QTimer::singleShot( 200, this, SLOT(newInstanceNoFork()) );
+ return;
+ }
+
+ s_handleAutoStarted = false;
+ newInstance();
+ d->firstInstance = false;
+#if defined Q_WS_X11
+ // KDE4 remove
+ // A hack to make startup notification stop for apps which override newInstance()
+ // and reuse an already existing window there, but use KWin::activateWindow()
+ // instead of KStartupInfo::setNewStartupId(). Therefore KWin::activateWindow()
+ // for now sets this flag. Automatically ending startup notification always
+ // would cause problem if the new window would show up with a small delay.
+ if( s_handleAutoStarted )
+ KStartupInfo::handleAutoAppStartedSending();
+#endif
+ // What to do with the return value ?
+}
+
+bool KUniqueApplication::process(const QCString &fun, const QByteArray &data,
+ QCString &replyType, QByteArray &replyData)
+{
+ if (fun == "newInstance()")
+ {
+ delayRequest(fun, data);
+ return true;
+ } else
+ return DCOPObject::process(fun, data, replyType, replyData);
+}
+
+void
+KUniqueApplication::delayRequest(const QCString &fun, const QByteArray &data)
+{
+ DCOPRequest *request = new DCOPRequest;
+ request->fun = fun;
+ request->data = data;
+ request->transaction = dcopClient()->beginTransaction();
+ d->requestList.append(request);
+ if (!d->processingRequest)
+ {
+ QTimer::singleShot(0, this, SLOT(processDelayed()));
+ }
+}
+
+void
+KUniqueApplication::processDelayed()
+{
+ if (dcopClient()->isSuspended())
+ {
+ // Try again later.
+ QTimer::singleShot( 200, this, SLOT(processDelayed()));
+ return;
+ }
+ d->processingRequest = true;
+ while( !d->requestList.isEmpty() )
+ {
+ DCOPRequest *request = d->requestList.take(0);
+ QByteArray replyData;
+ QCString replyType;
+ if (request->fun == "newInstance()") {
+ dcopClient()->setPriorityCall(false);
+ QDataStream ds(request->data, IO_ReadOnly);
+ KCmdLineArgs::loadAppArgs(ds);
+ if( !ds.atEnd()) // backwards compatibility
+ {
+ QCString asn_id;
+ ds >> asn_id;
+ setStartupId( asn_id );
+ }
+ s_handleAutoStarted = false;
+ int exitCode = newInstance();
+ d->firstInstance = false;
+#if defined Q_WS_X11
+ if( s_handleAutoStarted )
+ KStartupInfo::handleAutoAppStartedSending(); // KDE4 remove?
+#endif
+ QDataStream rs(replyData, IO_WriteOnly);
+ rs << exitCode;
+ replyType = "int";
+ }
+ dcopClient()->endTransaction( request->transaction, replyType, replyData);
+ delete request;
+ }
+
+ d->processingRequest = false;
+}
+
+bool KUniqueApplication::restoringSession()
+{
+ return d->firstInstance && isRestored();
+}
+
+int KUniqueApplication::newInstance()
+{
+ if (!d->firstInstance)
+ {
+
+ if ( mainWidget() )
+ {
+ mainWidget()->show();
+#if defined Q_WS_X11
+ // This is the line that handles window activation if necessary,
+ // and what's important, it does it properly. If you reimplement newInstance(),
+ // and don't call the inherited one, use this (but NOT when newInstance()
+ // is called for the first time, like here).
+ KStartupInfo::setNewStartupId( mainWidget(), kapp->startupId());
+#endif
+ }
+ }
+ return 0; // do nothing in default implementation
+}
+
+void KUniqueApplication::setHandleAutoStarted()
+{
+ s_handleAutoStarted = false;
+}
+
+void KUniqueApplication::virtual_hook( int id, void* data )
+{ KApplication::virtual_hook( id, data );
+ DCOPObject::virtual_hook( id, data ); }
+
+#include "kuniqueapplication.moc"