You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
14 KiB
496 lines
14 KiB
/* 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 <tqfile.h>
|
|
#include <tqptrlist.h>
|
|
#include <tqtimer.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 {
|
|
TQCString fun;
|
|
TQByteArray data;
|
|
DCOPClientTransaction *transaction;
|
|
};
|
|
|
|
class KUniqueApplicationPrivate {
|
|
public:
|
|
TQPtrList <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
|
|
|
|
TQCString appName = KCmdLineArgs::about->appName();
|
|
|
|
if (s_nofork)
|
|
{
|
|
if (s_multipleInstances)
|
|
{
|
|
TQCString 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(TQCString().setNum(getpid()));
|
|
dc = dcopClient();
|
|
{
|
|
TQCString regName = dc->registerAs(appName, false);
|
|
if (regName.isEmpty())
|
|
{
|
|
// Check DISPLAY
|
|
if (TQCString(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(TQCString().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;
|
|
}
|
|
|
|
TQCString 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
|
|
|
|
TQByteArray data, reply;
|
|
TQDataStream ds(data, IO_WriteOnly);
|
|
|
|
KCmdLineArgs::saveAppArgs(ds);
|
|
ds << new_asn_id;
|
|
|
|
dc->setPriorityCall(true);
|
|
TQCString 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);
|
|
}
|
|
TQDataStream 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...
|
|
TQTimer::singleShot( 0, this, TQT_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...
|
|
TQTimer::singleShot( 0, this, TQT_SLOT(newInstanceNoFork()) );
|
|
}
|
|
#endif
|
|
|
|
|
|
KUniqueApplication::~KUniqueApplication()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
// this gets called before even entering TQApplication::TQApplication()
|
|
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.
|
|
TQTimer::singleShot( 200, this, TQT_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 TQCString &fun, const TQByteArray &data,
|
|
TQCString &replyType, TQByteArray &replyData)
|
|
{
|
|
if (fun == "newInstance()")
|
|
{
|
|
delayRequest(fun, data);
|
|
return true;
|
|
} else
|
|
return DCOPObject::process(fun, data, replyType, replyData);
|
|
}
|
|
|
|
void
|
|
KUniqueApplication::delayRequest(const TQCString &fun, const TQByteArray &data)
|
|
{
|
|
DCOPRequest *request = new DCOPRequest;
|
|
request->fun = fun;
|
|
request->data = data;
|
|
request->transaction = dcopClient()->beginTransaction();
|
|
d->requestList.append(request);
|
|
if (!d->processingRequest)
|
|
{
|
|
TQTimer::singleShot(0, this, TQT_SLOT(processDelayed()));
|
|
}
|
|
}
|
|
|
|
void
|
|
KUniqueApplication::processDelayed()
|
|
{
|
|
if (dcopClient()->isSuspended())
|
|
{
|
|
// Try again later.
|
|
TQTimer::singleShot( 200, this, TQT_SLOT(processDelayed()));
|
|
return;
|
|
}
|
|
d->processingRequest = true;
|
|
while( !d->requestList.isEmpty() )
|
|
{
|
|
DCOPRequest *request = d->requestList.take(0);
|
|
TQByteArray replyData;
|
|
TQCString replyType;
|
|
if (request->fun == "newInstance()") {
|
|
dcopClient()->setPriorityCall(false);
|
|
TQDataStream ds(request->data, IO_ReadOnly);
|
|
KCmdLineArgs::loadAppArgs(ds);
|
|
if( !ds.atEnd()) // backwards compatibility
|
|
{
|
|
TQCString 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
|
|
TQDataStream 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"
|