From 65773fc84adc6211f082475c4ad23fb3acbf6e8c Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@qt.io>
Date: Tue, 24 May 2016 11:15:16 +0200
Subject: [PATCH 1/2] QFileSystemWatcher/Win32: Listen for WM_DEVICECHANGE

Add a class QWindowsRemovableDriveListener to
QWindowsFileSystemWatcherEngine listening to the WM_DEVICECHANGE messages
sent to the top level windows to detect insertion and removal of USB drives.
In addition, hook into QWindowsFileSystemWatcherEngine::addPaths() when
watching on removable drives, registering a notification for a
volume-lock-for-removal message on the internal window handle obtained from
QEventDispatcherWin32. When a request to lock the volume for removal is
received, remove the paths from the watcher, enabling removal.

The class instance is set as a dynamic property on the QFileSystemWatcher where
it can be retrieved from clients. This is primarily intended for use by
QFileInfoGatherer/QFileSystemModel.

Task-number: QTBUG-14290
Task-number: QTBUG-18729
Task-number: QTBUG-55459
Change-Id: Ic95b9d9291b1ec6f426c0702bad896bb064b9346
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
---
 src/corelib/io/qfilesystemwatcher.cpp       |  53 ++++++
 src/corelib/io/qfilesystemwatcher_p.h       |  10 +
 src/corelib/io/qfilesystemwatcher_win.cpp   | 286 ++++++++++++++++++++++++++++
 src/corelib/io/qfilesystemwatcher_win_p.h   |  15 +-
 src/corelib/kernel/qeventdispatcher_win.cpp |   7 +
 src/corelib/kernel/qeventdispatcher_win_p.h |   2 +
 6 files changed, 369 insertions(+), 4 deletions(-)

diff --git a/src/corelib/io/qfilesystemwatcher.cpp b/src/corelib/io/qfilesystemwatcher.cpp
index 52ba1b5..ec89681 100644
--- a/src/corelib/io/qfilesystemwatcher.cpp
+++ b/src/corelib/io/qfilesystemwatcher.cpp
@@ -58,6 +58,9 @@
 #  include "qfilesystemwatcher_fsevents_p.h"
 #endif
 
+#include <algorithm>
+#include <iterator>
+
 QT_BEGIN_NAMESPACE
 
 QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject *parent)
@@ -96,6 +99,17 @@ void QFileSystemWatcherPrivate::init()
                          SIGNAL(directoryChanged(QString,bool)),
                          q,
                          SLOT(_q_directoryChanged(QString,bool)));
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
+        QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
+                         &QWindowsFileSystemWatcherEngine::driveLockForRemoval,
+                         q, [this] (const QString &p) { _q_winDriveLockForRemoval(p); });
+        QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
+                         &QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed,
+                         q, [this] (const QString &p) { _q_winDriveLockForRemovalFailed(p); });
+        QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
+                         &QWindowsFileSystemWatcherEngine::driveRemoved,
+                         q, [this] (const QString &p) { _q_winDriveRemoved(p); });
+#endif  // !Q_OS_WINRT
     }
 }
 
@@ -140,7 +154,46 @@ void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool re
     emit q->directoryChanged(path, QFileSystemWatcher::QPrivateSignal());
 }
 
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
+
+void QFileSystemWatcherPrivate::_q_winDriveLockForRemoval(const QString &path)
+{
+    // Windows: Request to lock a (removable/USB) drive for removal, release
+    // its paths under watch, temporarily storing them should the lock fail.
+    Q_Q(QFileSystemWatcher);
+    QStringList pathsToBeRemoved;
+    auto pred = [&path] (const QString &f) { return !f.startsWith(path, Qt::CaseInsensitive); };
+    std::remove_copy_if(files.cbegin(), files.cend(),
+                        std::back_inserter(pathsToBeRemoved), pred);
+    std::remove_copy_if(directories.cbegin(), directories.cend(),
+                        std::back_inserter(pathsToBeRemoved), pred);
+    if (!pathsToBeRemoved.isEmpty()) {
+        q->removePaths(pathsToBeRemoved);
+        temporarilyRemovedPaths.insert(path.at(0), pathsToBeRemoved);
+    }
+}
+
+void QFileSystemWatcherPrivate::_q_winDriveLockForRemovalFailed(const QString &path)
+{
+    // Windows: Request to lock a (removable/USB) drive failed (blocked by other
+    // application), restore the watched paths.
+    Q_Q(QFileSystemWatcher);
+    if (!path.isEmpty()) {
+        const auto it = temporarilyRemovedPaths.find(path.at(0));
+        if (it != temporarilyRemovedPaths.end()) {
+            q->addPaths(it.value());
+            temporarilyRemovedPaths.erase(it);
+        }
+    }
+}
 
