kdeui: do not fork from KUniqueApplication

simplifies startup notification for example, external applications that
pass the "nofork" argument may fail (not uncommon to be passed to
konsole)

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2023-07-13 17:22:49 +03:00
parent 0220b5bd85
commit cd8ab8d93e
5 changed files with 214 additions and 430 deletions

View file

@ -157,9 +157,6 @@ class KAboutData;
* // Register the supported options
* KCmdLineArgs::addCmdLineOptions( options );
*
* // Add options from other components
* KUniqueApplication::addCmdLineOptions();
*
* ....
*
* // Create application object without passing 'argc' and 'argv' again.

View file

@ -17,321 +17,134 @@
Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "kuniqueapplication.h"
#include "kuniqueapplication_p.h"
#include "kmainwindow.h"
#include "kcmdlineargs.h"
#include "kstandarddirs.h"
#include "kaboutdata.h"
#include "kconfiggroup.h"
#include "kwindowsystem.h"
#include <QtCore/QFile>
#include <QtCore/QList>
#include <QtCore/QTimer>
#include <QtDBus/QtDBus>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.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 "kconfig.h"
#include "kstartupinfo.h"
#include "kdebug.h"
#include <QList>
#include <QTimer>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <sys/types.h>
#include <unistd.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
# include <X11/Xlib.h>
#endif
bool KUniqueApplication::Private::s_nofork = false;
bool KUniqueApplication::Private::s_multipleInstances = false;
bool s_kuniqueapplication_startCalled = false;
void
KUniqueApplication::addCmdLineOptions()
bool KUniqueApplication::start(StartFlags flags)
{
KCmdLineOptions kunique_options;
kunique_options.add("nofork", ki18n("Do not run in the background."));
KCmdLineArgs::addCmdLineOptions(kunique_options, KLocalizedString(), "kuniqueapp", "kde");
}
if (s_kuniqueapplication_startCalled) {
return true;
}
s_kuniqueapplication_startCalled = true;
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);
}
}
static QDBusConnectionInterface *tryToInitDBusConnection()
{
// Check the D-Bus connection health
QDBusConnectionInterface* dbusService = 0;
QDBusConnectionInterface* dbusService = nullptr;
QDBusConnection sessionBus = QDBusConnection::sessionBus();
if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface()))
{
if (!sessionBus.isConnected() || !(dbusService = sessionBus.interface())) {
kError() << "KUniqueApplication: Cannot find the D-Bus session server: " << sessionBus.lastError().message();
::exit(255);
}
return dbusService;
}
bool
KUniqueApplication::start(StartFlags flags)
{
if( s_kuniqueapplication_startCalled )
return true;
s_kuniqueapplication_startCalled = true;
if (Private::s_multipleInstances || flags & KUniqueApplication::NonUniqueInstance) {
appName = appName + '-' + QString::number(::getpid());
}
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)
{
// 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.";
::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!";
::exit(255);
}
int fork_result = fork();
switch(fork_result) {
case -1:
kError() << "KUniqueApplication: fork() failed!";
::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.";
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.";
::exit(255);
}
if (errno != EINTR)
{
kError() << "KUniqueApplication: Error reading from pipe.";
::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!";
}
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.\n"
<< "Error message was: " << err.name() << ": \"" << err.message() << "\"";
::exit(255);
}
::exit(reply);
break;
}
return false; // make insure++ happy
// We'll call newInstance in the constructor. Do nothing here.
return true;
}
KUniqueApplication::KUniqueApplication(bool configUnique)
: KApplication( Private::initHack( configUnique )),
: KApplication( Private::initHack(configUnique)),
d(new Private(this))
{
d->firstInstance = true;
d->firstInstance = true;
// the sanity checking happened in initHack
new KUniqueApplicationAdaptor(this);
// 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()) );
QTimer::singleShot(0, this, SLOT(_k_newInstance()));
}
#ifdef Q_WS_X11
KUniqueApplication::KUniqueApplication(Display *display, Qt::HANDLE visual,
Qt::HANDLE colormap, bool configUnique)
: KApplication( display, visual, colormap, Private::initHack( configUnique )),
Qt::HANDLE colormap, bool configUnique)
: KApplication(display, visual, colormap, Private::initHack(configUnique)),
d(new Private(this))
{
d->firstInstance = true;
d->firstInstance = true;
// the sanity checking happened in initHack
new KUniqueApplicationAdaptor(this);
// 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()) );
QTimer::singleShot(0, this, SLOT(_k_newInstance()));
}
#endif
KUniqueApplication::~KUniqueApplication()
{
delete d;
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;
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()
void KUniqueApplication::Private::_k_newInstance()
{
q->newInstance();
firstInstance = false;
q->newInstance();
firstInstance = false;
}
bool KUniqueApplication::restoringSession()
{
return d->firstInstance && isSessionRestored();
return d->firstInstance && isSessionRestored();
}
int KUniqueApplication::newInstance()
@ -355,16 +168,16 @@ int KUniqueApplication::newInstance()
}
}
}
return 0; // do nothing in default implementation
// do nothing in default implementation
return 0;
}
////
int KUniqueApplicationAdaptor::newInstance(const QByteArray &asn_id, const QByteArray &args)
{
if (!asn_id.isEmpty())
parent()->setStartupId(asn_id);
if (!asn_id.isEmpty()) {
parent()->setStartupId(asn_id);
}
QDataStream ds(args);
KCmdLineArgs::loadAppArgs(ds);

View file

@ -42,9 +42,6 @@
* The .desktop file for the application should state X-DBUS-StartupType=Unique,
* see ktoolinvocation.h
*
* If you use command line options before start() is called, you MUST call addCmdLineOptions()
* so that the KUniqueApplication-specific command-line options can still work.
*
* If your application is used to open files, it should also support the --tempfile
* option (see KCmdLineArgs::addTempFileOption()), to delete tempfiles after use.
* Add X-KDE-HasTempFileOption=true to the .desktop file to indicate this.
@ -54,165 +51,151 @@
*/
class KDEUI_EXPORT KUniqueApplication : public KApplication
{
Q_OBJECT
Q_OBJECT
public:
/**
* Constructor. Takes command line arguments from KCmdLineArgs
*
* @param configUnique If true, the uniqueness of the application will
* depend on the value of the "MultipleInstances"
* key in the "KDE" group of the application config file.
*/
explicit KUniqueApplication( bool configUnique=false);
/**
* Constructor. Takes command line arguments from KCmdLineArgs
*
* @param configUnique If true, the uniqueness of the application will
* depend on the value of the "MultipleInstances"
* key in the "KDE" group of the application config file.
*/
explicit KUniqueApplication(bool configUnique = false);
#ifdef Q_WS_X11
/**
* Constructor. Takes command line arguments from KCmdLineArgs
*
* @param display Will be passed to Qt as the X display. The display
* must be valid and already opened.
*
* @param visual Pointer to the X11 visual that should be used by the
* application. If NULL, the default visual will be used instead.
*
* @param colormap The colormap that should be used by the application.
* If 0, the default colormap will be used instead.
*
* @param configUnique If true, the uniqueness of the application will
* depend on the value of the "MultipleInstances"
* key in the "KDE" group of the application config file.
*/
explicit KUniqueApplication( Display *display,
Qt::HANDLE visual=0,
Qt::HANDLE colormap=0,
bool configUnique=false);
/**
* Constructor. Takes command line arguments from KCmdLineArgs
*
* @param display Will be passed to Qt as the X display. The display
* must be valid and already opened.
*
* @param visual Pointer to the X11 visual that should be used by the
* application. If NULL, the default visual will be used instead.
*
* @param colormap The colormap that should be used by the application.
* If 0, the default colormap will be used instead.
*
* @param configUnique If true, the uniqueness of the application will
* depend on the value of the "MultipleInstances"
* key in the "KDE" group of the application config file.
*/
explicit KUniqueApplication(Display *display,
Qt::HANDLE visual=0,
Qt::HANDLE colormap=0,
bool configUnique=false);
#endif
/**
* Adds command line options specific for KUniqueApplication.
*
* Should be called before calling KUniqueApplication constructor
* and / or start().
*/
static void addCmdLineOptions();
/**
* These flags can be used to specify how new instances of
* unique applications are created.
*/
enum StartFlag {
/**
* Create a new instance of the application in a new process and
* do not attempt to re-use an existing process.
*
* With this flag set, the new instance of the application will
* behave as if it were a plain KApplication rather than a KUniqueApplication.
*
* This is useful if you have an application where all instances are typically run
* in a single process but under certain circumstances new instances may require
* their own process.
*/
NonUniqueInstance = 0x1
};
Q_DECLARE_FLAGS(StartFlags,StartFlag)
/**
* These flags can be used to specify how new instances of
* unique applications are created.
*/
enum StartFlag
{
/**
* Create a new instance of the application in a new process and
* do not attempt to re-use an existing process.
*
* With this flag set, the new instance of the application will
* behave as if it were a plain KApplication rather than a KUniqueApplication.
*
* This is useful if you have an application where all instances are typically run
* in a single process but under certain circumstances new instances may require
* their own process.
*/
NonUniqueInstance = 0x1
};
Q_DECLARE_FLAGS(StartFlags,StartFlag)
/**
* Starts and registers with D-Bus.
*
* The command line arguments are being sent via D-Bus to newInstance()
* and will be received once the application enters the event loop.
*
* Typically this is used like:
* \code
* int main(int argc, char **argv) {
* KAboutData about("myappname", 0, ki18n("myAppName"), .....);
* KCmdLineArgs::init(argc, argv, &about);
*
* if (!KUniqueApplication::start()) {
* fprintf(stderr, "myAppName is already running!\n");
* return 0;
* }
* KUniqueApplication a;
* return a.exec();
* }
* \endcode
* Note that it's not necessary to call start() explicitly. It will be
* called automatically before creating KUniqueApplication if it hasn't
* been called yet, without any performance impact.
*
* @param flags Optional flags which control how a new instance
* of the application is started.
* @return true if registration is successful.
* false if another process was already running.
*/
static bool start(StartFlags flags = 0);
/**
* Forks and registers with D-Bus.
*
* The command line arguments are being sent via D-Bus to newInstance()
* and will be received once the application enters the event loop.
*
* Typically this is used like:
* \code
* int main(int argc, char **argv) {
* KAboutData about("myappname", 0, ki18n("myAppName"), .....);
* KCmdLineArgs::init(argc, argv, &about);
* KCmdLineArgs::addCmdLineOptions( myCmdOptions );
* KUniqueApplication::addCmdLineOptions();
*
* if (!KUniqueApplication::start()) {
* fprintf(stderr, "myAppName is already running!\n");
* return 0;
* }
* KUniqueApplication a;
* return a.exec();
* }
* \endcode
* Note that it's not necessary to call start() explicitly. It will be
* called automatically before creating KUniqueApplication if it hasn't
* been called yet, without any performance impact.
*
* Also note that you MUST call KUniqueApplication::addCmdLineOptions(),
* if you use command line options before start() is called.
*
* @param flags Optional flags which control how a new instance
* of the application is started.
* @return true if registration is successful.
* false if another process was already running.
*/
static bool start(StartFlags flags = 0);
/**
* Destructor
*/
virtual ~KUniqueApplication();
/**
* Destructor
*/
virtual ~KUniqueApplication();
/**
* Creates a new "instance" of the application.
*
* Usually this will involve making some calls into the GUI portion of your
* application asking for a new window to be created, possibly with
* some data already loaded based on the arguments received.
*
* Command line arguments have been passed to KCmdLineArgs before this
* function is called and can be checked in the usual way.
*
* The default implementation ensures the mainwindow of the already
* running instance is shown and activated if necessary. If your
* application has only one mainwindow, you should call this default
* implementation and only add your special handling if needed.
*
* Note that newInstance() is called also in the first started
* application process.
*
* For applications that share one process for several mainwindows,
* the reimplementation could be:
* \code
int MyApp::newInstance()
{
KCmdLineArgs::setCwd(QDir::currentPath().toUtf8());
KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
static bool first = true;
if (args->count() > 0) {
for (int i = 0; i < args->count(); ++i) {
openWindow(args->url(i));
}
} else if( !first || !isSessionRestored()) {
openWindow(KUrl()); // create a new window
}
first = false;
args->clear();
return 0;
}
* \endcode
*
* @return An exit value. The calling process will exit with this value.
*/
virtual int newInstance();
/**
* Creates a new "instance" of the application.
*
* Usually this will involve making some calls into the GUI portion of your
* application asking for a new window to be created, possibly with
* some data already loaded based on the arguments received.
*
* Command line arguments have been passed to KCmdLineArgs before this
* function is called and can be checked in the usual way.
*
* The default implementation ensures the mainwindow of the already
* running instance is shown and activated if necessary. If your
* application has only one mainwindow, you should call this default
* implementation and only add your special handling if needed.
*
* Note that newInstance() is called also in the first started
* application process.
*
* For applications that share one process for several mainwindows,
* the reimplementation could be:
* \code
int MyApp::newInstance()
{
KCmdLineArgs::setCwd(QDir::currentPath().toUtf8());
KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
static bool first = true;
if (args->count() > 0) {
for (int i = 0; i < args->count(); ++i) {
openWindow(args->url(i));
}
} else if( !first || !isSessionRestored()) {
openWindow(KUrl()); // create a new window
}
first = false;
args->clear();
return 0;
}
* \endcode
*
* @return An exit value. The calling process will exit with this value.
*/
virtual int newInstance();
/**
* Returns whether newInstance() is being called while session
* restoration is in progress.
*/
bool restoringSession();
/**
* Returns whether newInstance() is being called while session
* restoration is in progress.
*/
bool restoringSession();
private:
friend class KUniqueApplicationAdaptor;
class Private;
Private * const d;
friend class KUniqueApplicationAdaptor;
class Private;
Private * const d;
Q_PRIVATE_SLOT(d, void _k_newInstanceNoFork())
Q_PRIVATE_SLOT(d, void _k_newInstance())
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KUniqueApplication::StartFlags)

View file

@ -21,7 +21,6 @@
#define KUNIQUEAPPLICATION_P_H
#include <QtDBus/qdbusabstractadaptor.h>
#include <kcmdlineargs.h>
class KUniqueApplication::Private
{
@ -31,38 +30,31 @@ public:
{
}
void _k_newInstanceNoFork();
void _k_newInstance();
static KComponentData initHack(bool configUnique);
KUniqueApplication *q;
bool firstInstance;
static bool s_nofork;
static bool s_multipleInstances;
};
class KUniqueApplicationAdaptor: public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KUniqueApplication")
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KUniqueApplication")
public:
KUniqueApplicationAdaptor(KUniqueApplication *parent)
: QDBusAbstractAdaptor(parent)
{ }
KUniqueApplicationAdaptor(KUniqueApplication *parent)
: QDBusAbstractAdaptor(parent)
{
}
inline KUniqueApplication *parent() const
{ return static_cast<KUniqueApplication *>(QDBusAbstractAdaptor::parent()); }
inline KUniqueApplication *parent() const
{ return static_cast<KUniqueApplication *>(QDBusAbstractAdaptor::parent()); }
public Q_SLOTS:
int newInstance(const QByteArray &asn_id = QByteArray(), const QByteArray &args = QByteArray());
int newInstance(const QByteArray &asn_id = QByteArray(), const QByteArray &args = QByteArray());
};
#endif
/*
* Local variables:
* c-basic-offset: 2
* indent-tabs-mode: nil
* End:
*/
#endif // KUNIQUEAPPLICATION_P_H

View file

@ -75,7 +75,6 @@ int main(int argc, char *argv[])
KAboutData about("kuniqueapptest", 0, ki18n("kuniqueapptest"), "version");
KCmdLineArgs::init(argc, argv, &about);
KCmdLineArgs::addCmdLineOptions( options );
KUniqueApplication::addCmdLineOptions();
if (!TestApp::start())
{