qt6windows7/tests/auto/gui/kernel/noqteventloop/tst_noqteventloop.cpp

488 lines
14 KiB
C++
Raw Normal View History

2023-10-30 06:33:08 +08:00
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QEvent>
#include <QtTest/QSignalSpy>
#include <QtCore/qthread.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpainter.h>
#include <QtGui/qrasterwindow.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qtcpsocket.h>
#include <QtNetwork/qlocalserver.h>
#include <QtNetwork/qlocalsocket.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qwineventnotifier.h>
#include <QtCore/qt_windows.h>
static const int topVerticalMargin = 50;
static const int margin = 10;
class tst_NoQtEventLoop : public QObject
{
Q_OBJECT
private slots:
void consumeMouseEvents();
void consumeSocketEvents();
void consumeLocalSocketEvents();
void consumeWinEvents_data();
void consumeWinEvents();
void deliverEventsInLivelock();
void postingWithNoYieldFlag();
};
class Window : public QRasterWindow
{
public:
explicit Window(QWindow *parentWindow = nullptr) : QRasterWindow(parentWindow)
{
}
void reset()
{
m_received.clear();
}
bool event(QEvent *event) override
{
m_received[event->type()]++;
return QRasterWindow::event(event);
}
int received(QEvent::Type type)
{
return m_received.value(type, 0);
}
QHash<QEvent::Type, int> m_received;
protected:
void paintEvent(QPaintEvent *) override
{
QPainter p(this);
p.fillRect(QRect(QPoint(0, 0), size()), Qt::yellow);
}
};
bool g_exit = false;
extern "C" LRESULT QT_WIN_CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_SHOWWINDOW && wParam == 0)
g_exit = true;
return DefWindowProc(hwnd, message, wParam, lParam);
}
class TestThread : public QThread
{
Q_OBJECT
public:
TestThread(HWND parentWnd, Window *childWindow) : QThread(), m_hwnd(parentWnd), m_childWindow(childWindow) {
m_screenW = ::GetSystemMetrics(SM_CXSCREEN);
m_screenH = ::GetSystemMetrics(SM_CYSCREEN);
}
enum {
MouseClick,
MouseMove
};
void mouseInput(int command, const QPoint &p = QPoint())
{
INPUT mouseEvent;
mouseEvent.type = INPUT_MOUSE;
MOUSEINPUT &mi = mouseEvent.mi;
mi.mouseData = 0;
mi.time = 0;
mi.dwExtraInfo = 0;
mi.dx = 0;
mi.dy = 0;
switch (command) {
case MouseClick:
mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
::SendInput(1, &mouseEvent, sizeof(INPUT));
::Sleep(50);
mi.dwFlags = MOUSEEVENTF_LEFTUP;
::SendInput(1, &mouseEvent, sizeof(INPUT));
break;
case MouseMove:
mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
mi.dx = p.x() * 65536 / m_screenW;
mi.dy = p.y() * 65536 / m_screenH;
::SendInput(1, &mouseEvent, sizeof(INPUT));
break;
}
}
void mouseClick()
{
mouseInput(MouseClick);
}
void mouseMove(const QPoint &pt)
{
mouseInput(MouseMove, pt);
}
void run() override
{
struct ScopedCleanup
{
/* This is in order to ensure that the window is hidden when returning from run(),
regardless of the return point (e.g. with QTRY_COMPARE) */
ScopedCleanup(HWND hwnd) : m_hwnd(hwnd) { }
~ScopedCleanup() {
::ShowWindow(m_hwnd, SW_HIDE);
}
HWND m_hwnd;
} cleanup(m_hwnd);
m_testPassed = false;
POINT pt;
pt.x = 0;
pt.y = 0;
if (!::ClientToScreen(m_hwnd, &pt))
return;
m_windowPos = QPoint(pt.x, pt.y);
// First activate the parent window (which will also activate the child window)
m_windowPos += QPoint(5,5);
mouseMove(m_windowPos);
::Sleep(150);
mouseClick();
// At this point the windows are activated, no further events will be send to the QWindow
// if we click on the native parent HWND
m_childWindow->reset();
::Sleep(150);
mouseClick();
::Sleep(150);
QTRY_COMPARE(m_childWindow->received(QEvent::MouseButtonPress) + m_childWindow->received(QEvent::MouseButtonRelease), 0);
// Now click in the QWindow. The QWindow should receive those events.
m_windowPos.rx() += margin;
m_windowPos.ry() += topVerticalMargin;
mouseMove(m_windowPos);
::Sleep(150);
mouseClick();
QTRY_COMPARE(m_childWindow->received(QEvent::MouseButtonPress), 1);
QTRY_COMPARE(m_childWindow->received(QEvent::MouseButtonRelease), 1);
m_testPassed = true;
// ScopedCleanup will hide the window here
// Once the native window is hidden, it will exit the event loop.
}
bool passed() const { return m_testPassed; }
private:
int m_screenW;
int m_screenH;
bool m_testPassed;
HWND m_hwnd;
Window *m_childWindow;
QPoint m_windowPos;
};
void tst_NoQtEventLoop::consumeMouseEvents()
{
int argc = 1;
char *argv[] = {const_cast<char*>("test")};
// ensure scaling is off since the child window is positioned using QWindow API.
// FIXME: Position by taking the screen DPR into account instead
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
QGuiApplication app(argc, argv);
QString clsName(QStringLiteral("tst_NoQtEventLoop_WINDOW"));
const HINSTANCE appInstance = (HINSTANCE)GetModuleHandle(0);
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_DBLCLKS | CS_OWNDC; // CS_SAVEBITS
wc.lpfnWndProc = wndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = appInstance;
wc.hIcon = 0;
wc.hIconSm = 0;
wc.hCursor = 0;
wc.hbrBackground = ::GetSysColorBrush(COLOR_BTNFACE /*COLOR_WINDOW*/);
wc.lpszMenuName = 0;
wc.lpszClassName = (wchar_t*)clsName.utf16();
ATOM atom = ::RegisterClassEx(&wc);
QVERIFY2(atom, "RegisterClassEx failed");
DWORD dwExStyle = WS_EX_APPWINDOW;
DWORD dwStyle = WS_CAPTION | WS_TABSTOP | WS_VISIBLE;
const int screenWidth = ::GetSystemMetrics(SM_CXSCREEN);
const int screenHeight = ::GetSystemMetrics(SM_CYSCREEN);
const int width = screenWidth / 4;
const int height = screenHeight / 4;
HWND mainWnd =
::CreateWindowEx(dwExStyle, reinterpret_cast<const wchar_t*>(clsName.utf16()),
TEXT("tst_NoQtEventLoop"), dwStyle,
(screenWidth - width) / 2, (screenHeight - height) / 2 , width, height,
0, NULL, appInstance, NULL);
QVERIFY2(mainWnd, "CreateWindowEx failed");
::ShowWindow(mainWnd, SW_SHOW);
::SetWindowPos(mainWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
QWindow *mainWindow = QWindow::fromWinId(reinterpret_cast<WId>(mainWnd));
Window *childWindow = new Window(mainWindow);
childWindow->setGeometry(margin, topVerticalMargin,
width - 2 * margin, height - margin - topVerticalMargin);
childWindow->show();
TestThread *testThread = new TestThread(mainWnd, childWindow);
connect(testThread, SIGNAL(finished()), testThread, SLOT(deleteLater()));
testThread->start();
// Our own message loop...
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0) > 0) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (g_exit)
break;
}
delete mainWindow;
QCOMPARE(testThread->passed(), true);
}
void tst_NoQtEventLoop::consumeSocketEvents()
{
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
QTcpServer server;
QTcpSocket client;
QVERIFY(server.listen(QHostAddress::LocalHost));
client.connectToHost(server.serverAddress(), server.serverPort());
QVERIFY(client.waitForConnected());
QElapsedTimer elapsedTimer;
elapsedTimer.start();
// Exec own message loop
MSG msg;
forever {
if (elapsedTimer.hasExpired(3000) || server.hasPendingConnections())
break;
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
QVERIFY(server.hasPendingConnections());
}
void tst_NoQtEventLoop::consumeLocalSocketEvents()
{
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
QLocalServer server;
QLocalSocket client;
QSignalSpy readyReadSpy(&client, &QIODevice::readyRead);
QVERIFY(server.listen("consumeLocalSocketEvents"));
client.connectToServer("consumeLocalSocketEvents");
QVERIFY(client.waitForConnected(200));
QVERIFY(server.waitForNewConnection(200));
QLocalSocket *clientSocket = server.nextPendingConnection();
QVERIFY(clientSocket);
QSignalSpy bytesWrittenSpy(clientSocket, &QIODevice::bytesWritten);
server.close();
bool timeExpired = false;
QTimer::singleShot(3000, Qt::CoarseTimer, [&timeExpired]() {
timeExpired = true;
});
QVERIFY(clientSocket->putChar(0));
// Exec own message loop
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (timeExpired || readyReadSpy.count() != 0)
break;
}
QVERIFY(!timeExpired);
QCOMPARE(bytesWrittenSpy.count(), 1);
QCOMPARE(readyReadSpy.count(), 1);
}
void tst_NoQtEventLoop::consumeWinEvents_data()
{
QTest::addColumn<bool>("peeking");
QTest::addColumn<bool>("nestedLoops");
QTest::addColumn<bool>("nativeMainLoop");
QTest::newRow("PeekMessage, single loop") << true << false << true;
QTest::newRow("GetMessage, single loop") << false << false << true;
QTest::newRow("PeekMessage, nested loops") << true << true << true;
QTest::newRow("GetMessage, nested loops") << false << true << true;
QTest::newRow("PeekMessage, nested loops, Qt main loop") << true << true << false;
QTest::newRow("GetMessage, nested loops, Qt main loop") << false << true << false;
}
void tst_NoQtEventLoop::consumeWinEvents()
{
QFETCH(bool, peeking);
QFETCH(bool, nestedLoops);
QFETCH(bool, nativeMainLoop);
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
HANDLE winEvent = ::CreateEvent(0, FALSE, FALSE, 0);
QVERIFY(winEvent != NULL);
bool notifierActivated = false;
QWinEventNotifier notifier(winEvent);
connect(&notifier, &QWinEventNotifier::activated, [&notifierActivated]() {
notifierActivated = true;
});
bool timeExpired = false;
QTimer expiredTimer;
connect(&expiredTimer, &QTimer::timeout, [&timeExpired]() {
timeExpired = true;
});
expiredTimer.start(3000);
auto runNativeEventLoop = [&timeExpired, &notifierActivated, peeking]() -> void {
MSG msg;
while (!(timeExpired || notifierActivated)) {
if (peeking) {
if (!::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
QThread::msleep(100);
continue;
}
} else {
if (::GetMessage(&msg, NULL, 0, 0) <= 0)
break;
}
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
};
QTimer::singleShot(0, [winEvent, nestedLoops, runNativeEventLoop]() {
::SetEvent(winEvent);
if (nestedLoops)
runNativeEventLoop();
});
// Exec main message loop
if (nativeMainLoop) {
runNativeEventLoop();
} else {
QEventLoop loop;
connect(&notifier, &QWinEventNotifier::activated,
&loop, &QEventLoop::quit);
connect(&expiredTimer, &QTimer::timeout,
&loop, &QEventLoop::quit);
loop.exec();
}
QVERIFY(notifierActivated);
QVERIFY(!timeExpired);
}
void tst_NoQtEventLoop::deliverEventsInLivelock()
{
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
QTimer livelockTimer;
livelockTimer.start(0);
QTimer::singleShot(100, Qt::CoarseTimer, &livelockTimer, &QTimer::stop);
QElapsedTimer elapsedTimer;
elapsedTimer.start();
// Exec own message loop
MSG msg;
forever {
if (elapsedTimer.hasExpired(3000) || !livelockTimer.isActive())
break;
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
QVERIFY(!livelockTimer.isActive());
}
void tst_NoQtEventLoop::postingWithNoYieldFlag()
{
int argc = 1;
char *argv[] = { const_cast<char *>("test"), 0 };
QGuiApplication app(argc, argv);
bool signalReceived = false;
// Post a message to the queue
QMetaObject::invokeMethod(this, [&signalReceived]() {
signalReceived = true;
}, Qt::QueuedConnection);
// Post some system messages
QWindow mainWindow;
mainWindow.show();
QElapsedTimer elapsedTimer;
elapsedTimer.start();
// Exec own message loop
MSG msg;
forever {
if (elapsedTimer.hasExpired(3000) || signalReceived)
break;
if (!::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
QThread::msleep(100);
continue;
}
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
QVERIFY(signalReceived);
}
#include <tst_noqteventloop.moc>
QTEST_APPLESS_MAIN(tst_NoQtEventLoop)