blob: e9944f8157f6cd8a53af1704482e797d9287f6ab [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QScreen>
#include <QtGui/QRasterWindow>
using namespace MockCompositor;
class tst_output : public QObject, private DefaultCompositor
{
Q_OBJECT
private slots:
void initTestCase()
{
m_config.autoConfigure = true;
m_config.autoEnter = false;
}
void cleanup()
{
QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // Only the default output should be left
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage()));
}
void primaryScreen();
void secondaryHiDpiScreen();
void addScreenWithGeometryChange();
void windowScreens();
void removePrimaryScreen();
void screenOrder();
void removeAllScreens();
};
void tst_output::primaryScreen()
{
// Verify that the client has bound to the output global
QCOMPOSITOR_TRY_COMPARE(output()->resourceMap().size(), 1);
QTRY_VERIFY(QGuiApplication::primaryScreen());
QScreen *screen = QGuiApplication::primaryScreen();
QCOMPARE(screen->manufacturer(), "Make");
QCOMPARE(screen->model(), "Model");
QCOMPARE(screen->size(), QSize(1920, 1080));
QCOMPARE(screen->refreshRate(), 60);
QCOMPARE(qRound(screen->physicalDotsPerInch()), 96 / screen->devicePixelRatio());
QCOMPARE(screen->devicePixelRatio(), 1);
QCOMPARE(screen->logicalDotsPerInch(), 96);
}
void tst_output::secondaryHiDpiScreen()
{
exec([=] {
OutputData d;
d.position = {1920, 0}; // in global compositor space (not pixels)
d.mode.resolution = {800, 640};
d.physicalSize = d.mode.physicalSizeForDpi(200);
d.scale = 2;
add<Output>(d);
});
// Verify that the client has bound to the output global
QCOMPOSITOR_TRY_VERIFY(output(1) && output(1)->resourceMap().size() == 1);
QTRY_COMPARE(QGuiApplication::screens().size(), 2);
QScreen *screen = QGuiApplication::screens()[1];
QCOMPARE(screen->refreshRate(), 60);
QCOMPARE(screen->devicePixelRatio(), 2);
QCOMPARE(screen->logicalDotsPerInch(), 96);
// Dots currently means device pixels, not actual pixels (see QTBUG-62649)
QCOMPARE(qRound(screen->physicalDotsPerInch() * screen->devicePixelRatio()), 200);
// Size is in logical pixel coordinates
QCOMPARE(screen->size(), QSize(800, 640) / 2);
QCOMPARE(screen->geometry(), QRect(QPoint(1920, 0), QSize(400, 320)));
QCOMPARE(screen->virtualGeometry(), QRect(QPoint(0, 0), QSize(1920 + 800 / 2, 1080)));
exec([=] { remove(output(1)); });
}
// QTBUG-62044
void tst_output::addScreenWithGeometryChange()
{
const QPoint initialPosition = exec([=] { return output(0)->m_data.position; });
exec([=] {
auto *oldOutput = output(0);
auto *newOutput = add<Output>();
newOutput->m_data.mode.resolution = {1280, 720};
// Move the primary output to the right
QPoint newPosition(newOutput->m_data.mode.resolution.width(), 0);
Q_ASSERT(newPosition != initialPosition);
oldOutput->m_data.position = newPosition;
oldOutput->sendGeometry();
oldOutput->sendDone();
});
QTRY_COMPARE(QGuiApplication::screens().size(), 2);
QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(1280, 0), QSize(1920, 1080)));
// Remove the extra output and move the old one back
exec([=] {
remove(output(1));
output()->m_data.position = initialPosition;
output()->sendGeometry();
output()->sendDone();
});
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
QTRY_COMPARE(QGuiApplication::primaryScreen()->geometry(), QRect(QPoint(0, 0), QSize(1920, 1080)));
}
void tst_output::windowScreens()
{
QRasterWindow window;
window.resize(400, 320);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
QScreen *primaryScreen = QGuiApplication::screens().first();
QCOMPARE(window.screen(), primaryScreen);
exec([=] { add<Output>(); });
QTRY_COMPARE(QGuiApplication::screens().size(), 2);
QScreen *secondaryScreen = QGuiApplication::screens().at(1);
QVERIFY(secondaryScreen);
window.setScreen(secondaryScreen);
QCOMPARE(window.screen(), secondaryScreen);
exec([=] {
xdgToplevel()->surface()->sendEnter(output(0));
xdgToplevel()->surface()->sendEnter(output(1));
});
QTRY_COMPARE(window.screen(), primaryScreen);
exec([=] {
xdgToplevel()->surface()->sendLeave(output(0));
});
QTRY_COMPARE(window.screen(), secondaryScreen);
exec([=] {
remove(output(1));
});
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
QCOMPARE(window.screen(), primaryScreen);
}
void tst_output::removePrimaryScreen()
{
QRasterWindow window;
window.resize(400, 320);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
QScreen *primaryScreen = QGuiApplication::screens().first();
QCOMPARE(window.screen(), primaryScreen);
// Add a clone of the primary output
exec([&] { add<Output>(output()->m_data); });
QTRY_COMPARE(QGuiApplication::screens().size(), 2);
QTRY_COMPARE(QGuiApplication::primaryScreen()->virtualSiblings().size(), 2);
QScreen *secondaryScreen = QGuiApplication::screens().at(1);
QVERIFY(secondaryScreen);
exec([&] { remove(output()); });
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
exec([&] {
auto *surface = xdgToplevel()->surface();
pointer()->sendEnter(surface, {32, 32});
pointer()->sendFrame(client());
pointer()->sendButton(client(), BTN_LEFT, 1);
pointer()->sendFrame(client());
pointer()->sendButton(client(), BTN_LEFT, 0);
pointer()->sendFrame(client());
});
// Wait to make sure mouse events dont't cause a crash now that the screen has changed
xdgPingAndWaitForPong();
}
// QTBUG-72828
void tst_output::screenOrder()
{
exec([=] {
add<Output>()->m_data.model = "Screen 1";
add<Output>()->m_data.model = "Screen 2";
});
QTRY_COMPARE(QGuiApplication::screens().size(), 3);
const auto screens = QGuiApplication::screens();
QCOMPARE(screens[1]->model(), "Screen 1");
QCOMPARE(screens[2]->model(), "Screen 2");
exec([=] {
remove(output(2));
remove(output(1));
});
}
// This is different from tst_nooutput::noScreens because here we have a screen at platform
// integration initialization, which we then remove.
void tst_output::removeAllScreens()
{
QRasterWindow window1;
window1.resize(400, 320);
window1.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface(0) && xdgSurface(0)->m_committedConfigureSerial);
const QString wlOutputPrimaryScreenModel = QGuiApplication::primaryScreen()->model();
// Get screen info so we can restore it after
auto screenInfo = exec([=] { return output()->m_data; });
exec([=] { remove(output()); });
// Make sure the wl_output is actually removed before we continue
QTRY_VERIFY(!QGuiApplication::primaryScreen() || QGuiApplication::primaryScreen()->model() != wlOutputPrimaryScreenModel);
// Adding a window while there are no screens should also work
QRasterWindow window2;
window2.resize(400, 320);
window2.show();
exec([=] { add<Output>(screenInfo); });
// Things should be back to normal
QTRY_VERIFY(QGuiApplication::primaryScreen());
QTRY_COMPARE(QGuiApplication::primaryScreen()->model(), wlOutputPrimaryScreenModel);
// Test that we don't leave any fake screens around after we get a wl_output back.
QTRY_COMPARE(QGuiApplication::screens().size(), 1);
// Qt may choose to recreate/hide windows in response to changing screens, so give the client
// some time to potentially mess up before we verify that the windows are visible.
xdgPingAndWaitForPong();
// Windows should be visible after we've reconnected the screen
QCOMPOSITOR_TRY_VERIFY(xdgToplevel(0) && xdgToplevel(0)->m_xdgSurface->m_committedConfigureSerial);
QCOMPOSITOR_TRY_VERIFY(xdgToplevel(1) && xdgToplevel(1)->m_xdgSurface->m_committedConfigureSerial);
}
QCOMPOSITOR_TEST_MAIN(tst_output)
#include "tst_output.moc"