QWindowsTheme: Run SHGetFileInfo() in a thread.

Windows 10: SHGetFileInfo() (as called by item views on file system
models has been observed to trigger a WM_PAINT on the mainwindow
for totally obscure reasons, causing a recursive repaint. Suppress
this by running it via QThreadPool.

Task-number: QTBUG-45298
Task-number: QTBUG-48823
Task-number: QTCREATORBUG-14888
Change-Id: I7479102b9b8fb0771681260298c3d735e66f220f
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
This commit is contained in:
Friedemann Kleint 2016-02-22 09:42:39 +01:00
parent c0963486ce
commit ea757da436
4 changed files with 165 additions and 12 deletions

View File

@ -122,6 +122,41 @@ static inline QColor getSysColor(int index)
return COLORREFToQColor(GetSysColor(index));
}
#ifndef QT_NO_WINCE_SHELLSDK
// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
// behavior by running it in a thread.
class ShGetFileInfoFunction
{
public:
explicit ShGetFileInfoFunction(const wchar_t *fn, DWORD a, SHFILEINFO *i, UINT f, bool *r) :
m_fileName(fn), m_attributes(a), m_flags(f), m_info(i), m_result(r) {}
void operator()() const { *m_result = SHGetFileInfo(m_fileName, m_attributes, m_info, sizeof(SHFILEINFO), m_flags); }
private:
const wchar_t *m_fileName;
const DWORD m_attributes;
const UINT m_flags;
SHFILEINFO *const m_info;
bool *m_result;
};
static bool shGetFileInfoBackground(QWindowsThreadPoolRunner &r,
const wchar_t *fileName, DWORD attributes,
SHFILEINFO *info, UINT flags,
unsigned long timeOutMSecs = 5000)
{
bool result = false;
if (!r.run(ShGetFileInfoFunction(fileName, attributes, info, flags, &result), timeOutMSecs)) {
qWarning().noquote() << "ShGetFileInfoBackground() timed out for "
<< QString::fromWCharArray(fileName);
return false;
}
return result;
}
#endif // !QT_NO_WINCE_SHELLSDK
// from QStyle::standardPalette
static inline QPalette standardPalette()
{
@ -690,23 +725,22 @@ QPixmap QWindowsTheme::fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &s
}
SHFILEINFO info;
unsigned int flags =
const unsigned int flags =
#ifndef Q_OS_WINCE
SHGFI_ICON|iconSize|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX;
#else
iconSize|SHGFI_SYSICONINDEX;
#endif // Q_OS_WINCE
unsigned long val = 0;
#if !defined(QT_NO_WINCE_SHELLSDK)
if (cacheableDirIcon && useDefaultFolderIcon) {
flags |= SHGFI_USEFILEATTRIBUTES;
val = SHGetFileInfo(L"dummy",
FILE_ATTRIBUTE_DIRECTORY,
&info, sizeof(SHFILEINFO), flags);
} else {
val = SHGetFileInfo(reinterpret_cast<const wchar_t *>(filePath.utf16()), 0,
&info, sizeof(SHFILEINFO), flags);
}
const bool val = cacheableDirIcon && useDefaultFolderIcon
? shGetFileInfoBackground(m_threadPoolRunner, L"dummy", FILE_ATTRIBUTE_DIRECTORY,
&info, flags | SHGFI_USEFILEATTRIBUTES)
: shGetFileInfoBackground(m_threadPoolRunner, reinterpret_cast<const wchar_t *>(filePath.utf16()), 0,
&info, flags);
#else
const bool val = false;
#endif // !QT_NO_WINCE_SHELLSDK
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases

View File

@ -34,6 +34,7 @@
#ifndef QWINDOWSTHEME_H
#define QWINDOWSTHEME_H
#include "qwindowsthreadpoolrunner.h"
#include <qpa/qplatformtheme.h>
QT_BEGIN_NAMESPACE
@ -74,6 +75,7 @@ private:
static QWindowsTheme *m_instance;
QPalette *m_palettes[NPalettes];
QFont *m_fonts[NFonts];
mutable QWindowsThreadPoolRunner m_threadPoolRunner;
};
QT_END_NAMESPACE

View File

@ -0,0 +1,116 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QWINDOWSTHREADPOOLRUNNER_H
#define QWINDOWSTHREADPOOLRUNNER_H
#include <QtCore/QMutex>
#include <QtCore/QRunnable>
#include <QtCore/QThreadPool>
#include <QtCore/QWaitCondition>
QT_BEGIN_NAMESPACE
/*!
\class QWindowsThreadPoolRunner
\brief Runs a task in the global instance of QThreadPool
QThreadPool does not provide a method to wait on a single task, so this needs
to be done by using QWaitCondition/QMutex.
\internal
\ingroup qt-lighthouse-win
*/
class QWindowsThreadPoolRunner
{
Q_DISABLE_COPY(QWindowsThreadPoolRunner)
#ifndef QT_NO_THREAD
template <class RunnableFunction> // nested class implementing QRunnable to execute a function.
class Runnable : public QRunnable
{
public:
explicit Runnable(QMutex *m, QWaitCondition *c, RunnableFunction f)
: m_mutex(m), m_condition(c), m_function(f) {}
void run() Q_DECL_OVERRIDE
{
m_function();
m_mutex->lock();
m_condition->wakeAll();
m_mutex->unlock();
}
private:
QMutex *m_mutex;
QWaitCondition *m_condition;
RunnableFunction m_function;
}; // class Runnable
public:
QWindowsThreadPoolRunner() {}
template <class Function>
bool run(Function f, unsigned long timeOutMSecs = 5000)
{
QThreadPool *pool = QThreadPool::globalInstance();
Q_ASSERT(pool);
Runnable<Function> *runnable = new Runnable<Function>(&m_mutex, &m_condition, f);
m_mutex.lock();
pool->start(runnable);
const bool ok = m_condition.wait(&m_mutex, timeOutMSecs);
m_mutex.unlock();
if (!ok)
pool->cancel(runnable);
return ok;
}
private:
QMutex m_mutex;
QWaitCondition m_condition;
#else // !QT_NO_THREAD
public:
QWindowsThreadPoolRunner() {}
template <class Function>
bool run(Function f, unsigned long /* timeOutMSecs */ = 5000)
{
f();
return true;
}
#endif // QT_NO_THREAD
};
QT_END_NAMESPACE
#endif // QWINDOWSTHREADPOOLRUNNER_H

View File

@ -63,7 +63,8 @@ HEADERS += \
$$PWD/qplatformfunctions_wince.h \
$$PWD/qwindowsnativeimage.h \
$$PWD/qwindowsnativeinterface.h \
$$PWD/qwindowsopengltester.h
$$PWD/qwindowsopengltester.h \
$$PWD/qwindowsthreadpoolrunner.h
INCLUDEPATH += $$PWD