mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 19:02:48 +00:00
405 lines
12 KiB
C++
405 lines
12 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (c) 1999 Preston Brown <pbrown@kde.org>
|
|
|
|
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 "kuniqueapplication.h"
|
|
#include "kuniqueapplication_p.h"
|
|
#include <kmainwindow.h>
|
|
|
|
#include <config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QList>
|
|
#include <QtCore/QTimer>
|
|
#include <QtDBus/QtDBus>
|
|
|
|
#include <kcmdlineargs.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kaboutdata.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kwindowsystem.h>
|
|
|
|
#if defined Q_WS_X11
|
|
#include <kstartupinfo.h>
|
|
#endif
|
|
|
|
/* I don't know why, but I end up with complaints about
|
|
a forward-declaration of QWidget in the activeWidow()->show
|
|
call below on Qt/Mac if I don't include this here... */
|
|
#include <QWidget>
|
|
|
|
#include <kconfig.h>
|
|
#include "kdebug.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::Private::s_nofork = false;
|
|
bool KUniqueApplication::Private::s_multipleInstances = false;
|
|
bool s_kuniqueapplication_startCalled = false;
|
|
bool KUniqueApplication::Private::s_handleAutoStarted = false;
|
|
|
|
void
|
|
KUniqueApplication::addCmdLineOptions()
|
|
{
|
|
KCmdLineOptions kunique_options;
|
|
kunique_options.add("nofork", ki18n("Do not run in the background."));
|
|
KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde");
|
|
}
|
|
|
|
static QDBusConnectionInterface *tryToInitDBusConnection()
|
|
{
|
|
// Check the D-Bus connection health
|
|
QDBusConnectionInterface* dbusService = 0;
|
|
QDBusConnection sessionBus = QDBusConnection::sessionBus();
|
|
if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface()))
|
|
{
|
|
kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << sessionBus.lastError().message() << endl;
|
|
::exit(255);
|
|
}
|
|
return dbusService;
|
|
}
|
|
|
|
bool
|
|
KUniqueApplication::start(StartFlags flags)
|
|
{
|
|
if( s_kuniqueapplication_startCalled )
|
|
return true;
|
|
s_kuniqueapplication_startCalled = true;
|
|
|
|
addCmdLineOptions(); // Make sure to add cmd line options
|
|
KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kuniqueapp");
|
|
Private::s_nofork = !args->isSet("fork");
|
|
|
|
QString appName = KCmdLineArgs::aboutData()->appName();
|
|
const QStringList parts = KCmdLineArgs::aboutData()->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts);
|
|
if (parts.isEmpty())
|
|
appName.prepend(QLatin1String("local."));
|
|
else
|
|
foreach (const QString& s, parts)
|
|
{
|
|
appName.prepend(QLatin1Char('.'));
|
|
appName.prepend(s);
|
|
}
|
|
|
|
bool forceNewProcess = Private::s_multipleInstances || flags & NonUniqueInstance;
|
|
|
|
if (Private::s_nofork)
|
|
{
|
|
|
|
|
|
QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
|
|
|
|
QString pid = QString::number(getpid());
|
|
if (forceNewProcess)
|
|
appName = appName + '-' + pid;
|
|
|
|
// Check to make sure that we're actually able to register with the D-Bus session
|
|
// server.
|
|
bool registered = dbusService->registerService(appName) == QDBusConnectionInterface::ServiceRegistered;
|
|
if (!registered)
|
|
{
|
|
kError() << "KUniqueApplication: Can't setup D-Bus service. Probably already running."
|
|
<< endl;
|
|
::exit(255);
|
|
}
|
|
|
|
// We'll call newInstance in the constructor. Do nothing here.
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
int fd[2];
|
|
signed char result;
|
|
if (0 > pipe(fd))
|
|
{
|
|
kError() << "KUniqueApplication: pipe() failed!" << endl;
|
|
::exit(255);
|
|
}
|
|
int fork_result = fork();
|
|
switch(fork_result) {
|
|
case -1:
|
|
kError() << "KUniqueApplication: fork() failed!" << endl;
|
|
::exit(255);
|
|
break;
|
|
case 0:
|
|
{
|
|
// Child
|
|
|
|
QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
|
|
::close(fd[0]);
|
|
if (forceNewProcess)
|
|
appName.append("-").append(QString::number(getpid()));
|
|
|
|
QDBusReply<QDBusConnectionInterface::RegisterServiceReply> reply =
|
|
dbusService->registerService(appName);
|
|
if (!reply.isValid())
|
|
{
|
|
kError() << "KUniqueApplication: Can't setup D-Bus service." << endl;
|
|
result = -1;
|
|
::write(fd[1], &result, 1);
|
|
::exit(255);
|
|
}
|
|
if (reply == QDBusConnectionInterface::ServiceNotRegistered)
|
|
{
|
|
// Already running. Ok.
|
|
result = 0;
|
|
::write(fd[1], &result, 1);
|
|
::close(fd[1]);
|
|
return false;
|
|
}
|
|
|
|
#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
|
|
|
|
if (forceNewProcess)
|
|
appName.append("-").append(QString::number(fork_result));
|
|
::close(fd[1]);
|
|
|
|
Q_FOREVER
|
|
{
|
|
int n = ::read(fd[0], &result, 1);
|
|
if (n == 1) break;
|
|
if (n == 0)
|
|
{
|
|
kError() << "KUniqueApplication: Pipe closed unexpectedly." << endl;
|
|
::exit(255);
|
|
}
|
|
if (errno != EINTR)
|
|
{
|
|
kError() << "KUniqueApplication: Error reading from pipe." << endl;
|
|
::exit(255);
|
|
}
|
|
}
|
|
::close(fd[0]);
|
|
|
|
if (result != 0)
|
|
::exit(result); // Error occurred in child.
|
|
|
|
QDBusConnectionInterface* dbusService = tryToInitDBusConnection();
|
|
if (!dbusService->isServiceRegistered(appName))
|
|
{
|
|
kError() << "KUniqueApplication: Registering failed!" << endl;
|
|
}
|
|
|
|
QByteArray saved_args;
|
|
QDataStream ds(&saved_args, QIODevice::WriteOnly);
|
|
KCmdLineArgs::saveAppArgs(ds);
|
|
|
|
QByteArray 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
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(appName, "/MainApplication", "org.kde.KUniqueApplication",
|
|
"newInstance");
|
|
msg << new_asn_id << saved_args;
|
|
QDBusReply<int> reply = QDBusConnection::sessionBus().call(msg, QDBus::Block, INT_MAX);
|
|
|
|
if (!reply.isValid())
|
|
{
|
|
QDBusError err = reply.error();
|
|
kError() << "Communication problem with " << KCmdLineArgs::aboutData()->appName() << ", it probably crashed." << endl
|
|
<< "Error message was: " << err.name() << ": \"" << err.message() << "\"" << endl;
|
|
::exit(255);
|
|
}
|
|
::exit(reply);
|
|
break;
|
|
}
|
|
return false; // make insure++ happy
|
|
}
|
|
|
|
|
|
KUniqueApplication::KUniqueApplication(bool GUIenabled, bool configUnique)
|
|
: KApplication( GUIenabled, Private::initHack( configUnique )),
|
|
d(new Private(this))
|
|
{
|
|
d->processingRequest = false;
|
|
d->firstInstance = true;
|
|
|
|
// the sanity checking happened in initHack
|
|
new KUniqueApplicationAdaptor(this);
|
|
|
|
if (Private::s_nofork)
|
|
// Can't call newInstance directly from the constructor since it's virtual...
|
|
QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) );
|
|
}
|
|
|
|
|
|
#ifdef Q_WS_X11
|
|
KUniqueApplication::KUniqueApplication(Display *display, Qt::HANDLE visual,
|
|
Qt::HANDLE colormap, bool configUnique)
|
|
: KApplication( display, visual, colormap, Private::initHack( configUnique )),
|
|
d(new Private(this))
|
|
{
|
|
d->processingRequest = false;
|
|
d->firstInstance = true;
|
|
|
|
// the sanity checking happened in initHack
|
|
new KUniqueApplicationAdaptor(this);
|
|
|
|
if (Private::s_nofork)
|
|
// Can't call newInstance directly from the constructor since it's virtual...
|
|
QTimer::singleShot( 0, this, SLOT(_k_newInstanceNoFork()) );
|
|
}
|
|
#endif
|
|
|
|
|
|
KUniqueApplication::~KUniqueApplication()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
// this gets called before even entering QApplication::QApplication()
|
|
KComponentData KUniqueApplication::Private::initHack(bool configUnique)
|
|
{
|
|
KComponentData cData(KCmdLineArgs::aboutData());
|
|
if (configUnique)
|
|
{
|
|
KConfigGroup cg(cData.config(), "KDE");
|
|
s_multipleInstances = cg.readEntry("MultipleInstances", false);
|
|
}
|
|
if( !KUniqueApplication::start())
|
|
// Already running
|
|
::exit( 0 );
|
|
return cData;
|
|
}
|
|
|
|
void KUniqueApplication::Private::_k_newInstanceNoFork()
|
|
{
|
|
s_handleAutoStarted = false;
|
|
q->newInstance();
|
|
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 KWindowSystem::activateWindow()
|
|
// instead of KStartupInfo::setNewStartupId(). Therefore KWindowSystem::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::restoringSession()
|
|
{
|
|
return d->firstInstance && isSessionRestored();
|
|
}
|
|
|
|
int KUniqueApplication::newInstance()
|
|
{
|
|
if (!d->firstInstance) {
|
|
QList<KMainWindow*> allWindows = KMainWindow::memberList();
|
|
if (!allWindows.isEmpty()) {
|
|
// This method is documented to only work for applications
|
|
// with only one mainwindow.
|
|
KMainWindow* mainWindow = allWindows.first();
|
|
if (mainWindow) {
|
|
mainWindow->show();
|
|
#ifdef 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(mainWindow, startupId());
|
|
#endif
|
|
|
|
}
|
|
}
|
|
}
|
|
return 0; // do nothing in default implementation
|
|
}
|
|
|
|
|
|
////
|
|
|
|
int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args)
|
|
{
|
|
if (!asn_id.isEmpty())
|
|
parent()->setStartupId(asn_id);
|
|
|
|
const int index = parent()->metaObject()->indexOfMethod("loadCommandLineOptionsForNewInstance");
|
|
if (index != -1) {
|
|
// This hook allows the application to set up KCmdLineArgs using addCmdLineOptions
|
|
// before we load the app args. Normally not necessary, but needed by kontact
|
|
// since it switches to other sets of options when called as e.g. kmail or korganizer
|
|
QMetaObject::invokeMethod(parent(), "loadCommandLineOptionsForNewInstance");
|
|
}
|
|
|
|
QDataStream ds(args);
|
|
KCmdLineArgs::loadAppArgs(ds);
|
|
|
|
int ret = parent()->newInstance();
|
|
// Must be done out of the newInstance code, in case it is overloaded
|
|
parent()->d->firstInstance = false;
|
|
return ret;
|
|
}
|
|
|
|
#include "moc_kuniqueapplication.cpp"
|
|
#include "moc_kuniqueapplication_p.cpp"
|