summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuboš Luňák <l.lunak@collabora.com>2014-03-28 15:09:13 +0100
committerCaolán McNamara <caolanm@redhat.com>2014-04-30 08:29:14 +0000
commit80f113efa6f60c6b3aad33128921792451f114ce (patch)
tree0d4077ee44d89c0c1b2baae8ebf6ff8565fd357d
parent68dfe73d209091455c79e1f746ca84cf8743629d (diff)
fix KFileDialog crashes (fdo#69002)
Requires Qt patches (or otherwise LO generic file dialog is used). Squashed from several master patches. (cherry picked from commit 2cd8a1e0f1e81efd15979953d7f274ab8a6806d6) (cherry picked from commit 474ad6b0e2fb18370be9d228456a2abbfc15bad2) (cherry picked from commit e809aa1e916e0f6d1a849d0374f59ef9619b1db7) (cherry picked from commit 65a3622148ea67744c9c1fc18c2b8d48e5f1c79f) (cherry picked from commit 508337db0c53caa5fb43ef26f781df159497a482) (cherry picked from commit 9aa32a34c7c7a2ee4da5f01983a0ed224c38c875) (cherry picked from commit 145f2e970f46a3a3e5456b122d71f17c3abe878f) (cherry picked from commit f09d4bc2853be2fa3faa0502b8efe94ad9719731) Change-Id: I6dba5da0170fb94bab928f71efc7dc8c03cc70d9 Reviewed-on: https://gerrit.libreoffice.org/9206 Reviewed-by: Caolán McNamara <caolanm@redhat.com> Tested-by: Caolán McNamara <caolanm@redhat.com>
-rw-r--r--configure.ac45
-rw-r--r--vcl/CustomTarget_kde4_moc.mk3
-rw-r--r--vcl/unx/kde4/KDE4FilePicker.cxx21
-rw-r--r--vcl/unx/kde4/KDESalInstance.cxx2
-rw-r--r--vcl/unx/kde4/KDEXLib.cxx21
-rw-r--r--vcl/unx/kde4/KDEXLib.hxx4
-rw-r--r--vcl/unx/kde4/VCLKDEApplication.cxx44
-rw-r--r--vcl/unx/kde4/VCLKDEApplication.hxx2
-rw-r--r--vcl/unx/kde4/tst_exclude_posted_events.hxx72
-rw-r--r--vcl/unx/kde4/tst_exclude_socket_notifiers.hxx130
10 files changed, 231 insertions, 113 deletions
diff --git a/configure.ac b/configure.ac
index 5751f85937fc..98e2a0e84408 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11240,6 +11240,8 @@ int main(int argc, char **argv) {
AC_DEFINE(KDE_HAVE_GLIB,1)
KDE_GLIB_CFLAGS=$(printf '%s' "$KDE_GLIB_CFLAGS" | sed -e "s/-I/${ISYSTEM?}/g")
+ qt4_fix_warning=
+
AC_LANG_PUSH([C++])
save_CXXFLAGS=$CXXFLAGS
CXXFLAGS="$CXXFLAGS $KDE4_CFLAGS"
@@ -11266,12 +11268,53 @@ int main(int argc, char *argv[])
AC_MSG_RESULT([yes])
],[
AC_MSG_RESULT([no])
- AC_MSG_WARN([native KDE4 file pickers will be disabled at runtime - fix your Qt4 library!])
+ AC_MSG_WARN([native KDE4 file pickers will be disabled at runtime])
+ if test -z "$qt4_fix_warning"; then
+ add_warning "native KDE4 file pickers will be disabled at runtime, Qt4 fixes needed"
+ fi
+ qt4_fix_warning=1
+ add_warning " https://bugreports.qt-project.org/browse/QTBUG-37380 (needed)"
])
# Remove meta object data
rm -f "${TSTBASE}."*
+ AC_MSG_CHECKING([whether Qt avoids QClipboard recursion caused by posted events])
+
+ # Prepare meta object data
+ TSTBASE="tst_exclude_posted_events"
+ TSTMOC="${SRC_ROOT}/vcl/unx/kde4/${TSTBASE}"
+ ln -fs "${TSTMOC}.hxx"
+ $MOC4 "${TSTBASE}.hxx" -o "${TSTBASE}.moc"
+
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
+#include "tst_exclude_posted_events.moc"
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ exit(tst_excludePostedEvents());
+ return 0;
+}
+ ]])],[
+ AC_MSG_RESULT([yes])
+ ],[
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([native KDE4 file pickers will be disabled at runtime])
+ if test -z "$qt4_fix_warning"; then
+ add_warning "native KDE4 file pickers will be disabled at runtime, Qt4 fixes needed"
+ fi
+ qt4_fix_warning=1
+ add_warning " https://bugreports.qt-project.org/browse/QTBUG-34614 (needed)"
+ ])
+
+ # Remove meta object data
+ rm -f "${TSTBASE}."*
+
+ if test -n "$qt4_fix_warning"; then
+ add_warning " https://bugreports.qt-project.org/browse/QTBUG-38585 (recommended)"
+ fi
+
LIBS=$save_LIBS
CXXFLAGS=$save_CXXFLAGS
AC_LANG_POP([C++])
diff --git a/vcl/CustomTarget_kde4_moc.mk b/vcl/CustomTarget_kde4_moc.mk
index 9e417548da4b..16d1561944c6 100644
--- a/vcl/CustomTarget_kde4_moc.mk
+++ b/vcl/CustomTarget_kde4_moc.mk
@@ -12,7 +12,8 @@ $(eval $(call gb_CustomTarget_CustomTarget,vcl/unx/kde4))
$(call gb_CustomTarget_get_target,vcl/unx/kde4) : \
$(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/KDEXLib.moc \
$(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/KDE4FilePicker.moc \
- $(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/tst_exclude_socket_notifiers.moc
+ $(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/tst_exclude_socket_notifiers.moc \
+ $(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/tst_exclude_posted_events.moc
$(call gb_CustomTarget_get_workdir,vcl/unx/kde4)/%.moc : \
$(SRCDIR)/vcl/unx/kde4/%.hxx \
diff --git a/vcl/unx/kde4/KDE4FilePicker.cxx b/vcl/unx/kde4/KDE4FilePicker.cxx
index 1cb146002236..2d6bfbacce75 100644
--- a/vcl/unx/kde4/KDE4FilePicker.cxx
+++ b/vcl/unx/kde4/KDE4FilePicker.cxx
@@ -38,6 +38,7 @@
#include "KDE4FilePicker.hxx"
#include "FPServiceInfo.hxx"
+#include "VCLKDEApplication.hxx"
/* ********* Hack, but needed because of conflicting types... */
#define Region QtXRegion
@@ -260,21 +261,11 @@ sal_Int16 SAL_CALL KDE4FilePicker::execute()
_dialog->setFilter(_filter);
_dialog->filterWidget()->setEditable(false);
- // We're entering a nested loop.
- int result;
- {
- // Release the yield mutex to prevent deadlocks.
- SalYieldMutexReleaser aReleaser;
- result = _dialog->exec();
- }
-
- // HACK: KFileDialog uses KConfig("kdeglobals") for saving some settings
- // (such as the auto-extension flag), but that doesn't update KGlobal::config()
- // (which is probably a KDE bug), so force reading the new configuration,
- // otherwise the next opening of the dialog would use the old settings.
- KGlobal::config()->reparseConfiguration();
-
- if( result == KFileDialog::Accepted)
+ VCLKDEApplication::preDialogSetup();
+ //block and wait for user input
+ int result = _dialog->exec();
+ VCLKDEApplication::postDialogCleanup();
+ if( result == KFileDialog::Accepted )
return ExecutableDialogResults::OK;
return ExecutableDialogResults::CANCEL;
diff --git a/vcl/unx/kde4/KDESalInstance.cxx b/vcl/unx/kde4/KDESalInstance.cxx
index 023d79051741..094cd2055cd5 100644
--- a/vcl/unx/kde4/KDESalInstance.cxx
+++ b/vcl/unx/kde4/KDESalInstance.cxx
@@ -35,7 +35,7 @@ uno::Reference< ui::dialogs::XFilePicker2 > KDESalInstance::createFilePicker(
const uno::Reference< uno::XComponentContext >& xMSF )
{
KDEXLib* kdeXLib = static_cast<KDEXLib*>( mpXLib );
- if (kdeXLib->haveQt4SocketExcludeFix())
+ if (kdeXLib->allowKdeDialogs())
return uno::Reference< ui::dialogs::XFilePicker2 >(
kdeXLib->createFilePicker(xMSF) );
else
diff --git a/vcl/unx/kde4/KDEXLib.cxx b/vcl/unx/kde4/KDEXLib.cxx
index e4900a719408..2630c0c51781 100644
--- a/vcl/unx/kde4/KDEXLib.cxx
+++ b/vcl/unx/kde4/KDEXLib.cxx
@@ -47,13 +47,14 @@
#if KDE_HAVE_GLIB
#include "KDE4FilePicker.hxx"
#include "tst_exclude_socket_notifiers.moc"
+#include "tst_exclude_posted_events.moc"
#endif
KDEXLib::KDEXLib() :
SalXLib(), m_bStartupDone(false), m_pApplication(0),
m_pFreeCmdLineArgs(0), m_pAppCmdLineArgs(0), m_nFakeCmdLineArgs( 0 ),
m_frameWidth( -1 ), m_isGlibEventLoopType(false),
- m_haveQt4SocketExcludeFix(false)
+ m_allowKdeDialogs(false)
{
// the timers created here means they belong to the main thread.
// As the timeoutTimer runs the LO event queue, which may block on a dialog,
@@ -187,9 +188,14 @@ void KDEXLib::Init()
#if KDE_HAVE_GLIB
m_isGlibEventLoopType = QAbstractEventDispatcher::instance()->inherits( "QEventDispatcherGlib" );
- if (m_isGlibEventLoopType && (0 == tst_processEventsExcludeSocket()))
- // See http://bugreports.qt.nokia.com/browse/QTBUG-37380
- m_haveQt4SocketExcludeFix = true;
+ // Using KDE dialogs (and their nested event loops) works only with a proper event loop integration
+ // that will release SolarMutex when waiting for more events.
+ // Moreover there are bugs in Qt event loop code that allow QClipboard recursing because the event
+ // loop processes also events that it should not at that point, so no dialogs in that case either.
+ // https://bugreports.qt-project.org/browse/QTBUG-37380
+ // https://bugreports.qt-project.org/browse/QTBUG-34614
+ if (m_isGlibEventLoopType && (0 == tst_processEventsExcludeSocket()) && tst_excludePostedEvents() == 0 )
+ m_allowKdeDialogs = true;
#endif
setupEventLoop();
@@ -238,7 +244,7 @@ void KDEXLib::setupEventLoop()
{
old_gpoll = g_main_context_get_poll_func( NULL );
g_main_context_set_poll_func( NULL, gpoll_wrapper );
- if( m_haveQt4SocketExcludeFix )
+ if( m_allowKdeDialogs )
m_pApplication->clipboard()->setProperty( "useEventLoopWhenWaiting", true );
return;
}
@@ -286,7 +292,6 @@ void KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
}
return SalXLib::Yield( bWait, bHandleAllCurrentEvents );
}
-
// if we are the main thread (which is where the event processing is done),
// good, just do it
if( qApp->thread() == QThread::currentThread())
@@ -294,7 +299,9 @@ void KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
else
{
// we were called from another thread;
- // release the yield lock to prevent deadlock.
+ // release the yield lock to prevent deadlock with the main thread
+ // (it's ok to release it here, since even normal processYield() would
+ // temporarily do it while checking for new events)
SalYieldMutexReleaser aReleaser;
Q_EMIT processYieldSignal( bWait, bHandleAllCurrentEvents );
}
diff --git a/vcl/unx/kde4/KDEXLib.hxx b/vcl/unx/kde4/KDEXLib.hxx
index a88258c5985e..53e01c037364 100644
--- a/vcl/unx/kde4/KDEXLib.hxx
+++ b/vcl/unx/kde4/KDEXLib.hxx
@@ -53,7 +53,7 @@ class KDEXLib : public QObject, public SalXLib
QTimer userEventTimer;
int m_frameWidth;
bool m_isGlibEventLoopType;
- bool m_haveQt4SocketExcludeFix;
+ bool m_allowKdeDialogs;
private:
void setupEventLoop();
@@ -88,7 +88,7 @@ class KDEXLib : public QObject, public SalXLib
virtual void PostUserEvent();
void doStartup();
- bool haveQt4SocketExcludeFix() { return m_haveQt4SocketExcludeFix; }
+ bool allowKdeDialogs() { return m_allowKdeDialogs; }
public Q_SLOTS:
com::sun::star::uno::Reference< com::sun::star::ui::dialogs::XFilePicker2 >
diff --git a/vcl/unx/kde4/VCLKDEApplication.cxx b/vcl/unx/kde4/VCLKDEApplication.cxx
index e196ff3d903a..65f699947cfc 100644
--- a/vcl/unx/kde4/VCLKDEApplication.cxx
+++ b/vcl/unx/kde4/VCLKDEApplication.cxx
@@ -19,6 +19,7 @@
#include "VCLKDEApplication.hxx"
+#include <QClipboard>
#include <QEvent>
#include "KDESalDisplay.hxx"
@@ -40,4 +41,47 @@ bool VCLKDEApplication::x11EventFilter(XEvent* ev)
return false;
}
+// various hacks to be performed before re-entering Qt's event loop
+// because of showing a Qt dialog
+void VCLKDEApplication::preDialogSetup()
+{
+ // KFileDialog intergration requires using event loop with QClipboard.
+ // Opening the KDE file dialog here can lead to QClipboard
+ // asking for clipboard contents. If LO core is the owner of the clipboard
+ // content, without event loop use this will block for 5 seconds and timeout,
+ // since the clipboard thread will not be able to acquire SolarMutex
+ // and thus won't be able to respond. If the event loops
+ // are properly integrated and QClipboard can use a nested event loop
+ // (see the KDE VCL plug), then this won't happen.
+ // We cannot simply release SolarMutex here, because the event loop started
+ // by the file dialog would also call back to LO code.
+ assert( qApp->clipboard()->property( "useEventLoopWhenWaiting" ).toBool() == true );
+}
+
+// various hacks to be performed after a Qt dialog has been closed
+void VCLKDEApplication::postDialogCleanup()
+{
+ // HACK: KFileDialog uses KConfig("kdeglobals") for saving some settings
+ // (such as the auto-extension flag), but that doesn't update KGlobal::config()
+ // (which is probably a KDE bug), so force reading the new configuration,
+ // otherwise the next opening of the dialog would use the old settings.
+ KGlobal::config()->reparseConfiguration();
+ // HACK: If Qt owns clipboard or selection, give up on their ownership now. Otherwise
+ // LO core might ask for the contents, but it would block while doing so (i.e. it
+ // doesn't seem to have an equivalent of QClipboard's "useEventLoopWhenWaiting"),
+ // therefore QClipboard wouldn't be able to respond, and whole LO would block until
+ // a timeout. Given that Klipper is most probably running, giving up clipboard/selection
+ // ownership will not only avoid the blocking, but even pasting that content in LO
+ // will in fact work, if Klipper can handle it.
+ // Technically proper solution would be of course to allow Qt to process QClipboard
+ // events while LO waits for clipboard contents, or short-circuit to QClipboard somehow
+ // (it's a mystery why LO's clipboard handling has its own thread when whole LO can
+ // get blocked by both trying to send and receive clipboard contents anyway).
+ QClipboard* clipboard = QApplication::clipboard();
+ if( clipboard->ownsSelection())
+ clipboard->clear( QClipboard::Selection );
+ if( clipboard->ownsClipboard())
+ clipboard->clear( QClipboard::Clipboard );
+}
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kde4/VCLKDEApplication.hxx b/vcl/unx/kde4/VCLKDEApplication.hxx
index 4ce0b2ca4f67..d31f4e8ac31d 100644
--- a/vcl/unx/kde4/VCLKDEApplication.hxx
+++ b/vcl/unx/kde4/VCLKDEApplication.hxx
@@ -30,6 +30,8 @@ class VCLKDEApplication : public KApplication
public:
VCLKDEApplication();
virtual bool x11EventFilter(XEvent* event);
+ static void preDialogSetup();
+ static void postDialogCleanup();
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/kde4/tst_exclude_posted_events.hxx b/vcl/unx/kde4/tst_exclude_posted_events.hxx
new file mode 100644
index 000000000000..712750545462
--- /dev/null
+++ b/vcl/unx/kde4/tst_exclude_posted_events.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ *
+ * This code is based on the SocketEventsTester from the Qt4 test suite.
+ */
+
+#pragma once
+
+#include <qcoreapplication.h>
+#include <qeventloop.h>
+
+namespace
+{
+
+const QEvent::Type eventType = QEvent::User;
+
+class TestExcludePostedEvents
+ : public QObject
+{
+ Q_OBJECT
+ public:
+ TestExcludePostedEvents();
+ virtual bool event( QEvent* e );
+ bool processed;
+};
+
+TestExcludePostedEvents::TestExcludePostedEvents()
+ : processed( false )
+{
+}
+
+bool TestExcludePostedEvents::event( QEvent* e )
+{
+ if( e->type() == eventType )
+ processed = true;
+ return QObject::event( e );
+}
+
+}
+
+#define QVERIFY(a) \
+ if (!a) return 1;
+
+static int tst_excludePostedEvents()
+{
+ TestExcludePostedEvents test;
+ QCoreApplication::postEvent( &test, new QEvent( eventType ));
+ QEventLoop loop;
+ loop.processEvents(QEventLoop::ExcludeUserInputEvents
+ | QEventLoop::ExcludeSocketNotifiers
+// | QEventLoop::WaitForMoreEvents
+ | QEventLoop::X11ExcludeTimers);
+ QVERIFY( !test.processed );
+ loop.processEvents();
+ QVERIFY( test.processed );
+ return 0;
+}
diff --git a/vcl/unx/kde4/tst_exclude_socket_notifiers.hxx b/vcl/unx/kde4/tst_exclude_socket_notifiers.hxx
index 0c874fd7147d..acf4d361d177 100644
--- a/vcl/unx/kde4/tst_exclude_socket_notifiers.hxx
+++ b/vcl/unx/kde4/tst_exclude_socket_notifiers.hxx
@@ -23,104 +23,62 @@
#include <qcoreapplication.h>
#include <qeventloop.h>
-#include <qthread.h>
-#include <qtimer.h>
-#include <QtNetwork/qtcpserver.h>
-#include <QtNetwork/qtcpsocket.h>
+#include <qsocketnotifier.h>
+#include <unistd.h>
-class SocketEventsTester: public QObject
+namespace
+{
+
+class TestExcludeSocketNotifiers
+ : public QObject
{
Q_OBJECT
-public:
- SocketEventsTester()
- {
- socket = 0;
- server = 0;
- dataSent = false;
- testResult = false;
- dataArrived = false;
- }
- ~SocketEventsTester()
- {
- delete socket;
- delete server;
- }
- bool init()
- {
- bool ret = false;
- server = new QTcpServer();
- socket = new QTcpSocket();
- connect(server, SIGNAL(newConnection()), this, SLOT(sendHello()));
- connect(socket, SIGNAL(readyRead()), this, SLOT(sendAck()), Qt::DirectConnection);
- if((ret = server->listen(QHostAddress::LocalHost, 0))) {
- socket->connectToHost(server->serverAddress(), server->serverPort());
- socket->waitForConnected();
- }
- return ret;
- }
+ public:
+ TestExcludeSocketNotifiers( const int* pipes );
+ ~TestExcludeSocketNotifiers();
+ bool received;
+ public slots:
+ void slotReceived();
+ private:
+ const int* pipes;
+};
- QTcpSocket *socket;
- QTcpServer *server;
- bool dataSent;
- bool testResult;
- bool dataArrived;
-public slots:
- void sendAck()
- {
- dataArrived = true;
- }
- void sendHello()
- {
- char data[10] ="HELLO";
- qint64 size = sizeof(data);
+TestExcludeSocketNotifiers::TestExcludeSocketNotifiers( const int* pipes )
+ : received( false )
+ , pipes( pipes )
+{
+}
- QTcpSocket *serverSocket = server->nextPendingConnection();
- serverSocket->write(data, size);
- dataSent = serverSocket->waitForBytesWritten(-1);
- QEventLoop loop;
- //allow the TCP/IP stack time to loopback the data, so our socket is ready to read
- QTimer::singleShot(200, &loop, SLOT(quit()));
- loop.exec(QEventLoop::ExcludeSocketNotifiers);
- testResult = dataArrived;
- //check the deferred event is processed
- QTimer::singleShot(200, &loop, SLOT(quit()));
- loop.exec();
- serverSocket->close();
- QThread::currentThread()->exit(0);
- }
-};
+TestExcludeSocketNotifiers::~TestExcludeSocketNotifiers()
+{
+ close( pipes[ 0 ] );
+ close( pipes[ 1 ] );
+}
-class SocketTestThread : public QThread
+void TestExcludeSocketNotifiers::slotReceived()
{
- Q_OBJECT
-public:
- SocketTestThread():QThread(0),testResult(false){};
- void run()
- {
- SocketEventsTester *tester = new SocketEventsTester();
- if (tester->init())
- exec();
- dataSent = tester->dataSent;
- testResult = tester->testResult;
- dataArrived = tester->dataArrived;
- delete tester;
- }
- bool dataSent;
- bool testResult;
- bool dataArrived;
-};
+ received = true;
+}
+
+}
#define QVERIFY(a) \
if (!a) return 1;
static int tst_processEventsExcludeSocket()
{
- SocketTestThread thread;
- thread.start();
- QVERIFY(thread.wait());
- QVERIFY(thread.dataSent);
- QVERIFY(!thread.testResult);
- QVERIFY(thread.dataArrived);
+ int pipes[ 2 ];
+ if( pipe( pipes ) < 0 )
+ return 1;
+ TestExcludeSocketNotifiers test( pipes );
+ QSocketNotifier notifier( pipes[ 0 ], QSocketNotifier::Read );
+ QObject::connect( &notifier, SIGNAL( activated( int )), &test, SLOT( slotReceived()));
+ char dummy = 'a';
+ write( pipes[ 1 ], &dummy, 1 );
+ QEventLoop loop;
+ loop.processEvents( QEventLoop::ExcludeSocketNotifiers );
+ QVERIFY( !test.received );
+ loop.processEvents();
+ QVERIFY( test.received );
return 0;
}
-