kuniqueapplication.cpp
/* This file is part of the KDE libraries
Copyright (c) 1999 Preston Brown <pbrown@kde.org>
$Id: kuniqueapplication.cpp 474468 2005-10-26 13:51:39Z lunakl $
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"
KApplication
Controls and provides information to all KDE applications.
Definition: kapplication.h:94
KUniqueApplication::start
static bool start()
Forks and registers with dcop.
Definition: kuniqueapplication.cpp:93
KUniqueApplication::KUniqueApplication
KUniqueApplication(bool allowStyles=true, bool GUIenabled=true, bool configUnique=false)
Constructor.
Definition: kuniqueapplication.cpp:317
DCOPClient::attach
bool attach()
KCmdLineArgs
A class for command-line argument handling.
Definition: kcmdlineargs.h:222
KInstance::config
KConfig * config() const
Returns the general config object ("appnamerc").
Definition: kinstance.cpp:177
KUniqueApplication::process
bool process(const QCString &fun, const QByteArray &data, QCString &replyType, QByteArray &replyData)
Dispatches any incoming DCOP message for a new instance.
Definition: kuniqueapplication.cpp:393
KCmdLineArgs::parsedArgs
static KCmdLineArgs * parsedArgs(const char *id=0)
Access parsed arguments.
Definition: kcmdlineargs.cpp:310
KUniqueApplication::~KUniqueApplication
virtual ~KUniqueApplication()
Destructor.
Definition: kuniqueapplication.cpp:348
DCOPClient
DCOPClient::beginTransaction
DCOPClientTransaction * beginTransaction()
KCmdLineArgs::loadAppArgs
static void loadAppArgs(QDataStream &)
Load arguments from a stream.
Definition: kcmdlineargs.cpp:264
DCOPClient::endTransaction
void endTransaction(DCOPClientTransaction *t, QCString &replyType, QByteArray &replyData)
KCmdLineOptions
Structure that holds command line options.
Definition: kcmdlineargs.h:40
KUniqueApplication::restoringSession
bool restoringSession()
Returns whether newInstance() is being called while session restoration is in progress.
Definition: kuniqueapplication.cpp:461
DCOPClient::registerAs
QCString registerAs(const QCString &appId, bool addPID=true)
KCmdLineArgs::isSet
bool isSet(const char *option) const
Read out a boolean option or check for the presence of string option.
Definition: kcmdlineargs.cpp:1179
KInstance
Access to KDE global objects for use in shared libraries.
Definition: kinstance.h:43
KInstance::KInstance
KInstance(const QCString &instanceName)
Constructor.
Definition: kinstance.cpp:69
KUniqueApplication::newInstance
virtual int newInstance()
Creates a new "instance" of the application.
Definition: kuniqueapplication.cpp:466
KConfigGroupSaver
Helper class to facilitate working with KConfig / KSimpleConfig groups.
Definition: kconfigbase.h:2057
KApplication::isRestored
bool isRestored() const
Is the application restored from the session manager?
Definition: kapplication.h:243
KUniqueApplication::addCmdLineOptions
static void addCmdLineOptions()
Adds command line options specific for KUniqueApplication.
Definition: kuniqueapplication.cpp:87
DCOPClient::call
bool call(const QCString &remApp, const QCString &remObj, const QCString &remFun, const QByteArray &data, QCString &replyType, QByteArray &replyData, bool useEventLoop, int timeout)
KCmdLineArgs::addCmdLineOptions
static void addCmdLineOptions(const KCmdLineOptions *options, const char *name=0, const char *id=0, const char *afterId=0)
Add options to your application.
Definition: kcmdlineargs.cpp:206
KApplication::dcopClient
static DCOPClient * dcopClient()
Returns a pointer to a DCOPClient for the application.
Definition: kapplication.cpp:981
KConfigBase::readBoolEntry
bool readBoolEntry(const QString &pKey, bool bDefault=false) const
Reads a boolean entry.
Definition: kconfigbase.cpp:716
endl
kndbgstream & endl(kndbgstream &s)
Does nothing.
Definition: kdebug.h:583
DCOPObject
DCOPObject::process
virtual bool process(const QCString &fun, const QByteArray &data, QCString &replyType, QByteArray &replyData)
KAboutData::appName
const char * appName() const
Returns the application's internal name.
Definition: kaboutdata.cpp:237
DCOPClient::isApplicationRegistered
bool isApplicationRegistered(const QCString &remApp)

KDE Home | KDE Accessibility Home | Description of Access Keys