/* 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 #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 "ksessionmanager.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 #include #include #include #include #include #include #ifdef Q_WS_X11 # include # include # include # include # include # include #endif KApplication* KApplication::KApp = 0L; static const int s_quit_signals[] = { SIGTERM, SIGHUP, SIGINT, 0 }; 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; } 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(); } /* Private data to make keeping binary compatibility easier */ class KApplicationPrivate { public: KApplicationPrivate(KApplication* q, const QByteArray &cName) : q(q) , componentData(cName) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(true) , debugger(nullptr) { } KApplicationPrivate(KApplication* q, const KComponentData &cData) : q(q) , componentData(cData) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(true) , debugger(nullptr) { } KApplicationPrivate(KApplication *q) : q(q) , componentData(KCmdLineArgs::aboutData()) , startup_id("0") , app_started_timer(nullptr) , session_save(false) , pSessionConfig(nullptr) , bSessionManagement(true) , debugger(nullptr) { } void _k_x11FilterDestroyed(); void _k_checkAppStartedSlot(); void _k_disableAutorestartSlot(); QString sessionConfigName() const; void init(); void parseCommandLine( ); // Handle KDE arguments (Using KCmdLineArgs) KApplication *q; 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(); } } /* Auxiliary function to calculate a a session config name used for the instance specific config object. Syntax: "session/_" */ QString KApplicationPrivate::sessionConfigName() const { #ifdef QT_NO_SESSIONMANAGER #error QT_NO_SESSIONMANAGER was set, this will not compile. Reconfigure Qt with Session management support. #endif QString sessKey = q->sessionKey(); if ( sessKey.isEmpty() && !sessionKey.isEmpty() ) sessKey = sessionKey; return QString::fromLatin1("session/%1_%2_%3").arg(QCoreApplication::applicationName()).arg(q->sessionId()).arg(sessKey); } #ifdef Q_WS_X11 static SmcConn mySmcConnection = 0; #else // FIXME(E): Implement for Qt Embedded // Possibly "steal" XFree86's libSM? #endif 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("-") ); const QString 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); } } 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_disableAutorestartSlot())); KApplication::quitOnSignal(); KApplication::quitOnDisconnected(); qRegisterMetaType(); qRegisterMetaType(); } KApplication* KApplication::kApplication() { return KApp; } KConfig* KApplication::sessionConfig() { if (!d->pSessionConfig) // create an instance specific config object d->pSessionConfig = new KConfig( d->sessionConfigName(), KConfig::SimpleConfig ); return d->pSessionConfig; } void KApplication::reparseConfiguration() { KGlobal::config()->reparseConfiguration(); } void KApplication::quit() { QApplication::quit(); } void KApplication::disableSessionManagement() { d->bSessionManagement = false; } void KApplication::enableSessionManagement() { d->bSessionManagement = true; #ifdef Q_WS_X11 // Session management support in Qt/KDE is awfully broken. // If konqueror disables session management right after its startup, // and enables it later (preloading stuff), it won't be properly // saved on session shutdown. // I'm not actually sure why it doesn't work, but saveState() // doesn't seem to be called on session shutdown, possibly // because disabling session management after konqueror startup // disabled it somehow. Forcing saveState() here for this application // seems to fix it. if( mySmcConnection ) { SmcRequestSaveYourself( mySmcConnection, SmSaveLocal, False, SmInteractStyleAny, False, False ); // flush the request IceFlush(SmcGetIceConnection(mySmcConnection)); } #endif } void KApplication::commitData( QSessionManager& sm ) { d->session_save = true; bool canceled = false; foreach (KSessionManager *it, KSessionManager::sessionClients()) { if ( ( canceled = !it->commitData( sm ) ) ) break; } if ( canceled ) sm.cancel(); if ( sm.allowsInteraction() ) { QWidgetList donelist, todolist; QWidget* w; commitDataRestart: todolist = QApplication::topLevelWidgets(); for ( int i = 0; i < todolist.size(); ++i ) { w = todolist.at( i ); if( !w ) break; if ( donelist.contains( w ) ) continue; if ( !w->isHidden() && !w->inherits( "KMainWindow" ) ) { QCloseEvent e; sendEvent( w, &e ); if ( !e.isAccepted() ) break; //canceled donelist.append( w ); //grab the new list that was just modified by our closeevent goto commitDataRestart; } } } if ( !d->bSessionManagement ) sm.setRestartHint( QSessionManager::RestartNever ); else sm.setRestartHint( QSessionManager::RestartIfRunning ); d->session_save = false; } void KApplication::saveState( QSessionManager& sm ) { d->session_save = true; #ifdef Q_WS_X11 static bool firstTime = true; mySmcConnection = (SmcConn) sm.handle(); if ( !d->bSessionManagement ) { sm.setRestartHint( QSessionManager::RestartNever ); d->session_save = false; return; } else { sm.setRestartHint( QSessionManager::RestartIfRunning ); } if ( firstTime ) { firstTime = false; d->session_save = false; return; // no need to save the state. } // remove former session config if still existing, we want a new // and fresh one. Note that we do not delete the config file here, // this is done by the session manager when it executes the // discard commands. In fact it would be harmful to remove the // file here, as the session might be stored under a different // name, meaning the user still might need it eventually. delete d->pSessionConfig; d->pSessionConfig = 0; // finally: do session management bool canceled = false; foreach(KSessionManager* it, KSessionManager::sessionClients()) { if(canceled) break; canceled = !it->saveState( sm ); } // if we created a new session config object, register a proper discard command if ( d->pSessionConfig ) { d->pSessionConfig->sync(); QStringList discard; discard << QLatin1String("rm") << KStandardDirs::locateLocal("config", d->sessionConfigName()); sm.setDiscardCommand( discard ); } else { sm.setDiscardCommand( QStringList( QLatin1String("") ) ); } if ( canceled ) sm.cancel(); #endif d->session_save = false; } bool KApplication::sessionSaving() const { return d->session_save; } 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("smkey")) { sessionKey = args->getOption("smkey"); } if (args->isSet("debugger")) { debugger = new KDebugger(); debugger->show(); } } KApplication::~KApplication() { if (d->debugger) { delete d->debugger; } delete d; KApp = 0; #ifdef Q_WS_X11 mySmcConnection = 0; #endif } #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::updateRemoteUserTimestamp( const QString& service, int time ) { #if defined Q_WS_X11 Q_ASSERT(service.contains('.')); if( time == 0 ) time = QX11Info::appUserTime(); QDBusInterface(service, QLatin1String("/MainApplication"), QString(QLatin1String("org.kde.KApplication"))) .call(QLatin1String("updateUserTimestamp"), time); #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_disableAutorestartSlot() { KCrash::setFlags(KCrash::flags() & ~KCrash::AutoRestart); } #include "moc_kapplication.cpp"