+void  QFileSystemWatcherPrivate::_q_winDriveRemoved(const QString &path)
+{
+    // Windows: Drive finally removed, clear out paths stored in lock request.
+    if (!path.isEmpty())
+        temporarilyRemovedPaths.remove(path.at(0));
+}
+#endif // Q_OS_WIN && !Q_OS_WINRT
 
 /*!
     \class QFileSystemWatcher
diff --git a/src/corelib/io/qfilesystemwatcher_p.h b/src/corelib/io/qfilesystemwatcher_p.h
index b190a8f..c770e47 100644
--- a/src/corelib/io/qfilesystemwatcher_p.h
+++ b/src/corelib/io/qfilesystemwatcher_p.h
@@ -52,6 +52,7 @@
 #include <private/qobject_p.h>
 
 #include <QtCore/qstringlist.h>
+#include <QtCore/qhash.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -100,6 +101,15 @@ public:
     // private slots
     void _q_fileChanged(const QString &path, bool removed);
     void _q_directoryChanged(const QString &path, bool removed);
+
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
+    void _q_winDriveLockForRemoval(const QString &);
+    void _q_winDriveLockForRemovalFailed(const QString &);
+    void _q_winDriveRemoved(const QString &);
+
+private:
+    QHash<QChar, QStringList> temporarilyRemovedPaths;
+#endif // Q_OS_WIN && !Q_OS_WINRT
 };
 
 
diff --git a/src/corelib/io/qfilesystemwatcher_win.cpp b/src/corelib/io/qfilesystemwatcher_win.cpp
index 582aa3b..b87840f 100644
--- a/src/corelib/io/qfilesystemwatcher_win.cpp
+++ b/src/corelib/io/qfilesystemwatcher_win.cpp
@@ -46,6 +46,16 @@
 
 #include <qt_windows.h>
 
+#ifndef Q_OS_WINRT
+#  include <qabstractnativeeventfilter.h>
+#  include <qcoreapplication.h>
+#  include <qdir.h>
+#  include <private/qeventdispatcher_win_p.h>
+#  include <dbt.h>
+#  include <algorithm>
+#  include <vector>
+#endif // !Q_OS_WINRT
+
 QT_BEGIN_NAMESPACE
 
 // #define WINQFSW_DEBUG
@@ -55,11 +65,275 @@ QT_BEGIN_NAMESPACE
 #  define DEBUG if (false) qDebug
 #endif
 
+#ifndef Q_OS_WINRT
+///////////
+// QWindowsRemovableDriveListener
+// Listen for the various WM_DEVICECHANGE message indicating drive addition/removal
+// requests and removals.
+///////////
+class QWindowsRemovableDriveListener : public QObject, public QAbstractNativeEventFilter
+{
+    Q_OBJECT
+public:
+    // Device UUids as declared in ioevent.h (GUID_IO_VOLUME_LOCK, ...)
+    enum VolumeUuid { UnknownUuid, UuidIoVolumeLock, UuidIoVolumeLockFailed,
+                      UuidIoVolumeUnlock, UuidIoMediaRemoval };
+
+    struct RemovableDriveEntry {
+        HDEVNOTIFY devNotify;
+        wchar_t drive;
+    };
+
+    explicit QWindowsRemovableDriveListener(QObject *parent = nullptr);
+    ~QWindowsRemovableDriveListener();
+
+    // Call from QFileSystemWatcher::addPaths() to set up notifications on drives
+    void addPath(const QString &path);
+
+    bool nativeEventFilter(const QByteArray &, void *messageIn, long *) override;
+
+signals:
+    void driveAdded();
+    void driveRemoved(const QString &);
+    void driveLockForRemoval(const QString &);
+    void driveLockForRemovalFailed(const QString &);
+
+private:
+    static VolumeUuid volumeUuid(const UUID &needle);
+    void handleDbtCustomEvent(const MSG *msg);
+    void handleDbtDriveArrivalRemoval(const MSG *msg);
+
+    std::vector<RemovableDriveEntry> m_removableDrives;
+    quintptr m_lastMessageHash;
+};
+
+static inline QEventDispatcherWin32 *winEventDispatcher()
+{
+    return static_cast<QEventDispatcherWin32 *>(QCoreApplication::instance()->eventDispatcher());
+}
+
+QWindowsRemovableDriveListener::QWindowsRemovableDriveListener(QObject *parent)
+    : QObject(parent)
+    , m_lastMessageHash(0)
+{
+    winEventDispatcher()->installNativeEventFilter(this);
+}
+
+static void stopDeviceNotification(QWindowsRemovableDriveListener::RemovableDriveEntry &e)
+{
+    UnregisterDeviceNotification(e.devNotify);
+    e.devNotify = 0;
+}
+
+template <class Iterator> // Search sequence of RemovableDriveEntry for HDEVNOTIFY.
+static inline Iterator findByHDevNotify(Iterator i1, Iterator i2, HDEVNOTIFY hdevnotify)
+{
+    return std::find_if(i1, i2,
+                        [hdevnotify] (const QWindowsRemovableDriveListener::RemovableDriveEntry &e) { return e.devNotify == hdevnotify; });
+}
+
+QWindowsRemovableDriveListener::~QWindowsRemovableDriveListener()
+{
+    std::for_each(m_removableDrives.begin(), m_removableDrives.end(), stopDeviceNotification);
+}
+
+static QString pathFromEntry(const QWindowsRemovableDriveListener::RemovableDriveEntry &re)
+{
+    QString path = QStringLiteral("A:/");
+    path[0] = QChar::fromLatin1(re.drive);
+    return path;
+}
+
+// Handle WM_DEVICECHANGE+DBT_CUSTOMEVENT, which is sent based on the registration
+// on the volume handle with QEventDispatcherWin32's message window in the class.
+// Capture the GUID_IO_VOLUME_LOCK indicating the drive is to be removed.
+QWindowsRemovableDriveListener::VolumeUuid QWindowsRemovableDriveListener::volumeUuid(const UUID &needle)
+{
+    static const struct VolumeUuidMapping // UUIDs from IoEvent.h (missing in MinGW)
+    {
+        VolumeUuid v;
+        UUID uuid;
+    } mapping[] = {
+        { UuidIoVolumeLock, // GUID_IO_VOLUME_LOCK
+          {0x50708874, 0xc9af, 0x11d1, {0x8f, 0xef, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
+        { UuidIoVolumeLockFailed, // GUID_IO_VOLUME_LOCK_FAILED
+          {0xae2eed10, 0x0ba8, 0x11d2, {0x8f, 0xfb, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
+        { UuidIoVolumeUnlock, // GUID_IO_VOLUME_UNLOCK
+          {0x9a8c3d68, 0xd0cb, 0x11d1, {0x8f, 0xef, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
+        { UuidIoMediaRemoval, // GUID_IO_MEDIA_REMOVAL
+          {0xd07433c1, 0xa98e, 0x11d2, {0x91, 0x7a, 0x0, 0xa0, 0xc9, 0x06, 0x8f, 0xf3}} }
+    };
+
+    static const VolumeUuidMapping *end = mapping + sizeof(mapping) / sizeof(mapping[0]);
+    const VolumeUuidMapping *m =
+        std::find_if(mapping, end, [&needle] (const VolumeUuidMapping &m) { return IsEqualGUID(m.uuid, needle); });
+    return m != end ? m->v : UnknownUuid;
+}
+
+inline void QWindowsRemovableDriveListener::handleDbtCustomEvent(const MSG *msg)
+{
+    const DEV_BROADCAST_HDR *broadcastHeader = reinterpret_cast<const DEV_BROADCAST_HDR *>(msg->lParam);
+    if (broadcastHeader->dbch_devicetype != DBT_DEVTYP_HANDLE)
+        return;
+    const DEV_BROADCAST_HANDLE *broadcastHandle = reinterpret_cast<const DEV_BROADCAST_HANDLE *>(broadcastHeader);
+    const auto it = findByHDevNotify(m_removableDrives.cbegin(), m_removableDrives.cend(),
+                                     broadcastHandle->dbch_hdevnotify);
+    if (it == m_removableDrives.cend())
+        return;
+    switch (volumeUuid(broadcastHandle->dbch_eventguid)) {
+    case UuidIoVolumeLock: // Received for removable USB media
+        emit driveLockForRemoval(pathFromEntry(*it));
+        break;
+    case UuidIoVolumeLockFailed:
+        emit driveLockForRemovalFailed(pathFromEntry(*it));
+        break;
+    case UuidIoVolumeUnlock:
+        break;
+    case UuidIoMediaRemoval: // Received for optical drives
+        break;
+    default:
+        break;
+    }
+}
+
+// Handle WM_DEVICECHANGE+DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE which are
+// sent to all top level windows and cannot be registered for (that is, their
+// triggering depends on top level windows being present)
+inline void QWindowsRemovableDriveListener::handleDbtDriveArrivalRemoval(const MSG *msg)
+{
+    const DEV_BROADCAST_HDR *broadcastHeader = reinterpret_cast<const DEV_BROADCAST_HDR *>(msg->lParam);
+    switch (broadcastHeader->dbch_devicetype) {
+    case DBT_DEVTYP_HANDLE: // WM_DEVICECHANGE/DBT_DEVTYP_HANDLE is sent for our registered drives.
+        if (msg->wParam == DBT_DEVICEREMOVECOMPLETE) {
+            const DEV_BROADCAST_HANDLE *broadcastHandle = reinterpret_cast<const DEV_BROADCAST_HANDLE *>(broadcastHeader);
+            const auto it = findByHDevNotify(m_removableDrives.begin(), m_removableDrives.end(),
+                                           broadcastHandle->dbch_hdevnotify);
+            // Emit for removable USB drives we were registered for.
+            if (it != m_removableDrives.end()) {
+                emit driveRemoved(pathFromEntry(*it));
+                stopDeviceNotification(*it);
+                m_removableDrives.erase(it);
+            }
+        }
+        break;
+    case DBT_DEVTYP_VOLUME: {
+        const DEV_BROADCAST_VOLUME *broadcastVolume = reinterpret_cast<const DEV_BROADCAST_VOLUME *>(broadcastHeader);
+        // WM_DEVICECHANGE/DBT_DEVTYP_VOLUME messages are sent to all toplevel windows. Compare a hash value to ensure
+        // it is handled only once.
+        const quintptr newHash = reinterpret_cast<quintptr>(broadcastVolume) + msg->wParam
+            + quintptr(broadcastVolume->dbcv_flags) + quintptr(broadcastVolume->dbcv_unitmask);
+        if (newHash == m_lastMessageHash)
+            return;
+        m_lastMessageHash = newHash;
+        // Check for DBTF_MEDIA (inserted/Removed Optical drives). Ignore for now.
+        if (broadcastVolume->dbcv_flags & DBTF_MEDIA)
+            return;
+        // Continue with plugged in USB media where dbcv_flags=0.
+        switch (msg->wParam) {
+        case DBT_DEVICEARRIVAL:
+            emit driveAdded();
+            break;
+        case DBT_DEVICEREMOVECOMPLETE: // handled by DBT_DEVTYP_HANDLE above
+            break;
+        }
+    }
+        break;
+    }
+}
+
+bool QWindowsRemovableDriveListener::nativeEventFilter(const QByteArray &, void *messageIn, long *)
+{
+    const MSG *msg = reinterpret_cast<const MSG *>(messageIn);
+    if (msg->message == WM_DEVICECHANGE) {
+        switch (msg->wParam) {
+        case DBT_CUSTOMEVENT:
+            handleDbtCustomEvent(msg);
+            break;
+        case DBT_DEVICEARRIVAL:
+        case DBT_DEVICEREMOVECOMPLETE:
+            handleDbtDriveArrivalRemoval(msg);
+            break;
+        }
+    }
+    return false;
+}
+
+// Set up listening for WM_DEVICECHANGE+DBT_CUSTOMEVENT for a removable drive path,
+void QWindowsRemovableDriveListener::addPath(const QString &p)
+{
+    const wchar_t drive = p.size() >= 2 && p.at(0).isLetter() && p.at(1) == QLatin1Char(':')
+        ? wchar_t(p.at(0).toUpper().unicode()) : L'\0';
+    if (!drive)
+        return;
+    // Already listening?
+    if (std::any_of(m_removableDrives.cbegin(), m_removableDrives.cend(),
+                    [drive](const RemovableDriveEntry &e) { return e.drive == drive; })) {
+        return;
+    }
+
+    wchar_t devicePath[8] = L"\\\\.\\A:\\";
+    devicePath[4] = drive;
+    RemovableDriveEntry re;
+    re.drive = drive;
+    if (GetDriveTypeW(devicePath + 4) != DRIVE_REMOVABLE)
+        return;
+    const HANDLE volumeHandle =
+        CreateFile(devicePath, FILE_READ_ATTRIBUTES,
+                   FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, 0,
+                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, //  Volume requires BACKUP_SEMANTICS
+                   0);
+    if (volumeHandle == INVALID_HANDLE_VALUE) {
+        qErrnoWarning("CreateFile %s failed.",
+                      qPrintable(QString::fromWCharArray(devicePath)));
+        return;
+    }
+
+    DEV_BROADCAST_HANDLE notify;
+    ZeroMemory(&notify, sizeof(notify));
+    notify.dbch_size = sizeof(notify);
+    notify.dbch_devicetype = DBT_DEVTYP_HANDLE;
+    notify.dbch_handle = volumeHandle;
+    re.devNotify = RegisterDeviceNotification(winEventDispatcher()->internalHwnd(),
+                                              &notify, DEVICE_NOTIFY_WINDOW_HANDLE);
+    // Empirically found: The notifications also work when the handle is immediately
+    // closed. Do it here to avoid having to close/reopen in lock message handling.
+    CloseHandle(volumeHandle);
+    if (!re.devNotify) {
+        qErrnoWarning("RegisterDeviceNotification %s failed.",
+                      qPrintable(QString::fromWCharArray(devicePath)));
+        return;
+    }
+
+    m_removableDrives.push_back(re);
+}
+#endif // !Q_OS_WINRT
+
+///////////
+// QWindowsFileSystemWatcherEngine
+///////////
 QWindowsFileSystemWatcherEngine::Handle::Handle()
     : handle(INVALID_HANDLE_VALUE), flags(0u)
 {
 }
 
+QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine(QObject *parent)
+    : QFileSystemWatcherEngine(parent)
+#ifndef Q_OS_WINRT
+    , m_driveListener(new QWindowsRemovableDriveListener(this))
+#endif
+{
+#ifndef Q_OS_WINRT
+    parent->setProperty("_q_driveListener",
+                        QVariant::fromValue(static_cast<QObject *>(m_driveListener)));
+    QObject::connect(m_driveListener, &QWindowsRemovableDriveListener::driveLockForRemoval,
+                     this, &QWindowsFileSystemWatcherEngine::driveLockForRemoval);
+    QObject::connect(m_driveListener, &QWindowsRemovableDriveListener::driveLockForRemovalFailed,
+                     this, &QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed);
+    QObject::connect(m_driveListener, &QWindowsRemovableDriveListener::driveRemoved,
+                     this, &QWindowsFileSystemWatcherEngine::driveRemoved);
+#endif // !Q_OS_WINRT
+}
+
 QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine()
 {
     foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) {
@@ -208,6 +482,13 @@ QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths,
             }
         }
     }
+
+#ifndef Q_OS_WINRT
+    for (const QString &path : paths) {
+        if (!p.contains(path))
+            m_driveListener->addPath(path);
+    }
+#endif // !Q_OS_WINRT
     return p;
 }
 
@@ -437,4 +718,9 @@ void QWindowsFileSystemWatcherEngineThread::wakeup()
 }
 
 QT_END_NAMESPACE
+
+#ifndef Q_OS_WINRT
+#  include "qfilesystemwatcher_win.moc"
+#endif
+
 #endif // QT_NO_FILESYSTEMWATCHER
diff --git a/src/corelib/io/qfilesystemwatcher_win_p.h b/src/corelib/io/qfilesystemwatcher_win_p.h
index 49a42ea..38f5c8f 100644
--- a/src/corelib/io/qfilesystemwatcher_win_p.h
+++ b/src/corelib/io/qfilesystemwatcher_win_p.h
@@ -60,6 +60,7 @@
 QT_BEGIN_NAMESPACE
 
 class QWindowsFileSystemWatcherEngineThread;
+class QWindowsRemovableDriveListener;
 
 // Even though QWindowsFileSystemWatcherEngine is derived of QThread
 // via QFileSystemWatcher, it does not start a thread.
@@ -69,9 +70,7 @@ class QWindowsFileSystemWatcherEngine : public QFileSystemWatcherEngine
 {
     Q_OBJECT
 public:
-    inline QWindowsFileSystemWatcherEngine(QObject *parent)
-        : QFileSystemWatcherEngine(parent)
-    { }
+    explicit QWindowsFileSystemWatcherEngine(QObject *parent);
     ~QWindowsFileSystemWatcherEngine();
 
     QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories);
@@ -115,9 +114,17 @@ public:
                     || lastModified != fileInfo.lastModified());
         }
     };
+
+signals:
+    void driveLockForRemoval(const QString &);
+    void driveLockForRemovalFailed(const QString &);
+    void driveRemoved(const QString &);
+
 private:
     QList<QWindowsFileSystemWatcherEngineThread *> threads;
-
+#ifndef Q_OS_WINRT
+    QWindowsRemovableDriveListener *m_driveListener;
+#endif
 };
 
 class QFileSystemWatcherPathKey : public QString
diff --git a/src/corelib/kernel/qeventdispatcher_win.cpp b/src/corelib/kernel/qeventdispatcher_win.cpp
index 31c6530..fe738d6 100644
--- a/src/corelib/kernel/qeventdispatcher_win.cpp
+++ b/src/corelib/kernel/qeventdispatcher_win.cpp
@@ -1287,4 +1287,11 @@ void QEventDispatcherWin32::sendPostedEvents()
     QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
 }
 
+HWND QEventDispatcherWin32::internalHwnd()
+{
+    Q_D(QEventDispatcherWin32);
+    createInternalHwnd();
+    return d->internalHwnd;
+}
+
 QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qeventdispatcher_win_p.h b/src/corelib/kernel/qeventdispatcher_win_p.h
index 989bac3..f5f8194 100644
--- a/src/corelib/kernel/qeventdispatcher_win_p.h
+++ b/src/corelib/kernel/qeventdispatcher_win_p.h
@@ -100,6 +100,8 @@ public:
 
     bool event(QEvent *e);
 
+    HWND internalHwnd();
+
 protected:
     QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent = 0);
     virtual void sendPostedEvents();
-- 
2.8.1.windows.1

