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 | |
110 | KApplication* KApplication::KApp = 0L; |
111 | bool KApplication::loadedByKdeinit = false; |
112 | |
113 | #ifdef Q_WS_X11 |
114 | static Atom atom_DesktopWindow; |
115 | static Atom atom_NetSupported; |
116 | static Atom kde_xdnd_drop; |
117 | static QByteArray* startup_id_tmp; |
118 | #endif |
119 | |
120 | template class QList<KSessionManager*>; |
121 | |
122 | #ifdef Q_WS_X11 |
123 | extern "C" { |
124 | static int kde_xio_errhandler( Display * dpy ) |
125 | { |
126 | return kapp->xioErrhandler( dpy ); |
127 | } |
128 | |
129 | static 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 |
138 | void KApplication_init_windows(); |
139 | #endif |
140 | |
141 | /* |
142 | Private data to make keeping binary compatibility easier |
143 | */ |
144 | class KApplicationPrivate |
145 | { |
146 | public: |
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 | |
234 | static 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 | */ |
243 | static 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 | |
254 | void 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 | |
264 | void KApplicationPrivate::_k_x11FilterDestroyed() |
265 | { |
266 | q->removeX11EventFilter( static_cast< const QWidget* >(q->sender())); |
267 | } |
268 | |
269 | void 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 | |
288 | bool 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 | |
314 | void 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 | */ |
326 | QString 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 |
338 | static SmcConn mySmcConnection = 0; |
339 | #else |
340 | // FIXME(E): Implement for Qt Embedded |
341 | // Possibly "steal" XFree86's libSM? |
342 | #endif |
343 | |
344 | KApplication::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 |
356 | KApplication::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 | |
367 | KApplication::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 | |
379 | KApplication::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 |
391 | KApplication::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 |
407 | void KApplicationPrivate::preqapplicationhack() |
408 | { |
409 | preread_app_startup_id(); |
410 | |
411 | KGlobal::config(); // initialize qt plugin path (see KComponentDataPrivate::lazyInit) |
412 | } |
413 | |
414 | int 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 | |
428 | int 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 | |
445 | void 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 | |
456 | void 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 | |
603 | KApplication* KApplication::kApplication() |
604 | { |
605 | return KApp; |
606 | } |
607 | |
608 | KConfig* 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 | |
615 | void KApplication::reparseConfiguration() |
616 | { |
617 | KGlobal::config()->reparseConfiguration(); |
618 | } |
619 | |
620 | void KApplication::quit() |
621 | { |
622 | QApplication::quit(); |
623 | } |
624 | |
625 | void KApplication::disableSessionManagement() { |
626 | d->bSessionManagement = false; |
627 | } |
628 | |
629 | void 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 | |
652 | void 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 | |
669 | commitDataRestart: |
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 |
702 | static 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 | |
735 | void 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 | |
814 | bool KApplication::sessionSaving() const |
815 | { |
816 | return d->session_save; |
817 | } |
818 | |
819 | void 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 | |
892 | extern void kDebugCleanup(); |
893 | |
894 | KApplication::~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 |
915 | class KAppX11HackWidget: public QWidget |
916 | { |
917 | public: |
918 | bool publicx11Event( XEvent * e) { return x11Event( e ); } |
919 | }; |
920 | #endif |
921 | |
922 | |
923 | |
924 | #ifdef Q_WS_X11 |
925 | bool 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 | |
939 | void 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 | |
962 | unsigned long KApplication::userTimestamp() const |
963 | { |
964 | #if defined Q_WS_X11 |
965 | return QX11Info::appUserTime(); |
966 | #else |
967 | return 0; |
968 | #endif |
969 | } |
970 | |
971 | void 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 |
985 | QString 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 | |
1017 | QString 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 | |
1059 | void 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 | |
1075 | QByteArray KApplication::startupId() const |
1076 | { |
1077 | return d->startup_id; |
1078 | } |
1079 | |
1080 | void 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 | |
1102 | void 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. |
1110 | void 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 |
1121 | void 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 |
1131 | void 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 | |
1151 | void KApplication::setSynchronizeClipboard(bool synchronize) |
1152 | { |
1153 | KClipboardSynchronizer::self()->setSynchronizing(synchronize); |
1154 | KClipboardSynchronizer::self()->setReverseSynchronizing(synchronize); |
1155 | } |
1156 | |
1157 | #include "kapplication.moc" |
1158 | |
1159 | |