/* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) Copyright (C) 1998, 1999, 2000 KDE Team 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 "kapplication.h" #include "kdeversion.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kaboutdata.h" #include "kcrash.h" #include "kconfig.h" #include "kcmdlineargs.h" #include "kglobalsettings.h" #include "kdebug.h" #include "kglobal.h" #include "kicon.h" #include "kiconloader.h" #include "klocale.h" #include "kstandarddirs.h" #include "kstandardshortcut.h" #include "kurl.h" #include "kwindowsystem.h" #include "kde_file.h" #include "kstartupinfo.h" #include "kcomponentdata.h" #include "kmainwindow.h" #include "kmenu.h" #include "kconfiggroup.h" #include "kactioncollection.h" #include "kdebugger.h" #include "kapplication_adaptor.h" #include #include #include #include #include #include #include #ifdef Q_WS_X11 # include # include # include # include # include #endif KApplication* KApplication::KApp = 0L; static const int s_quit_signals[] = { SIGTERM, SIGHUP, SIGINT, 0 }; static QWidgetList s_asked; static void quit_handler(int sig) { if (!qApp) { KDE_signal(sig, SIG_DFL); return; } if (qApp->type() == KAPPLICATION_GUI_TYPE) { const QWidgetList toplevelwidgets = QApplication::topLevelWidgets(); if (!toplevelwidgets.isEmpty()) { kDebug() << "closing top-level main windows"; foreach (QWidget* topwidget, toplevelwidgets) { if (!topwidget || !topwidget->isWindow() || !topwidget->inherits("QMainWindow")) { continue; } if (s_asked.contains(topwidget)) { kDebug() << "already asked" << topwidget; continue; } kDebug() << "closing" << topwidget; if (!topwidget->close()) { kDebug() << "not quiting because a top-level window did not close"; return; } } kDebug() << "all top-level main windows closed"; } } KDE_signal(sig, SIG_DFL); qApp->quit(); } static void kRegisterSessionClient(const bool enable, const QString &serviceName) { if (serviceName.isEmpty()) { return; } QDBusInterface sessionManager( "org.kde.plasma-desktop", "/App", "local.PlasmaApp", QDBusConnection::sessionBus() ); if (sessionManager.isValid()) { sessionManager.call(enable ? "registerClient" : "unregisterClient", serviceName); } else { kWarning() << "org.kde.plasma-desktop is not valid interface"; } } static QString kSessionConfigName() { return QString::fromLatin1("%1_%2").arg(QCoreApplication::applicationName()).arg(QCoreApplication::applicationPid()); } /* Private data */ class KApplicationPrivate { public: KApplicationPrivate(KApplication* q, const QByteArray &cName) : q(q) , adaptor(nullptr) , componentData(cName) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(false) , debugger(nullptr) { } KApplicationPrivate(KApplication* q, const KComponentData &cData) : q(q) , adaptor(nullptr) , componentData(cData) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(false) , debugger(nullptr) { } KApplicationPrivate(KApplication *q) : q(q) , adaptor(nullptr) , componentData(KCmdLineArgs::aboutData()) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(false) , debugger(nullptr) { } void _k_x11FilterDestroyed(); void _k_checkAppStartedSlot(); void _k_aboutToQuitSlot(); void init(); void parseCommandLine( ); // Handle KDE arguments (Using KCmdLineArgs) KApplication *q; KApplicationAdaptor* adaptor; QString serviceName; KComponentData componentData; QByteArray startup_id; QTimer* app_started_timer; bool session_save; QString sessionKey; KConfig* pSessionConfig; //instance specific application config object bool bSessionManagement; KDebugger* debugger; }; static QList< QWeakPointer< QWidget > > *x11Filter = 0; /** * Installs a handler for the SIGPIPE signal. It is thrown when you write to * a pipe or socket that has been closed. * The handler is installed automatically in the constructor, but you may * need it if your application or component does not have a KApplication * instance. */ static void installSigpipeHandler() { struct sigaction act; act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGPIPE, &act, 0); } void KApplication::installX11EventFilter( QWidget* filter ) { if ( !filter ) return; if (!x11Filter) x11Filter = new QList< QWeakPointer< QWidget > >; connect ( filter, SIGNAL(destroyed()), this, SLOT(_k_x11FilterDestroyed()) ); x11Filter->append( filter ); } void KApplicationPrivate::_k_x11FilterDestroyed() { q->removeX11EventFilter( static_cast< const QWidget* >(q->sender())); } void KApplication::removeX11EventFilter( const QWidget* filter ) { if ( !x11Filter || !filter ) return; // removeAll doesn't work, creating QWeakPointer to something that's about to be deleted aborts // x11Filter->removeAll( const_cast< QWidget* >( filter )); QMutableListIterator< QWeakPointer< QWidget > > it( *x11Filter ); while (it.hasNext()) { QWeakPointer< QWidget > wp = it.next(); if( wp.isNull() || wp.data() == filter ) it.remove(); } if ( x11Filter->isEmpty() ) { delete x11Filter; x11Filter = 0; } } bool KApplication::notify(QObject *receiver, QEvent *event) { QEvent::Type t = event->type(); if( t == QEvent::Show && receiver->isWidgetType()) { QWidget* w = static_cast(receiver); #if defined Q_WS_X11 if (w->isTopLevel() && !startupId().isEmpty()) { // TODO better done using window group leader? KStartupInfo::setWindowStartupId(w->winId(), startupId()); #endif } if (w->isTopLevel() && !( w->windowFlags() & Qt::X11BypassWindowManagerHint ) && w->windowType() != Qt::Popup && !event->spontaneous()) { if (!d->app_started_timer) { d->app_started_timer = new QTimer(this); connect(d->app_started_timer, SIGNAL(timeout()), SLOT(_k_checkAppStartedSlot())); } if (!d->app_started_timer->isActive()) { d->app_started_timer->setSingleShot(true); d->app_started_timer->start(0); } } } return QApplication::notify(receiver, event); } void KApplicationPrivate::_k_checkAppStartedSlot() { #if defined Q_WS_X11 KStartupInfo::handleAutoAppStartedSending(); #endif // at this point all collections should be set, now is the time to read ther configuration // because it is not done anywhere else. unfortunately that magic also means any collections // created afterwards will need an explicit settings read foreach (KActionCollection* collection, KActionCollection::allCollections()) { collection->readSettings(); } } KApplication::KApplication() : QApplication(KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv()), d(new KApplicationPrivate(this)) { setApplicationName(d->componentData.componentName()); setOrganizationDomain(d->componentData.aboutData()->organizationDomain()); setApplicationVersion(d->componentData.aboutData()->version()); installSigpipeHandler(); d->init(); } #ifdef Q_WS_X11 KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap) : QApplication(dpy, KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap), d(new KApplicationPrivate(this)) { setApplicationName(d->componentData.componentName()); setOrganizationDomain(d->componentData.aboutData()->organizationDomain()); setApplicationVersion(d->componentData.aboutData()->version()); installSigpipeHandler(); d->init(); } KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap, const KComponentData &cData) : QApplication(dpy, KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap), d (new KApplicationPrivate(this, cData)) { setApplicationName(d->componentData.componentName()); setOrganizationDomain(d->componentData.aboutData()->organizationDomain()); setApplicationVersion(d->componentData.aboutData()->version()); installSigpipeHandler(); d->init(); } #endif KApplication::KApplication(const KComponentData &cData) : QApplication(KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv()), d (new KApplicationPrivate(this, cData)) { setApplicationName(d->componentData.componentName()); setOrganizationDomain(d->componentData.aboutData()->organizationDomain()); setApplicationVersion(d->componentData.aboutData()->version()); installSigpipeHandler(); d->init(); } #ifdef Q_WS_X11 KApplication::KApplication(Display *display, int& argc, char** argv, const QByteArray& rAppName) : QApplication(display), d(new KApplicationPrivate(this, rAppName)) { setApplicationName(QString::fromLocal8Bit(rAppName.constData(), rAppName.size())); installSigpipeHandler(); KCmdLineArgs::initIgnore(argc, argv, rAppName); d->init(); } #endif void KApplicationPrivate::init() { if ((getuid() != geteuid()) || (getgid() != getegid())) { fprintf(stderr, "The KDE libraries are not designed to run with suid privileges.\n"); ::exit(127); } KApplication::KApp = q; // make sure the clipboard is created before setting the window icon (bug 209263) (void) QApplication::clipboard(); #if defined Q_WS_X11 KStartupInfoId id = KStartupInfo::currentStartupIdEnv(); KStartupInfo::resetStartupEnv(); startup_id = id.id(); #endif parseCommandLine(); // sanity checking, to make sure we've connected QDBusConnection sessionBus = QDBusConnection::sessionBus(); QDBusConnectionInterface *bus = 0; if (!sessionBus.isConnected() || !(bus = sessionBus.interface())) { kFatal() << "Session bus not found, to circumvent this problem try the following command (with Linux and bash)\n" << "export $(dbus-launch)"; ::exit(125); } extern bool s_kuniqueapplication_startCalled; if ( bus && !s_kuniqueapplication_startCalled ) // don't register again if KUniqueApplication did so already { QStringList parts = q->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts); QString reversedDomain; if (parts.isEmpty()) reversedDomain = QLatin1String("local."); else foreach (const QString& s, parts) { reversedDomain.prepend(QLatin1Char('.')); reversedDomain.prepend(s); } const QString pidSuffix = QString::number( getpid() ).prepend( QLatin1String("-") ); serviceName = reversedDomain + QCoreApplication::applicationName() + pidSuffix; if ( bus->registerService(serviceName) == QDBusConnectionInterface::ServiceNotRegistered ) { kError() << "Couldn't register name '" << serviceName << "' with DBUS - another process owns it already!"; ::exit(126); } } adaptor = new KApplicationAdaptor(q); sessionBus.registerObject(QLatin1String("/MainApplication"), q, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportAdaptors); // Trigger creation of locale. (void) KGlobal::locale(); KSharedConfig::Ptr config = componentData.config(); QByteArray readOnly = qgetenv("KDE_HOME_READONLY"); if (readOnly.isEmpty() && QCoreApplication::applicationName() != QLatin1String("kdialog")) { config->isConfigWritable(true); } if (q->type() == KAPPLICATION_GUI_TYPE) { #ifdef Q_WS_X11 // this is important since we fork() to launch the help (Matthias) fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, FD_CLOEXEC); #endif // Trigger initial settings KGlobalSettings::self()->activate( KGlobalSettings::ApplySettings | KGlobalSettings::ListenForChanges ); } // too late to restart if the application is about to quit (e.g. if QApplication::quit() was // called or SIGTERM was received) q->connect(q, SIGNAL(aboutToQuit()), SLOT(_k_aboutToQuitSlot())); KApplication::quitOnSignal(); KApplication::quitOnDisconnected(); qRegisterMetaType(); qRegisterMetaType(); } KApplication* KApplication::kApplication() { return KApp; } KConfig* KApplication::sessionConfig() { if (!d->pSessionConfig) { // create an instance specific config object QString configName = d->sessionKey; if (configName.isEmpty()) { configName = kSessionConfigName(); } d->pSessionConfig = new KConfig( QString::fromLatin1("session/%1").arg(configName), KConfig::SimpleConfig ); } return d->pSessionConfig; } bool KApplication::saveSession() { s_asked.clear(); foreach (QWidget* topwidget, QApplication::topLevelWidgets()) { if (!topwidget || !topwidget->isWindow() || !topwidget->inherits("QMainWindow")) { continue; } QCloseEvent e; QApplication::sendEvent(topwidget, &e); if (!e.isAccepted()) { s_asked.clear(); return false; } s_asked.append(topwidget); } if (d->pSessionConfig) { // the config is used for restoring and saving, set it up for saving delete d->pSessionConfig; d->pSessionConfig = new KConfig( QString::fromLatin1("session/%1").arg(kSessionConfigName()), KConfig::SimpleConfig ); } d->session_save = true; KConfig* config = KApplication::kApplication()->sessionConfig(); if ( KMainWindow::memberList().count() ){ // According to Jochen Wilhelmy , this // hook is useful for better document orientation KMainWindow::memberList().first()->saveGlobalProperties(config); } int n = 0; foreach (KMainWindow* mw, KMainWindow::memberList()) { n++; mw->savePropertiesInternal(config, n); } KConfigGroup group( config, "Number" ); group.writeEntry("NumberOfWindows", n ); if ( d->pSessionConfig ) { d->pSessionConfig->sync(); } d->session_save = false; return true; } void KApplication::reparseConfiguration() { KGlobal::config()->reparseConfiguration(); } void KApplication::quit() { QApplication::quit(); } void KApplication::disableSessionManagement() { if (d->bSessionManagement) { kRegisterSessionClient(false, d->serviceName); } d->bSessionManagement = false; } void KApplication::enableSessionManagement() { kRegisterSessionClient(true, d->serviceName); d->bSessionManagement = true; } bool KApplication::sessionSaving() const { return d->session_save; } bool KApplication::isSessionRestored() const { return !d->sessionKey.isEmpty(); } void KApplicationPrivate::parseCommandLine( ) { KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kde"); if (args && args->isSet("config")) { QString config = args->getOption("config"); componentData.setConfigName(config); } if ( q->type() != KApplication::Tty ) { QString appicon; if (args && args->isSet("icon") && !args->getOption("icon").trimmed().isEmpty() && !KIconLoader::global()->iconPath(args->getOption("icon"), -1, true).isEmpty()) { appicon = args->getOption("icon"); } if(appicon.isEmpty()) { appicon = componentData.aboutData()->programIconName(); } q->setWindowIcon(KIcon(appicon)); } if (!args) return; if (qgetenv("KDE_DEBUG").isEmpty() && args->isSet("crashhandler")) { // setup default crash handler KCrash::setFlags(KCrash::Notify | KCrash::Log); } #ifdef Q_WS_X11 if (args->isSet("waitforwm")) { Atom type; (void) q->desktop(); // trigger desktop creation, we need PropertyNotify events for the root window int format; unsigned long length, after; unsigned char *data; Atom netSupported = XInternAtom(QX11Info::display(), "_NET_SUPPORTED", False); while (XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), netSupported, 0, 1, false, AnyPropertyType, &type, &format, &length, &after, &data) != Success || !length) { if (data) { XFree(data); } data = nullptr; XEvent event; XWindowEvent(QX11Info::display(), QX11Info::appRootWindow(), PropertyChangeMask, &event); } if (data) { XFree(data); } } #endif if (args->isSet("session")) { sessionKey = args->getOption("session"); } if (args->isSet("debugger")) { debugger = new KDebugger(); debugger->show(); } } KApplication::~KApplication() { if (d->debugger) { delete d->debugger; } if (d->pSessionConfig) { delete d->pSessionConfig; } delete d; KApp = 0; } #ifdef Q_WS_X11 class KAppX11HackWidget: public QWidget { public: bool publicx11Event( XEvent * e) { return x11Event( e ); } }; #endif #ifdef Q_WS_X11 bool KApplication::x11EventFilter( XEvent *_event ) { if (x11Filter) { // either deep-copy or mutex QListIterator< QWeakPointer< QWidget > > it( *x11Filter ); while (it.hasNext()) { QWeakPointer< QWidget > wp = it.next(); if( !wp.isNull() ) if ( static_cast( wp.data() )->publicx11Event(_event)) return true; } } return false; } #endif // Q_WS_X11 void KApplication::updateUserTimestamp( int time ) { #if defined Q_WS_X11 if( time == 0 ) { // get current X timestamp Window w = XCreateSimpleWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0, 0, 0 ); XSelectInput( QX11Info::display(), w, PropertyChangeMask ); unsigned char data[ 1 ]; XChangeProperty( QX11Info::display(), w, XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 ); XEvent ev; XWindowEvent( QX11Info::display(), w, PropertyChangeMask, &ev ); time = ev.xproperty.time; XDestroyWindow( QX11Info::display(), w ); } if( QX11Info::appUserTime() == 0 || NET::timestampCompare( time, QX11Info::appUserTime()) > 0 ) // time > appUserTime QX11Info::setAppUserTime(time); if( QX11Info::appTime() == 0 || NET::timestampCompare( time, QX11Info::appTime()) > 0 ) // time > appTime QX11Info::setAppTime(time); #endif } unsigned long KApplication::userTimestamp() const { #if defined Q_WS_X11 return QX11Info::appUserTime(); #else return 0; #endif } void KApplication::quitOnSignal() { sigset_t handlermask; ::sigemptyset(&handlermask); int counter = 0; while (s_quit_signals[counter]) { KDE_signal(s_quit_signals[counter], quit_handler); ::sigaddset(&handlermask, s_quit_signals[counter]); counter++; } ::sigprocmask(SIG_UNBLOCK, &handlermask, NULL); } void KApplication::quitOnDisconnected() { if (!qApp) { kWarning() << "KApplication::quitOnDisconnected() called before application instance is created"; return; } QDBusConnection::sessionBus().connect( QString(), QString::fromLatin1("/org/freedesktop/DBus/Local"), QString::fromLatin1("org.freedesktop.DBus.Local"), QString::fromLatin1("Disconnected"), qApp, SLOT(quit()) ); } void KApplication::setTopWidget( QWidget *topWidget ) { if( !topWidget ) return; // set the specified caption if ( !topWidget->inherits("KMainWindow") ) { // KMainWindow does this already for us topWidget->setWindowTitle(KGlobal::caption()); } #ifdef Q_WS_X11 // set the app startup notification window property KStartupInfo::setWindowStartupId(topWidget->winId(), startupId()); #endif } QByteArray KApplication::startupId() const { return d->startup_id; } void KApplication::setStartupId(const QByteArray &startup_id) { if (startup_id == d->startup_id) { return; } #if defined Q_WS_X11 KStartupInfo::handleAutoAppStartedSending(); // finish old startup notification if needed #endif if (startup_id.isEmpty()) { d->startup_id = "0"; } else { d->startup_id = startup_id; #if defined Q_WS_X11 KStartupInfoId id; id.initId(startup_id); long timestamp = id.timestamp(); if (timestamp != 0) { updateUserTimestamp(timestamp); } #endif } } void KApplication::clearStartupId() { d->startup_id = "0"; } void KApplicationPrivate::_k_aboutToQuitSlot() { KCrash::setFlags(KCrash::flags() & ~KCrash::AutoRestart); if (bSessionManagement) { kRegisterSessionClient(false, serviceName); } } #include "moc_kapplication.cpp"