Branch libreoffice-5-0-4
[LibreOffice.git] / vcl / unx / kde4 / KDEXLib.cxx
blob3101d3754072277a116bf3a7cfae528fb7f7ae06
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "VCLKDEApplication.hxx"
22 #include "KDESalInstance.hxx"
24 #include <kapplication.h>
25 #include <klocale.h>
26 #include <kaboutdata.h>
27 #include <kcmdlineargs.h>
28 #include <kstartupinfo.h>
29 #include <qabstracteventdispatcher.h>
30 #include <qclipboard.h>
31 #include <qthread.h>
33 #include "KDEXLib.hxx"
35 #include <unx/i18n_im.hxx>
36 #include <unx/i18n_xkb.hxx>
38 #include <unx/saldata.hxx>
39 #include <osl/process.h>
41 #include "KDESalDisplay.hxx"
43 #include <stdio.h>
45 #include <config_kde4.h>
47 #if KDE_HAVE_GLIB
48 #include "KDE4FilePicker.hxx"
49 #include "tst_exclude_socket_notifiers.moc"
50 #include "tst_exclude_posted_events.moc"
51 #endif
53 KDEXLib::KDEXLib() :
54 SalXLib(), m_bStartupDone(false), m_pApplication(0),
55 m_pFreeCmdLineArgs(0), m_pAppCmdLineArgs(0), m_nFakeCmdLineArgs( 0 ),
56 m_frameWidth( -1 ), m_isGlibEventLoopType(false),
57 m_allowKdeDialogs(false), blockIdleTimeout(false)
59 // the timers created here means they belong to the main thread.
60 // As the timeoutTimer runs the LO event queue, which may block on a dialog,
61 // the timer has to use a Qt::QueuedConnection, otherwise the nested event
62 // loop will detect the blocking timer and drop it from the polling
63 // freezing LO X11 processing.
64 connect( &timeoutTimer, SIGNAL( timeout()), this, SLOT( timeoutActivated()), Qt::QueuedConnection );
65 connect( &userEventTimer, SIGNAL( timeout()), this, SLOT( userEventActivated()), Qt::QueuedConnection );
67 // QTimer::start() can be called only in its (here main) thread, so this will
68 // forward between threads if needed
69 connect( this, SIGNAL( startTimeoutTimerSignal()), this, SLOT( startTimeoutTimer()), Qt::QueuedConnection );
70 connect( this, SIGNAL( startUserEventTimerSignal()), this, SLOT( startUserEventTimer()), Qt::QueuedConnection );
72 // this one needs to be blocking, so that the handling in main thread is processed before
73 // the thread emitting the signal continues
74 connect( this, SIGNAL( processYieldSignal( bool, bool )), this, SLOT( processYield( bool, bool )),
75 Qt::BlockingQueuedConnection );
77 // Create the File picker in the main / GUI thread and block the calling thread until
78 // the FilePicker is created.
79 connect( this, SIGNAL( createFilePickerSignal( const com::sun::star::uno::Reference<
80 com::sun::star::uno::XComponentContext >&) ),
81 this, SLOT( createFilePicker( const com::sun::star::uno::Reference<
82 com::sun::star::uno::XComponentContext >&) ),
83 Qt::BlockingQueuedConnection );
85 connect( this, SIGNAL( getFrameWidthSignal() ),
86 this, SLOT( getFrameWidth() ), Qt::BlockingQueuedConnection );
89 KDEXLib::~KDEXLib()
91 delete m_pApplication;
93 // free the faked cmdline arguments no longer needed by KApplication
94 for( int i = 0; i < m_nFakeCmdLineArgs; i++ )
96 free( m_pFreeCmdLineArgs[i] );
99 delete [] m_pFreeCmdLineArgs;
100 delete [] m_pAppCmdLineArgs;
103 void KDEXLib::Init()
105 SalI18N_InputMethod* pInputMethod = new SalI18N_InputMethod;
106 pInputMethod->SetLocale();
107 XrmInitialize();
109 KAboutData *kAboutData = new KAboutData( "LibreOffice",
110 "kdelibs4",
111 ki18n( "LibreOffice" ),
112 "3.6.0",
113 ki18n( "LibreOffice with KDE Native Widget Support." ),
114 KAboutData::License_File,
115 ki18n("Copyright (c) 2000, 2014 LibreOffice contributors" ),
116 ki18n( "LibreOffice is an office suite.\n" ),
117 "http://libreoffice.org",
118 "libreoffice@lists.freedesktop.org");
120 kAboutData->addAuthor( ki18n( "Jan Holesovsky" ),
121 ki18n( "Original author and maintainer of the KDE NWF." ),
122 "kendy@artax.karlin.mff.cuni.cz",
123 "http://artax.karlin.mff.cuni.cz/~kendy" );
124 kAboutData->addAuthor( ki18n("Roman Shtylman"),
125 ki18n( "Porting to KDE 4." ),
126 "shtylman@gmail.com", "http://shtylman.com" );
127 kAboutData->addAuthor( ki18n("Eric Bischoff"),
128 ki18n( "Accessibility fixes, porting to KDE 4." ),
129 "bischoff@kde.org" );
131 m_nFakeCmdLineArgs = 2;
132 sal_uInt16 nIdx;
134 int nParams = osl_getCommandArgCount();
135 OString aDisplay;
136 OUString aParam, aBin;
138 for ( nIdx = 0; nIdx < nParams; ++nIdx )
140 osl_getCommandArg( nIdx, &aParam.pData );
141 if ( !m_pFreeCmdLineArgs && aParam == "-display" && nIdx + 1 < nParams )
143 osl_getCommandArg( nIdx + 1, &aParam.pData );
144 aDisplay = OUStringToOString( aParam, osl_getThreadTextEncoding() );
146 m_pFreeCmdLineArgs = new char*[ m_nFakeCmdLineArgs + 2 ];
147 m_pFreeCmdLineArgs[ m_nFakeCmdLineArgs + 0 ] = strdup( "-display" );
148 m_pFreeCmdLineArgs[ m_nFakeCmdLineArgs + 1 ] = strdup( aDisplay.getStr() );
149 m_nFakeCmdLineArgs += 2;
152 if ( !m_pFreeCmdLineArgs )
153 m_pFreeCmdLineArgs = new char*[ m_nFakeCmdLineArgs ];
155 osl_getExecutableFile( &aParam.pData );
156 osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
157 OString aExec = OUStringToOString( aBin, osl_getThreadTextEncoding() );
158 m_pFreeCmdLineArgs[0] = strdup( aExec.getStr() );
159 m_pFreeCmdLineArgs[1] = strdup( "--nocrashhandler" );
161 // make a copy of the string list for freeing it since
162 // KApplication manipulates the pointers inside the argument vector
163 // note: KApplication bad !
164 m_pAppCmdLineArgs = new char*[ m_nFakeCmdLineArgs ];
165 for( int i = 0; i < m_nFakeCmdLineArgs; i++ )
166 m_pAppCmdLineArgs[i] = m_pFreeCmdLineArgs[i];
168 KCmdLineArgs::init( m_nFakeCmdLineArgs, m_pAppCmdLineArgs, kAboutData );
170 // LO does its own session management, so prevent KDE/Qt from interfering
171 // (QApplication::disableSessionManagement(false) wouldn't quite do,
172 // since that still actually connects to the session manager, it just
173 // won't save the application data on session shutdown).
174 char* session_manager = NULL;
175 if( getenv( "SESSION_MANAGER" ) != NULL )
177 session_manager = strdup( getenv( "SESSION_MANAGER" ));
178 unsetenv( "SESSION_MANAGER" );
180 m_pApplication = new VCLKDEApplication();
181 if( session_manager != NULL )
183 // coverity[tainted_string] - trusted source for setenv
184 setenv( "SESSION_MANAGER", session_manager, 1 );
185 free( session_manager );
188 KApplication::setQuitOnLastWindowClosed(false);
190 #if KDE_HAVE_GLIB
191 m_isGlibEventLoopType = QAbstractEventDispatcher::instance()->inherits( "QEventDispatcherGlib" );
192 // Using KDE dialogs (and their nested event loops) works only with a proper event loop integration
193 // that will release SolarMutex when waiting for more events.
194 // Moreover there are bugs in Qt event loop code that allow QClipboard recursing because the event
195 // loop processes also events that it should not at that point, so no dialogs in that case either.
196 // https://bugreports.qt-project.org/browse/QTBUG-37380
197 // https://bugreports.qt-project.org/browse/QTBUG-34614
198 if (m_isGlibEventLoopType && (0 == tst_processEventsExcludeSocket()) && tst_excludePostedEvents() == 0 )
199 m_allowKdeDialogs = true;
200 #endif
202 setupEventLoop();
204 Display* pDisp = QX11Info::display();
205 SalKDEDisplay *pSalDisplay = new SalKDEDisplay(pDisp);
207 pInputMethod->CreateMethod( pDisp );
208 pSalDisplay->SetupInput( pInputMethod );
211 // When we use Qt event loop, it can actually use its own event loop handling, or wrap
212 // the Glib event loop (the latter is the default is Qt is built with Glib support
213 // and $QT_NO_GLIB is not set). We mostly do not care which one it is, as QSocketNotifier's
214 // and QTimer's can handle it transparently, but it matters for the SolarMutex, which
215 // needs to be unlocked shortly before entering the main sleep (e.g. select()) and locked
216 // immediatelly after. So we need to know which event loop implementation is used and
217 // hook accordingly.
218 #if KDE_HAVE_GLIB
219 #include <glib.h>
221 static GPollFunc old_gpoll = NULL;
223 static gint gpoll_wrapper( GPollFD* ufds, guint nfds, gint timeout )
225 SalYieldMutexReleaser release; // release YieldMutex (and re-acquire at block end)
226 return old_gpoll( ufds, nfds, timeout );
228 #endif
230 static bool ( *old_qt_event_filter )( void* );
231 static bool qt_event_filter( void* m )
233 if( old_qt_event_filter != NULL && old_qt_event_filter( m ))
234 return true;
235 if( SalKDEDisplay::self() && SalKDEDisplay::self()->checkDirectInputEvent( static_cast< XEvent* >( m )))
236 return true;
237 return false;
240 void KDEXLib::setupEventLoop()
242 old_qt_event_filter = QAbstractEventDispatcher::instance()->setEventFilter( qt_event_filter );
243 #if KDE_HAVE_GLIB
244 if( m_isGlibEventLoopType )
246 old_gpoll = g_main_context_get_poll_func( NULL );
247 g_main_context_set_poll_func( NULL, gpoll_wrapper );
248 if( m_allowKdeDialogs )
249 QApplication::clipboard()->setProperty( "useEventLoopWhenWaiting", true );
250 return;
252 #endif
255 void KDEXLib::Insert( int fd, void* data, YieldFunc pending, YieldFunc queued, YieldFunc handle )
257 if( !m_isGlibEventLoopType )
258 return SalXLib::Insert( fd, data, pending, queued, handle );
259 SocketData sdata;
260 sdata.data = data;
261 sdata.pending = pending;
262 sdata.queued = queued;
263 sdata.handle = handle;
264 // qApp as parent to make sure it uses the main thread event loop
265 sdata.notifier = new QSocketNotifier( fd, QSocketNotifier::Read, qApp );
266 connect( sdata.notifier, SIGNAL( activated( int )), this, SLOT( socketNotifierActivated( int )));
267 socketData[ fd ] = sdata;
270 void KDEXLib::Remove( int fd )
272 if( !m_isGlibEventLoopType )
273 return SalXLib::Remove( fd );
274 SocketData sdata = socketData.take( fd );// according to SalXLib::Remove() this should be safe
275 delete sdata.notifier;
278 void KDEXLib::socketNotifierActivated( int fd )
280 const SocketData& sdata = socketData[ fd ];
281 sdata.handle( fd, sdata.data );
284 void KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
286 if( !m_isGlibEventLoopType )
288 if( qApp->thread() == QThread::currentThread())
290 // even if we use the LO event loop, still process Qt's events,
291 // otherwise they can remain unhandled for quite a long while
292 processYield( false, bHandleAllCurrentEvents );
294 return SalXLib::Yield( bWait, bHandleAllCurrentEvents );
296 // if we are the main thread (which is where the event processing is done),
297 // good, just do it
298 if( qApp->thread() == QThread::currentThread())
299 processYield( bWait, bHandleAllCurrentEvents );
300 else
302 // we were called from another thread;
303 // release the yield lock to prevent deadlock with the main thread
304 // (it's ok to release it here, since even normal processYield() would
305 // temporarily do it while checking for new events)
306 SalYieldMutexReleaser aReleaser;
307 Q_EMIT processYieldSignal( bWait, bHandleAllCurrentEvents );
311 void KDEXLib::processYield( bool bWait, bool bHandleAllCurrentEvents )
313 blockIdleTimeout = !bWait;
314 QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance( qApp->thread());
315 bool wasEvent = false;
316 for( int cnt = bHandleAllCurrentEvents ? 100 : 1;
317 cnt > 0;
318 --cnt )
320 if( !dispatcher->processEvents( QEventLoop::AllEvents ))
321 break;
322 wasEvent = true;
324 if( bWait && !wasEvent )
325 dispatcher->processEvents( QEventLoop::WaitForMoreEvents );
326 blockIdleTimeout = false;
329 void KDEXLib::StartTimer( sal_uLong nMS )
331 if( !m_isGlibEventLoopType )
332 return SalXLib::StartTimer( nMS );
333 timeoutTimer.setInterval( nMS );
334 // QTimer's can be started only in their thread (main thread here)
335 if( qApp->thread() == QThread::currentThread())
336 startTimeoutTimer();
337 else
338 Q_EMIT startTimeoutTimerSignal();
341 void KDEXLib::startTimeoutTimer()
343 timeoutTimer.start();
346 void KDEXLib::StopTimer()
348 if( !m_isGlibEventLoopType )
349 return SalXLib::StopTimer();
350 timeoutTimer.stop();
353 void KDEXLib::timeoutActivated()
355 // HACK? Always process posted events before timer timeouts.
356 // There are places that may watch both both (for example, there's a posted
357 // event about change of the current active window and there's a timeout
358 // event informing that a document has finished loading). This is of course
359 // racy, but both generic and gtk event loops manage to deliver posted events
360 // first, so it's at least consistent, and it probably kind of makes at least
361 // some sense (timeouts should be more ok to wait and be triggered somewhen).
362 while( SalKDEDisplay::self()->HasUserEvents() )
363 SalKDEDisplay::self()->DispatchInternalEvent();
365 // QGuiEventDispatcherGlib makes glib watch also X11 fd, but its hasPendingEvents()
366 // doesn't check X11, so explicitly check XPending() here.
367 bool idle = QApplication::hasPendingEvents() && !blockIdleTimeout && !XPending( QX11Info::display());
368 X11SalData::Timeout( idle );
369 // QTimer is not single shot, so will be restarted immediatelly
372 void KDEXLib::Wakeup()
374 if( !m_isGlibEventLoopType )
375 return SalXLib::Wakeup();
376 QAbstractEventDispatcher::instance( qApp->thread())->wakeUp(); // main thread event loop
379 void KDEXLib::PostUserEvent()
381 if( !m_isGlibEventLoopType )
382 return SalXLib::PostUserEvent();
383 if( qApp->thread() == QThread::currentThread())
384 startUserEventTimer();
385 else
386 Q_EMIT startUserEventTimerSignal();
389 void KDEXLib::startUserEventTimer()
391 userEventTimer.start( 0 );
394 void KDEXLib::userEventActivated()
396 if( ! SalKDEDisplay::self()->HasUserEvents() )
397 userEventTimer.stop();
398 SalKDEDisplay::self()->DispatchInternalEvent();
399 // QTimer is not single shot, so will be restarted immediatelly
402 void KDEXLib::doStartup()
404 if( ! m_bStartupDone )
406 KStartupInfo::appStarted();
407 m_bStartupDone = true;
408 SAL_INFO( "vcl.kde4", "called KStartupInfo::appStarted()" );
412 using namespace com::sun::star;
414 uno::Reference< ui::dialogs::XFilePicker2 > KDEXLib::createFilePicker(
415 const uno::Reference< uno::XComponentContext >& xMSF )
417 #if KDE_HAVE_GLIB
418 if( qApp->thread() != QThread::currentThread()) {
419 SalYieldMutexReleaser aReleaser;
420 return Q_EMIT createFilePickerSignal( xMSF );
422 return uno::Reference< ui::dialogs::XFilePicker2 >( new KDE4FilePicker( xMSF ) );
423 #else
424 return NULL;
425 #endif
428 #include <qframe.h>
430 int KDEXLib::getFrameWidth()
432 if( m_frameWidth >= 0 )
433 return m_frameWidth;
434 if( qApp->thread() != QThread::currentThread()) {
435 SalYieldMutexReleaser aReleaser;
436 return Q_EMIT getFrameWidthSignal();
439 // fill in a default
440 QFrame aFrame( NULL );
441 aFrame.setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
442 aFrame.ensurePolished();
443 m_frameWidth = aFrame.frameWidth();
444 return m_frameWidth;
447 #include "KDEXLib.moc"
449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */