1/* This file is part of the KDE libraries
2 Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
3 Copyright (C) 1998, 1999, 2000 KDE Team
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19 */
20
21#include "kapplication.h"
22// TODO: KDE5 +#include "kdeversion.h"
23
24#include <config.h>
25
26#include <QtCore/QDir>
27#include <QtCore/QFile>
28#include <QtGui/QSessionManager>
29#include <QtGui/QStyleFactory>
30#include <QtCore/QTimer>
31#include <QtGui/QWidget>
32#include <QtCore/QList>
33#include <QtDBus/QtDBus>
34#include <QtCore/QMetaType>
35
36#include "kauthorized.h"
37#include "kaboutdata.h"
38#include "kcheckaccelerators.h"
39#include "kcrash.h"
40#include "kconfig.h"
41#include "kcmdlineargs.h"
42#include "kclipboard.h"
43#include "kglobalsettings.h"
44#include "kdebug.h"
45#include "kglobal.h"
46#include "kicon.h"
47#include "klocale.h"
48#include "ksessionmanager.h"
49#include "kstandarddirs.h"
50#include "kstandardshortcut.h"
51#include "ktoolinvocation.h"
52#include "kgesturemap.h"
53#include "kurl.h"
54#include "kmessage.h"
55#include "kmessageboxmessagehandler.h"
56
57#if defined Q_WS_X11
58#include <QtGui/qx11info_x11.h>
59#include <kstartupinfo.h>
60#endif
61
62#include <sys/types.h>
63#ifdef HAVE_SYS_STAT_H
64#include <sys/stat.h>
65#endif
66#include <sys/wait.h>
67
68#ifndef Q_WS_WIN
69#include "kwindowsystem.h"
70#endif
71
72#include <fcntl.h>
73#include <stdlib.h> // srand(), rand()
74#include <unistd.h>
75#if defined Q_WS_X11
76//#ifndef Q_WS_QWS //FIXME(embedded): NetWM should talk to QWS...
77#include <netwm.h>
78#endif
79
80#ifdef HAVE_PATHS_H
81#include <paths.h>
82#endif
83
84#ifdef Q_WS_X11
85#include <X11/Xlib.h>
86#include <X11/Xutil.h>
87#include <X11/Xatom.h>
88#include <X11/SM/SMlib.h>
89#include <fixx11h.h>
90
91#include <QX11Info>
92#endif
93
94#ifdef Q_WS_MACX
95// ick
96#undef Status
97#include <Carbon/Carbon.h>
98#include <QImage>
99#include <ksystemtrayicon.h>
100#include <kkernel_mac.h>
101#endif
102
103#ifdef Q_OS_UNIX
104#include <signal.h>
105#endif
106
107#include <QtGui/QActionEvent>
108#include <kcomponentdata.h>
109
110KApplication* KApplication::KApp = 0L;
111bool KApplication::loadedByKdeinit = false;
112
113#ifdef Q_WS_X11
114static Atom atom_DesktopWindow;
115static Atom atom_NetSupported;
116static Atom kde_xdnd_drop;
117static QByteArray* startup_id_tmp;
118#endif
119
120template class QList<KSessionManager*>;
121
122#ifdef Q_WS_X11
123extern "C" {
124static int kde_xio_errhandler( Display * dpy )
125{
126 return kapp->xioErrhandler( dpy );
127}
128
129static int kde_x_errhandler( Display *dpy, XErrorEvent *err )
130{
131 return kapp->xErrhandler( dpy, err );
132}
133
134}
135#endif
136
137#ifdef Q_WS_WIN
138void KApplication_init_windows();
139#endif
140
141/*
142 Private data to make keeping binary compatibility easier
143 */
144class KApplicationPrivate
145{
146public:
147 KApplicationPrivate(KApplication* q, const QByteArray &cName)
148 : q(q)
149 , componentData(cName)
150 , startup_id("0")
151 , app_started_timer(0)
152 , session_save(false)
153#ifdef Q_WS_X11
154 , oldIceIOErrorHandler(0)
155 , oldXErrorHandler(0)
156 , oldXIOErrorHandler(0)
157#endif
158 , pSessionConfig( 0 )
159 , bSessionManagement( true )
160 {
161 }
162
163 KApplicationPrivate(KApplication* q, const KComponentData &cData)
164 : q(q)
165 , componentData(cData)
166 , startup_id("0")
167 , app_started_timer(0)
168 , session_save(false)
169#ifdef Q_WS_X11
170 , oldIceIOErrorHandler(0)
171 , oldXErrorHandler(0)
172 , oldXIOErrorHandler(0)
173#endif
174 , pSessionConfig( 0 )
175 , bSessionManagement( true )
176 {
177 }
178
179 KApplicationPrivate(KApplication *q)
180 : q(q)
181 , componentData(KCmdLineArgs::aboutData())
182 , startup_id( "0" )
183 , app_started_timer( 0 )
184 , session_save( false )
185#ifdef Q_WS_X11
186 , oldIceIOErrorHandler( 0 )
187 , oldXErrorHandler( 0 )
188 , oldXIOErrorHandler( 0 )
189#endif
190 , pSessionConfig( 0 )
191 , bSessionManagement( true )
192 {
193 }
194
195 ~KApplicationPrivate()
196 {
197 }
198
199#ifndef KDE3_SUPPORT
200 KConfig *config() { return KGlobal::config().data(); }
201#endif
202
203 void _k_x11FilterDestroyed();
204 void _k_checkAppStartedSlot();
205 void _k_slot_KToolInvocation_hook(QStringList&, QByteArray&);
206
207 QString sessionConfigName() const;
208 void init(bool GUIenabled=true);
209 void parseCommandLine( ); // Handle KDE arguments (Using KCmdLineArgs)
210 static void preqapplicationhack();
211 static void preread_app_startup_id();
212 void read_app_startup_id();
213
214 KApplication *q;
215 KComponentData componentData;
216 QByteArray startup_id;
217 QTimer* app_started_timer;
218 bool session_save;
219
220#ifdef Q_WS_X11
221 IceIOErrorHandler oldIceIOErrorHandler;
222 int (*oldXErrorHandler)(Display*,XErrorEvent*);
223 int (*oldXIOErrorHandler)(Display*);
224#endif
225
226 QString sessionKey;
227 QString pSessionConfigFile;
228
229 KConfig* pSessionConfig; //instance specific application config object
230 bool bSessionManagement;
231};
232
233
234static QList< QWeakPointer< QWidget > > *x11Filter = 0;
235
236/**
237 * Installs a handler for the SIGPIPE signal. It is thrown when you write to
238 * a pipe or socket that has been closed.
239 * The handler is installed automatically in the constructor, but you may
240 * need it if your application or component does not have a KApplication
241 * instance.
242 */
243static void installSigpipeHandler()
244{
245#ifdef Q_OS_UNIX
246 struct sigaction act;
247 act.sa_handler = SIG_IGN;
248 sigemptyset( &act.sa_mask );
249 act.sa_flags = 0;
250 sigaction( SIGPIPE, &act, 0 );
251#endif
252}
253
254void KApplication::installX11EventFilter( QWidget* filter )
255{
256 if ( !filter )
257 return;
258 if (!x11Filter)
259 x11Filter = new QList< QWeakPointer< QWidget > >;
260 connect ( filter, SIGNAL(destroyed()), this, SLOT(_k_x11FilterDestroyed()) );
261 x11Filter->append( filter );
262}
263
264void KApplicationPrivate::_k_x11FilterDestroyed()
265{
266 q->removeX11EventFilter( static_cast< const QWidget* >(q->sender()));
267}
268
269void KApplication::removeX11EventFilter( const QWidget* filter )
270{
271 if ( !x11Filter || !filter )
272 return;
273 // removeAll doesn't work, creating QWeakPointer to something that's about to be deleted aborts
274 // x11Filter->removeAll( const_cast< QWidget* >( filter ));
275 for( QMutableListIterator< QWeakPointer< QWidget > > it( *x11Filter );
276 it.hasNext();
277 ) {
278 QWidget* w = it.next().data();
279 if( w == filter || w == NULL )
280 it.remove();
281 }
282 if ( x11Filter->isEmpty() ) {
283 delete x11Filter;
284 x11Filter = 0;
285 }
286}
287
288bool KApplication::notify(QObject *receiver, QEvent *event)
289{
290 QEvent::Type t = event->type();
291 if( t == QEvent::Show && receiver->isWidgetType())
292 {
293 QWidget* w = static_cast< QWidget* >( receiver );
294#if defined Q_WS_X11
295 if( w->isTopLevel() && !startupId().isEmpty()) // TODO better done using window group leader?
296 KStartupInfo::setWindowStartupId( w->winId(), startupId());
297#endif
298 if( w->isTopLevel() && !( w->windowFlags() & Qt::X11BypassWindowManagerHint ) && w->windowType() != Qt::Popup && !event->spontaneous())
299 {
300 if( d->app_started_timer == NULL )
301 {
302 d->app_started_timer = new QTimer( this );
303 connect( d->app_started_timer, SIGNAL(timeout()), SLOT(_k_checkAppStartedSlot()));
304 }
305 if( !d->app_started_timer->isActive()) {
306 d->app_started_timer->setSingleShot( true );
307 d->app_started_timer->start( 0 );
308 }
309 }
310 }
311 return QApplication::notify(receiver, event);
312}
313
314void KApplicationPrivate::_k_checkAppStartedSlot()
315{
316#if defined Q_WS_X11
317 KStartupInfo::handleAutoAppStartedSending();
318#endif
319}
320
321/*
322 Auxiliary function to calculate a a session config name used for the
323 instance specific config object.
324 Syntax: "session/<appname>_<sessionId>"
325 */
326QString KApplicationPrivate::sessionConfigName() const
327{
328#ifdef QT_NO_SESSIONMANAGER
329#error QT_NO_SESSIONMANAGER was set, this will not compile. Reconfigure Qt with Session management support.
330#endif
331 QString sessKey = q->sessionKey();
332 if ( sessKey.isEmpty() && !sessionKey.isEmpty() )
333 sessKey = sessionKey;
334 return QString(QLatin1String("session/%1_%2_%3")).arg(q->applicationName()).arg(q->sessionId()).arg(sessKey);
335}
336
337#ifdef Q_WS_X11
338static SmcConn mySmcConnection = 0;
339#else
340// FIXME(E): Implement for Qt Embedded
341// Possibly "steal" XFree86's libSM?
342#endif
343
344KApplication::KApplication(bool GUIenabled)
345 : QApplication((KApplicationPrivate::preqapplicationhack(),KCmdLineArgs::qtArgc()), KCmdLineArgs::qtArgv(), GUIenabled),
346 d(new KApplicationPrivate(this))
347{
348 d->read_app_startup_id();
349 setApplicationName(d->componentData.componentName());
350 setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
351 installSigpipeHandler();
352 d->init(GUIenabled);
353}
354
355#ifdef Q_WS_X11
356KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap)
357 : QApplication((KApplicationPrivate::preqapplicationhack(),dpy), KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap),
358 d(new KApplicationPrivate(this))
359{
360 d->read_app_startup_id();
361 setApplicationName(d->componentData.componentName());
362 setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
363 installSigpipeHandler();
364 d->init();
365}
366
367KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap, const KComponentData &cData)
368 : QApplication((KApplicationPrivate::preqapplicationhack(),dpy), KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap),
369 d (new KApplicationPrivate(this, cData))
370{
371 d->read_app_startup_id();
372 setApplicationName(d->componentData.componentName());
373 setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
374 installSigpipeHandler();
375 d->init();
376}
377#endif
378
379KApplication::KApplication(bool GUIenabled, const KComponentData &cData)
380 : QApplication((KApplicationPrivate::preqapplicationhack(),KCmdLineArgs::qtArgc()), KCmdLineArgs::qtArgv(), GUIenabled),
381 d (new KApplicationPrivate(this, cData))
382{
383 d->read_app_startup_id();
384 setApplicationName(d->componentData.componentName());
385 setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
386 installSigpipeHandler();
387 d->init(GUIenabled);
388}
389
390#ifdef Q_WS_X11
391KApplication::KApplication(Display *display, int& argc, char** argv, const QByteArray& rAppName,
392 bool GUIenabled)
393 : QApplication((KApplicationPrivate::preqapplicationhack(),display)),
394 d(new KApplicationPrivate(this, rAppName))
395{
396 Q_UNUSED(GUIenabled);
397 d->read_app_startup_id();
398 setApplicationName(QLatin1String(rAppName));
399 installSigpipeHandler();
400 KCmdLineArgs::initIgnore(argc, argv, rAppName.data());
401 d->init();
402}
403#endif
404
405// this function is called in KApplication ctors while evaluating arguments to QApplication ctor,
406// i.e. before QApplication ctor is called
407void KApplicationPrivate::preqapplicationhack()
408{
409 preread_app_startup_id();
410
411 KGlobal::config(); // initialize qt plugin path (see KComponentDataPrivate::lazyInit)
412}
413
414int KApplication::xioErrhandler( Display* dpy )
415{
416 if(kapp)
417 {
418#ifdef Q_WS_X11
419 d->oldXIOErrorHandler( dpy );
420#else
421 Q_UNUSED(dpy);
422#endif
423 }
424 exit( 1 );
425 return 0;
426}
427
428int KApplication::xErrhandler( Display* dpy, void* err_ )
429{ // no idea how to make forward decl. for XErrorEvent
430#ifdef Q_WS_X11
431 XErrorEvent* err = static_cast< XErrorEvent* >( err_ );
432 if(kapp)
433 {
434 // add KDE specific stuff here
435 d->oldXErrorHandler( dpy, err );
436 }
437 const QByteArray fatalXError = qgetenv("KDE_FATAL_X_ERROR");
438 if (!fatalXError.isEmpty()) {
439 abort();
440 }
441#endif
442 return 0;
443}
444
445void KApplication::iceIOErrorHandler( _IceConn *conn )
446{
447 emit aboutToQuit();
448
449#ifdef Q_WS_X11
450 if ( d->oldIceIOErrorHandler != NULL )
451 (*d->oldIceIOErrorHandler)( conn );
452#endif
453 exit( 1 );
454}
455
456void KApplicationPrivate::init(bool GUIenabled)
457{
458 if ((getuid() != geteuid()) ||
459 (getgid() != getegid()))
460 {
461 fprintf(stderr, "The KDE libraries are not designed to run with suid privileges.\n");
462 ::exit(127);
463 }
464
465#ifdef Q_WS_MAC
466 mac_initialize_dbus();
467#endif
468
469 KApplication::KApp = q;
470
471 // make sure the clipboard is created before setting the window icon (bug 209263)
472 if(GUIenabled)
473 (void) QApplication::clipboard();
474
475 extern KDECORE_EXPORT bool kde_kdebug_enable_dbus_interface;
476 kde_kdebug_enable_dbus_interface = true;
477
478 parseCommandLine();
479
480 if(GUIenabled)
481 (void) KClipboardSynchronizer::self();
482
483 QApplication::setDesktopSettingsAware( false );
484
485#ifdef Q_WS_X11
486 // create all required atoms in _one_ roundtrip to the X server
487 if ( q->type() == KApplication::GuiClient ) {
488 const int max = 20;
489 Atom* atoms[max];
490 char* names[max];
491 Atom atoms_return[max];
492 int n = 0;
493
494 atoms[n] = &atom_DesktopWindow;
495 names[n++] = (char *) "KDE_DESKTOP_WINDOW";
496
497 atoms[n] = &atom_NetSupported;
498 names[n++] = (char *) "_NET_SUPPORTED";
499
500 atoms[n] = &kde_xdnd_drop;
501 names[n++] = (char *) "XdndDrop";
502
503 XInternAtoms( QX11Info::display(), names, n, false, atoms_return );
504
505 for (int i = 0; i < n; i++ )
506 *atoms[i] = atoms_return[i];
507 }
508#endif
509
510
511 // sanity checking, to make sure we've connected
512 extern void qDBusBindToApplication();
513 qDBusBindToApplication();
514 QDBusConnectionInterface *bus = 0;
515 if (!QDBusConnection::sessionBus().isConnected() || !(bus = QDBusConnection::sessionBus().interface())) {
516 kFatal(240) << "Session bus not found" << endl <<
517 "To circumvent this problem try the following command (with Linux and bash)" << endl <<
518 "export $(dbus-launch)";
519 ::exit(125);
520 }
521
522 extern bool s_kuniqueapplication_startCalled;
523 if ( bus && !s_kuniqueapplication_startCalled ) // don't register again if KUniqueApplication did so already
524 {
525 QStringList parts = q->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts);
526 QString reversedDomain;
527 if (parts.isEmpty())
528 reversedDomain = QLatin1String("local.");
529 else
530 foreach (const QString& s, parts)
531 {
532 reversedDomain.prepend(QLatin1Char('.'));
533 reversedDomain.prepend(s);
534 }
535 const QString pidSuffix = QString::number( getpid() ).prepend( QLatin1String("-") );
536 const QString serviceName = reversedDomain + q->applicationName() + pidSuffix;
537 if ( bus->registerService(serviceName) == QDBusConnectionInterface::ServiceNotRegistered ) {
538 kError(240) << "Couldn't register name '" << serviceName << "' with DBUS - another process owns it already!" << endl;
539 ::exit(126);
540 }
541 }
542 QDBusConnection::sessionBus().registerObject(QLatin1String("/MainApplication"), q,
543 QDBusConnection::ExportScriptableSlots |
544 QDBusConnection::ExportScriptableProperties |
545 QDBusConnection::ExportAdaptors);
546
547 // Trigger creation of locale.
548 (void) KGlobal::locale();
549
550 KSharedConfig::Ptr config = componentData.config();
551 QByteArray readOnly = qgetenv("KDE_HOME_READONLY");
552 if (readOnly.isEmpty() && q->applicationName() != QLatin1String("kdialog"))
553 {
554 if (KAuthorized::authorize(QLatin1String("warn_unwritable_config")))
555 config->isConfigWritable(true);
556 }
557
558 if (q->type() == KApplication::GuiClient)
559 {
560#ifdef Q_WS_X11
561 // this is important since we fork() to launch the help (Matthias)
562 fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, FD_CLOEXEC);
563 // set up the fancy (=robust and error ignoring ) KDE xio error handlers (Matthias)
564 oldXErrorHandler = XSetErrorHandler( kde_x_errhandler );
565 oldXIOErrorHandler = XSetIOErrorHandler( kde_xio_errhandler );
566#endif
567
568 // Trigger initial settings
569 KGlobalSettings::self()->activate();
570
571 KMessage::setMessageHandler( new KMessageBoxMessageHandler(0) );
572
573 KCheckAccelerators::initiateIfNeeded(q);
574 KGestureMap::self()->installEventFilterOnMe( q );
575
576 q->connect(KToolInvocation::self(), SIGNAL(kapplication_hook(QStringList&,QByteArray&)),
577 q, SLOT(_k_slot_KToolInvocation_hook(QStringList&,QByteArray&)));
578 }
579
580#ifdef Q_WS_MAC
581 if (q->type() == KApplication::GuiClient) {
582 // This is a QSystemTrayIcon instead of K* because we can't be sure q is a QWidget
583 QSystemTrayIcon *trayIcon; //krazy:exclude=qclasses
584 if (QSystemTrayIcon::isSystemTrayAvailable()) //krazy:exclude=qclasses
585 {
586 trayIcon = new QSystemTrayIcon(q); //krazy:exclude=qclasses
587 trayIcon->setIcon(q->windowIcon());
588 /* it's counter-intuitive, but once you do setIcon it's already set the
589 dock icon... ->show actually shows an icon in the menu bar too :P */
590 // trayIcon->show();
591 }
592 }
593#endif
594
595 qRegisterMetaType<KUrl>();
596 qRegisterMetaType<KUrl::List>();
597
598#ifdef Q_WS_WIN
599 KApplication_init_windows();
600#endif
601}
602
603KApplication* KApplication::kApplication()
604{
605 return KApp;
606}
607
608KConfig* KApplication::sessionConfig()
609{
610 if (!d->pSessionConfig) // create an instance specific config object
611 d->pSessionConfig = new KConfig( d->sessionConfigName(), KConfig::SimpleConfig );
612 return d->pSessionConfig;
613}
614
615void KApplication::reparseConfiguration()
616{
617 KGlobal::config()->reparseConfiguration();
618}
619
620void KApplication::quit()
621{
622 QApplication::quit();
623}
624
625void KApplication::disableSessionManagement() {
626 d->bSessionManagement = false;
627}
628
629void KApplication::enableSessionManagement() {
630 d->bSessionManagement = true;
631#ifdef Q_WS_X11
632 // Session management support in Qt/KDE is awfully broken.
633 // If konqueror disables session management right after its startup,
634 // and enables it later (preloading stuff), it won't be properly
635 // saved on session shutdown.
636 // I'm not actually sure why it doesn't work, but saveState()
637 // doesn't seem to be called on session shutdown, possibly
638 // because disabling session management after konqueror startup
639 // disabled it somehow. Forcing saveState() here for this application
640 // seems to fix it.
641 if( mySmcConnection ) {
642 SmcRequestSaveYourself( mySmcConnection, SmSaveLocal, False,
643 SmInteractStyleAny,
644 False, False );
645
646 // flush the request
647 IceFlush(SmcGetIceConnection(mySmcConnection));
648 }
649#endif
650}
651
652void KApplication::commitData( QSessionManager& sm )
653{
654 d->session_save = true;
655 bool canceled = false;
656
657 foreach (KSessionManager *it, KSessionManager::sessionClients()) {
658 if ( ( canceled = !it->commitData( sm ) ) )
659 break;
660 }
661
662 if ( canceled )
663 sm.cancel();
664
665 if ( sm.allowsInteraction() ) {
666 QWidgetList donelist, todolist;
667 QWidget* w;
668
669commitDataRestart:
670 todolist = QApplication::topLevelWidgets();
671
672 for ( int i = 0; i < todolist.size(); ++i ) {
673 w = todolist.at( i );
674 if( !w )
675 break;
676
677 if ( donelist.contains( w ) )
678 continue;
679
680 if ( !w->isHidden() && !w->inherits( "KMainWindow" ) ) {
681 QCloseEvent e;
682 sendEvent( w, &e );
683 if ( !e.isAccepted() )
684 break; //canceled
685
686 donelist.append( w );
687
688 //grab the new list that was just modified by our closeevent
689 goto commitDataRestart;
690 }
691 }
692 }
693
694 if ( !d->bSessionManagement )
695 sm.setRestartHint( QSessionManager::RestartNever );
696 else
697 sm.setRestartHint( QSessionManager::RestartIfRunning );
698 d->session_save = false;
699}
700
701#ifdef Q_WS_X11
702static void checkRestartVersion( QSessionManager& sm )
703{
704 Display* dpy = QX11Info::display();
705 Atom type;
706 int format;
707 unsigned long nitems, after;
708 unsigned char* data;
709 if( dpy != NULL && XGetWindowProperty( dpy, RootWindow( dpy, 0 ), XInternAtom( dpy, "KDE_SESSION_VERSION", False ),
710 0, 1, False, AnyPropertyType, &type, &format, &nitems, &after, &data ) == Success ) {
711 if( type == XA_CARDINAL && format == 32 ) {
712 int version = *( long* ) data;
713 if( version == KDE_VERSION_MAJOR ) { // we run in our native session
714 XFree( data );
715 return; // no need to wrap
716 }
717 }
718 XFree( data );
719 }
720 if( getenv( "KDE_SESSION_VERSION" ) != NULL && atoi( getenv( "KDE_SESSION_VERSION" )) == KDE_VERSION_MAJOR )
721 return; // we run in our native session, no need to wrap
722#define NUM_TO_STRING2( num ) #num
723#define NUM_TO_STRING( num ) NUM_TO_STRING2( num )
724 QString wrapper = KStandardDirs::findExe( "kde" NUM_TO_STRING( KDE_VERSION_MAJOR ) ); // "kde4", etc.
725#undef NUM_TO_STRING
726#undef NUM_TO_STRING2
727 if( !wrapper.isEmpty()) {
728 QStringList restartCommand = sm.restartCommand();
729 restartCommand.prepend( wrapper );
730 sm.setRestartCommand( restartCommand );
731 }
732}
733#endif // Q_WS_X11
734
735void KApplication::saveState( QSessionManager& sm )
736{
737 d->session_save = true;
738#ifdef Q_WS_X11
739 static bool firstTime = true;
740 mySmcConnection = (SmcConn) sm.handle();
741
742 if ( !d->bSessionManagement ) {
743 sm.setRestartHint( QSessionManager::RestartNever );
744 d->session_save = false;
745 return;
746 }
747 else
748 sm.setRestartHint( QSessionManager::RestartIfRunning );
749
750 if ( firstTime ) {
751 firstTime = false;
752 d->session_save = false;
753 return; // no need to save the state.
754 }
755
756 // remove former session config if still existing, we want a new
757 // and fresh one. Note that we do not delete the config file here,
758 // this is done by the session manager when it executes the
759 // discard commands. In fact it would be harmful to remove the
760 // file here, as the session might be stored under a different
761 // name, meaning the user still might need it eventually.
762 delete d->pSessionConfig;
763 d->pSessionConfig = 0;
764
765 // tell the session manager about our new lifecycle
766 QStringList restartCommand = sm.restartCommand();
767
768 QByteArray multiHead = qgetenv("KDE_MULTIHEAD");
769 if (multiHead.toLower() == "true") {
770 // if multihead is enabled, we save our -display argument so that
771 // we are restored onto the correct head... one problem with this
772 // is that the display is hard coded, which means we cannot restore
773 // to a different display (ie. if we are in a university lab and try,
774 // try to restore a multihead session, our apps could be started on
775 // someone else's display instead of our own)
776 QByteArray displayname = qgetenv("DISPLAY");
777 if (! displayname.isNull()) {
778 // only store the command if we actually have a DISPLAY
779 // environment variable
780 restartCommand.append(QLatin1String("-display"));
781 restartCommand.append(QLatin1String(displayname));
782 }
783 sm.setRestartCommand( restartCommand );
784 }
785
786#ifdef Q_WS_X11
787 checkRestartVersion( sm );
788#endif
789
790 // finally: do session management
791 emit saveYourself(); // for compatibility
792 bool canceled = false;
793 foreach(KSessionManager* it, KSessionManager::sessionClients()) {
794 if(canceled) break;
795 canceled = !it->saveState( sm );
796 }
797
798 // if we created a new session config object, register a proper discard command
799 if ( d->pSessionConfig ) {
800 d->pSessionConfig->sync();
801 QStringList discard;
802 discard << QLatin1String("rm") << KStandardDirs::locateLocal("config", d->sessionConfigName());
803 sm.setDiscardCommand( discard );
804 } else {
805 sm.setDiscardCommand( QStringList( QLatin1String("") ) );
806 }
807
808 if ( canceled )
809 sm.cancel();
810#endif
811 d->session_save = false;
812}
813
814bool KApplication::sessionSaving() const
815{
816 return d->session_save;
817}
818
819void KApplicationPrivate::parseCommandLine( )
820{
821 KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kde");
822
823 if (args && args->isSet("style"))
824 {
825 extern QString kde_overrideStyle; // see KGlobalSettings. Should we have a static setter?
826 QString reqStyle(args->getOption("style").toLower());
827 if (QStyleFactory::keys().contains(reqStyle, Qt::CaseInsensitive))
828 kde_overrideStyle = reqStyle;
829 else
830 qWarning() << i18n("The style '%1' was not found", reqStyle);
831 }
832
833 if (args && args->isSet("config"))
834 {
835 QString config = args->getOption("config");
836 componentData.setConfigName(config);
837 }
838
839 if ( q->type() != KApplication::Tty ) {
840 if (args && args->isSet("icon"))
841 {
842 q->setWindowIcon(KIcon(args->getOption("icon")));
843 }
844 else {
845 q->setWindowIcon(KIcon(componentData.aboutData()->programIconName()));
846 }
847 }
848
849 if (!args)
850 return;
851
852 bool nocrashhandler = (!qgetenv("KDE_DEBUG").isEmpty());
853 if (!nocrashhandler && args->isSet("crashhandler"))
854 {
855 // enable drkonqi
856 KCrash::setDrKonqiEnabled(true);
857 }
858 // Always set the app name, can be usefuls for apps that call setEmergencySaveFunction or enable AutoRestart
859 KCrash::setApplicationName(args->appName());
860 if (!QCoreApplication::applicationDirPath().isEmpty()) {
861 KCrash::setApplicationPath(QCoreApplication::applicationDirPath());
862 }
863
864#ifdef Q_WS_X11
865 if ( args->isSet( "waitforwm" ) ) {
866 Atom type;
867 (void) q->desktop(); // trigger desktop creation, we need PropertyNotify events for the root window
868 int format;
869 unsigned long length, after;
870 unsigned char *data;
871 while ( XGetWindowProperty( QX11Info::display(), QX11Info::appRootWindow(), atom_NetSupported,
872 0, 1, false, AnyPropertyType, &type, &format,
873 &length, &after, &data ) != Success || !length ) {
874 if ( data )
875 XFree( data );
876 XEvent event;
877 XWindowEvent( QX11Info::display(), QX11Info::appRootWindow(), PropertyChangeMask, &event );
878 }
879 if ( data )
880 XFree( data );
881 }
882#endif
883
884#ifndef Q_WS_WIN
885 if (args->isSet("smkey"))
886 {
887 sessionKey = args->getOption("smkey");
888 }
889#endif
890}
891
892extern void kDebugCleanup();
893
894KApplication::~KApplication()
895{
896#ifdef Q_WS_X11
897 if ( d->oldXErrorHandler != NULL )
898 XSetErrorHandler( d->oldXErrorHandler );
899 if ( d->oldXIOErrorHandler != NULL )
900 XSetIOErrorHandler( d->oldXIOErrorHandler );
901 if ( d->oldIceIOErrorHandler != NULL )
902 IceSetIOErrorHandler( d->oldIceIOErrorHandler );
903#endif
904
905 delete d;
906 KApp = 0;
907
908#ifdef Q_WS_X11
909 mySmcConnection = 0;
910#endif
911}
912
913
914#ifdef Q_WS_X11
915class KAppX11HackWidget: public QWidget
916{
917public:
918 bool publicx11Event( XEvent * e) { return x11Event( e ); }
919};
920#endif
921
922
923
924#ifdef Q_WS_X11
925bool KApplication::x11EventFilter( XEvent *_event )
926{
927 if (x11Filter) {
928 foreach (const QWeakPointer< QWidget >& wp, *x11Filter) {
929 if( QWidget* w = wp.data())
930 if ( static_cast<KAppX11HackWidget*>( w )->publicx11Event(_event))
931 return true;
932 }
933 }
934
935 return false;
936}
937#endif // Q_WS_X11
938
939void KApplication::updateUserTimestamp( int time )
940{
941#if defined Q_WS_X11
942 if( time == 0 )
943 { // get current X timestamp
944 Window w = XCreateSimpleWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0, 0, 0 );
945 XSelectInput( QX11Info::display(), w, PropertyChangeMask );
946 unsigned char data[ 1 ];
947 XChangeProperty( QX11Info::display(), w, XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 );
948 XEvent ev;
949 XWindowEvent( QX11Info::display(), w, PropertyChangeMask, &ev );
950 time = ev.xproperty.time;
951 XDestroyWindow( QX11Info::display(), w );
952 }
953 if( QX11Info::appUserTime() == 0
954 || NET::timestampCompare( time, QX11Info::appUserTime()) > 0 ) // time > appUserTime
955 QX11Info::setAppUserTime(time);
956 if( QX11Info::appTime() == 0
957 || NET::timestampCompare( time, QX11Info::appTime()) > 0 ) // time > appTime
958 QX11Info::setAppTime(time);
959#endif
960}
961
962unsigned long KApplication::userTimestamp() const
963{
964#if defined Q_WS_X11
965 return QX11Info::appUserTime();
966#else
967 return 0;
968#endif
969}
970
971void KApplication::updateRemoteUserTimestamp( const QString& service, int time )
972{
973#if defined Q_WS_X11
974 Q_ASSERT(service.contains('.'));
975 if( time == 0 )
976 time = QX11Info::appUserTime();
977 QDBusInterface(service, QLatin1String("/MainApplication"),
978 QString(QLatin1String("org.kde.KApplication")))
979 .call(QLatin1String("updateUserTimestamp"), time);
980#endif
981}
982
983
984#ifndef KDE_NO_DEPRECATED
985QString KApplication::tempSaveName( const QString& pFilename )
986{
987 QString aFilename;
988
989 if( QDir::isRelativePath(pFilename) )
990 {
991 kWarning(240) << "Relative filename passed to KApplication::tempSaveName";
992 aFilename = QFileInfo( QDir( QLatin1String(".") ), pFilename ).absoluteFilePath();
993 }
994 else
995 aFilename = pFilename;
996
997 QDir aAutosaveDir( QDir::homePath() + QLatin1String("/autosave/") );
998 if( !aAutosaveDir.exists() )
999 {
1000 if( !aAutosaveDir.mkdir( aAutosaveDir.absolutePath() ) )
1001 {
1002 // Last chance: use temp dir
1003 aAutosaveDir.setPath( KGlobal::dirs()->saveLocation("tmp") );
1004 }
1005 }
1006
1007 aFilename.replace( '/', QLatin1String("\\!") )
1008 .prepend( QLatin1Char('#') )
1009 .append( QLatin1Char('#') )
1010 .prepend( QLatin1Char('/') ).prepend( aAutosaveDir.absolutePath() );
1011
1012 return aFilename;
1013}
1014#endif
1015
1016
1017QString KApplication::checkRecoverFile( const QString& pFilename,
1018 bool& bRecover )
1019{
1020 QString aFilename;
1021
1022 if( QDir::isRelativePath(pFilename) )
1023 {
1024 kWarning(240) << "Relative filename passed to KApplication::tempSaveName";
1025 aFilename = QFileInfo( QDir( QLatin1String(".") ), pFilename ).absoluteFilePath();
1026 }
1027 else
1028 aFilename = pFilename;
1029
1030 QDir aAutosaveDir( QDir::homePath() + QLatin1String("/autosave/") );
1031 if( !aAutosaveDir.exists() )
1032 {
1033 if( !aAutosaveDir.mkdir( aAutosaveDir.absolutePath() ) )
1034 {
1035 // Last chance: use temp dir
1036 aAutosaveDir.setPath( KGlobal::dirs()->saveLocation("tmp") );
1037 }
1038 }
1039
1040 aFilename.replace( QLatin1String("/"), QLatin1String("\\!") )
1041 .prepend( QLatin1Char('#') )
1042 .append( QLatin1Char('#') )
1043 .prepend( QLatin1Char('/') )
1044 .prepend( aAutosaveDir.absolutePath() );
1045
1046 if( QFile( aFilename ).exists() )
1047 {
1048 bRecover = true;
1049 return aFilename;
1050 }
1051 else
1052 {
1053 bRecover = false;
1054 return pFilename;
1055 }
1056}
1057
1058
1059void KApplication::setTopWidget( QWidget *topWidget )
1060{
1061 if( !topWidget )
1062 return;
1063
1064 // set the specified caption
1065 if ( !topWidget->inherits("KMainWindow") ) { // KMainWindow does this already for us
1066 topWidget->setWindowTitle(KGlobal::caption());
1067 }
1068
1069#ifdef Q_WS_X11
1070 // set the app startup notification window property
1071 KStartupInfo::setWindowStartupId(topWidget->winId(), startupId());
1072#endif
1073}
1074
1075QByteArray KApplication::startupId() const
1076{
1077 return d->startup_id;
1078}
1079
1080void KApplication::setStartupId( const QByteArray& startup_id )
1081{
1082 if( startup_id == d->startup_id )
1083 return;
1084#if defined Q_WS_X11
1085 KStartupInfo::handleAutoAppStartedSending(); // finish old startup notification if needed
1086#endif
1087 if( startup_id.isEmpty())
1088 d->startup_id = "0";
1089 else
1090 {
1091 d->startup_id = startup_id;
1092#if defined Q_WS_X11
1093 KStartupInfoId id;
1094 id.initId( startup_id );
1095 long timestamp = id.timestamp();
1096 if( timestamp != 0 )
1097 updateUserTimestamp( timestamp );
1098#endif
1099 }
1100}
1101
1102void KApplication::clearStartupId()
1103{
1104 d->startup_id = "0";
1105}
1106
1107// Qt reads and unsets the value and doesn't provide any way to reach the value,
1108// so steal it from it beforehand. If Qt gets API for taking (reading and unsetting)
1109// the startup id from it, this can be dumped.
1110void KApplicationPrivate::preread_app_startup_id()
1111{
1112#if defined Q_WS_X11
1113 KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
1114 KStartupInfo::resetStartupEnv();
1115 startup_id_tmp = new QByteArray( id.id());
1116#endif
1117}
1118
1119// read the startup notification env variable, save it and unset it in order
1120// not to propagate it to processes started from this app
1121void KApplicationPrivate::read_app_startup_id()
1122{
1123#if defined Q_WS_X11
1124 startup_id = *startup_id_tmp;
1125 delete startup_id_tmp;
1126 startup_id_tmp = NULL;
1127#endif
1128}
1129
1130// Hook called by KToolInvocation
1131void KApplicationPrivate::_k_slot_KToolInvocation_hook(QStringList& envs,QByteArray& startup_id)
1132{
1133#ifdef Q_WS_X11
1134 if (QX11Info::display()) {
1135 QByteArray dpystring(XDisplayString(QX11Info::display()));
1136 envs << QLatin1String("DISPLAY=") + dpystring;
1137 } else {
1138 const QByteArray dpystring( qgetenv( "DISPLAY" ));
1139 if(!dpystring.isEmpty())
1140 envs << QLatin1String("DISPLAY=") + dpystring;
1141 }
1142
1143 if(startup_id.isEmpty())
1144 startup_id = KStartupInfo::createNewStartupId();
1145#else
1146 Q_UNUSED(envs);
1147 Q_UNUSED(startup_id);
1148#endif
1149}
1150
1151void KApplication::setSynchronizeClipboard(bool synchronize)
1152{
1153 KClipboardSynchronizer::self()->setSynchronizing(synchronize);
1154 KClipboardSynchronizer::self()->setReverseSynchronizing(synchronize);
1155}
1156
1157#include "kapplication.moc"
1158
1159