From cd8ab8d93ef5888c06142573b512cdbdacf34294 Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Thu, 13 Jul 2023 17:22:49 +0300 Subject: [PATCH] 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 --- kdecore/kernel/kcmdlineargs.h | 3 - kdeui/kernel/kuniqueapplication.cpp | 327 ++++++---------------------- kdeui/kernel/kuniqueapplication.h | 283 +++++++++++------------- kdeui/kernel/kuniqueapplication_p.h | 30 +-- kdeui/tests/kuniqueapptest.cpp | 1 - 5 files changed, 214 insertions(+), 430 deletions(-) diff --git a/kdecore/kernel/kcmdlineargs.h b/kdecore/kernel/kcmdlineargs.h index ee3fe23d..9a214d38 100644 --- a/kdecore/kernel/kcmdlineargs.h +++ b/kdecore/kernel/kcmdlineargs.h @@ -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. diff --git a/kdeui/kernel/kuniqueapplication.cpp b/kdeui/kernel/kuniqueapplication.cpp index 1556bf17..4ab0233d 100644 --- a/kdeui/kernel/kuniqueapplication.cpp +++ b/kdeui/kernel/kuniqueapplication.cpp @@ -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 -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#if defined Q_WS_X11 -#include -#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 - -#include +#include "kconfig.h" +#include "kstartupinfo.h" #include "kdebug.h" +#include +#include +#include +#include + +#include +#include + #if defined Q_WS_X11 -#include -#include -#define DISPLAY "DISPLAY" -#else -# ifdef Q_WS_QWS -# define DISPLAY "QWS_DISPLAY" -# else -# define DISPLAY "DISPLAY" -# endif +# include #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 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 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); diff --git a/kdeui/kernel/kuniqueapplication.h b/kdeui/kernel/kuniqueapplication.h index 0526ccf0..b084ee72 100644 --- a/kdeui/kernel/kuniqueapplication.h +++ b/kdeui/kernel/kuniqueapplication.h @@ -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) diff --git a/kdeui/kernel/kuniqueapplication_p.h b/kdeui/kernel/kuniqueapplication_p.h index 34e5417d..5c7e9685 100644 --- a/kdeui/kernel/kuniqueapplication_p.h +++ b/kdeui/kernel/kuniqueapplication_p.h @@ -21,7 +21,6 @@ #define KUNIQUEAPPLICATION_P_H #include -#include 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(QDBusAbstractAdaptor::parent()); } + inline KUniqueApplication *parent() const + { return static_cast(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 diff --git a/kdeui/tests/kuniqueapptest.cpp b/kdeui/tests/kuniqueapptest.cpp index dc9e2ed6..5025798d 100644 --- a/kdeui/tests/kuniqueapptest.cpp +++ b/kdeui/tests/kuniqueapptest.cpp @@ -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()) {