diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 2974d9664..25c059de9 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,312 +1,315 @@ add_definitions(-DKWIN_UNIT_TEST) remove_definitions(-DQT_USE_QSTRINGBUILDER) add_subdirectory(libkwineffects) add_subdirectory(libxrenderutils) add_subdirectory(integration) if (HAVE_INPUT) add_subdirectory(libinput) endif() add_subdirectory(tabbox) ######################################################## # Test ScreenPaintData ######################################################## set( testScreenPaintData_SRCS test_screen_paint_data.cpp ) add_executable(testScreenPaintData ${testScreenPaintData_SRCS}) target_link_libraries( testScreenPaintData kwineffects Qt5::Test Qt5::Widgets KF5::WindowSystem) add_test(kwin-testScreenPaintData testScreenPaintData) ecm_mark_as_test(testScreenPaintData) ######################################################## # Test WindowPaintData ######################################################## set( testWindowPaintData_SRCS test_window_paint_data.cpp ) add_executable(testWindowPaintData ${testWindowPaintData_SRCS}) target_link_libraries( testWindowPaintData kwineffects Qt5::Widgets Qt5::Test ) add_test(kwin-testWindowPaintData testWindowPaintData) ecm_mark_as_test(testWindowPaintData) ######################################################## # Test VirtualDesktopManager ######################################################## set( testVirtualDesktops_SRCS test_virtual_desktops.cpp ../virtualdesktops.cpp ) add_executable(testVirtualDesktops ${testVirtualDesktops_SRCS}) target_link_libraries( testVirtualDesktops Qt5::Test Qt5::Widgets KF5::I18n KF5::GlobalAccel KF5::ConfigCore KF5::WindowSystem ) add_test(kwin-testVirtualDesktops testVirtualDesktops) ecm_mark_as_test(testVirtualDesktops) ######################################################## # Test ClientMachine ######################################################## set( testClientMachine_SRCS test_client_machine.cpp ../client_machine.cpp ) add_executable( testClientMachine ${testClientMachine_SRCS} ) target_link_libraries( testClientMachine Qt5::Concurrent Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::XFIXES ${X11_X11_LIB} # to make jenkins happy ) add_test(kwin-testClientMachine testClientMachine) ecm_mark_as_test(testClientMachine) ######################################################## # Test XcbWrapper ######################################################## set( testXcbWrapper_SRCS test_xcb_wrapper.cpp ) add_executable( testXcbWrapper ${testXcbWrapper_SRCS} ) target_link_libraries( testXcbWrapper Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) add_test(kwin-testXcbWrapper testXcbWrapper) ecm_mark_as_test(testXcbWrapper) if (XCB_ICCCM_FOUND) add_executable( testXcbSizeHints test_xcb_size_hints.cpp ) target_link_libraries( testXcbSizeHints Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::WindowSystem XCB::XCB XCB::ICCCM ) add_test(kwin-testXcbSizeHints testXcbSizeHints) ecm_mark_as_test(testXcbSizeHints) endif() ######################################################## # Test XcbWindow ######################################################## set( testXcbWindow_SRCS test_xcb_window.cpp ) add_executable( testXcbWindow ${testXcbWindow_SRCS} ) target_link_libraries( testXcbWindow Qt5::Test Qt5::X11Extras Qt5::Widgets KF5::ConfigCore KF5::WindowSystem XCB::XCB ) add_test(kwin-testXcbWindow testXcbWindow) ecm_mark_as_test(testXcbWindow) ######################################################## # Test BuiltInEffectLoader ######################################################## set( testBuiltInEffectLoader_SRCS test_builtin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testBuiltInEffectLoader ${testBuiltInEffectLoader_SRCS}) target_link_libraries(testBuiltInEffectLoader Qt5::Concurrent Qt5::Test + Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testBuiltInEffectLoader testBuiltInEffectLoader) ecm_mark_as_test(testBuiltInEffectLoader) ######################################################## # Test ScriptedEffectLoader ######################################################## include_directories(${KWIN_SOURCE_DIR}) set( testScriptedEffectLoader_SRCS test_scripted_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ../scripting/scriptedeffect.cpp ../scripting/scriptingutils.cpp ../scripting/scripting_logging.cpp ) add_executable( testScriptedEffectLoader ${testScriptedEffectLoader_SRCS}) target_link_libraries(testScriptedEffectLoader Qt5::Concurrent Qt5::Script Qt5::Test + Qt5::X11Extras KF5::ConfigGui KF5::GlobalAccel KF5::I18n KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testScriptedEffectLoader testScriptedEffectLoader) ecm_mark_as_test(testScriptedEffectLoader) ######################################################## # Test PluginEffectLoader ######################################################## set( testPluginEffectLoader_SRCS test_plugin_effectloader.cpp mock_effectshandler.cpp ../effectloader.cpp ) add_executable( testPluginEffectLoader ${testPluginEffectLoader_SRCS}) target_link_libraries(testPluginEffectLoader Qt5::Concurrent Qt5::Test + Qt5::X11Extras KF5::Package kwineffects kwin4_effect_builtins ) add_test(kwin-testPluginEffectLoader testPluginEffectLoader) ecm_mark_as_test(testPluginEffectLoader) ######################################################## # FakeEffectPlugin ######################################################## add_library(fakeeffectplugin MODULE fakeeffectplugin.cpp) set_target_properties(fakeeffectplugin PROPERTIES PREFIX "") target_link_libraries(fakeeffectplugin kwineffects) ######################################################## # FakeEffectPlugin-Version ######################################################## add_library(effectversionplugin MODULE fakeeffectplugin_version.cpp) set_target_properties(effectversionplugin PROPERTIES PREFIX "") target_link_libraries(effectversionplugin kwineffects) ######################################################## # Test Screens ######################################################## set( testScreens_SRCS test_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../x11eventfilter.cpp ) kconfig_add_kcfg_files(testScreens_SRCS ../settings.kcfgc) add_executable( testScreens ${testScreens_SRCS}) target_include_directories(testScreens BEFORE PRIVATE ./) target_link_libraries(testScreens Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::ConfigGui KF5::WindowSystem ) add_test(kwin_testScreens testScreens) ecm_mark_as_test(testScreens) ######################################################## # Test XrandRScreens ######################################################## set( testXRandRScreens_SRCS test_xrandr_screens.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../screens.cpp ../plugins/platforms/x11/standalone/screens_xrandr.cpp ../xcbutils.cpp # init of extensions ../x11eventfilter.cpp ) kconfig_add_kcfg_files(testXRandRScreens_SRCS ../settings.kcfgc) add_executable( testXRandRScreens ${testXRandRScreens_SRCS} ) target_link_libraries( testXRandRScreens Qt5::Test Qt5::Gui KF5::ConfigCore KF5::ConfigGui KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) add_test(kwin-testXRandRScreens testXRandRScreens) ecm_mark_as_test(testXRandRScreens) ######################################################## # Test ScreenEdges ######################################################## set( testScreenEdges_SRCS test_screen_edges.cpp mock_abstract_client.cpp mock_client.cpp mock_screens.cpp mock_workspace.cpp ../atoms.cpp ../screens.cpp ../screenedge.cpp ../virtualdesktops.cpp ../xcbutils.cpp # init of extensions ../plugins/platforms/x11/standalone/edge.cpp ) kconfig_add_kcfg_files(testScreenEdges_SRCS ../settings.kcfgc) qt5_add_dbus_interface( testScreenEdges_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.freedesktop.ScreenSaver.xml screenlocker_interface) add_executable( testScreenEdges ${testScreenEdges_SRCS}) target_include_directories(testScreenEdges BEFORE PRIVATE ./) target_link_libraries(testScreenEdges Qt5::DBus Qt5::Test Qt5::X11Extras KF5::ConfigCore KF5::ConfigGui KF5::I18n KF5::GlobalAccel KF5::WindowSystem XCB::XCB XCB::RANDR XCB::XFIXES XCB::SYNC XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::SHM ) add_test(kwin_testScreenEdges testScreenEdges) ecm_mark_as_test(testScreenEdges) diff --git a/autotests/mock_effectshandler.h b/autotests/mock_effectshandler.h index 38558928b..86b259d98 100644 --- a/autotests/mock_effectshandler.h +++ b/autotests/mock_effectshandler.h @@ -1,249 +1,251 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef MOCK_EFFECTS_HANDLER_H #define MOCK_EFFECTS_HANDLER_H #include +#include + class MockEffectsHandler : public KWin::EffectsHandler { Q_OBJECT public: explicit MockEffectsHandler(KWin::CompositingType type); void activateWindow(KWin::EffectWindow *) override {} KWin::Effect *activeFullScreenEffect() const { return nullptr; } int activeScreen() const override { return 0; } KWin::EffectWindow *activeWindow() const override { return nullptr; } void addRepaint(const QRect &) override {} void addRepaint(const QRegion &) override {} void addRepaint(int, int, int, int) override {} void addRepaintFull() override {} double animationTimeFactor() const override { return 0; } xcb_atom_t announceSupportProperty(const QByteArray &, KWin::Effect *) override { return XCB_ATOM_NONE; } void buildQuads(KWin::EffectWindow *, KWin::WindowQuadList &) override {} QRect clientArea(KWin::clientAreaOption, const QPoint &, int) const override { return QRect(); } QRect clientArea(KWin::clientAreaOption, const KWin::EffectWindow *) const override { return QRect(); } QRect clientArea(KWin::clientAreaOption, int, int) const override { return QRect(); } void closeTabBox() override {} QString currentActivity() const override { return QString(); } int currentDesktop() const override { return 0; } int currentTabBoxDesktop() const override { return 0; } QList< int > currentTabBoxDesktopList() const override { return QList(); } KWin::EffectWindow *currentTabBoxWindow() const override { return nullptr; } KWin::EffectWindowList currentTabBoxWindowList() const override { return KWin::EffectWindowList(); } QPoint cursorPos() const override { return QPoint(); } bool decorationsHaveAlpha() const override { return false; } bool decorationSupportsBlurBehind() const override { return false; } void defineCursor(Qt::CursorShape) override {} int desktopAbove(int, bool) const override { return 0; } int desktopAtCoords(QPoint) const override { return 0; } int desktopBelow(int, bool) const override { return 0; } QPoint desktopCoords(int) const override { return QPoint(); } QPoint desktopGridCoords(int) const override { return QPoint(); } int desktopGridHeight() const override { return 0; } QSize desktopGridSize() const override { return QSize(); } int desktopGridWidth() const override { return 0; } QString desktopName(int) const override { return QString(); } int desktopToLeft(int, bool) const override { return 0; } int desktopToRight(int, bool) const override { return 0; } void doneOpenGLContextCurrent() override {} void drawWindow(KWin::EffectWindow *, int, QRegion, KWin::WindowPaintData &) override {} KWin::EffectFrame *effectFrame(KWin::EffectFrameStyle, bool, const QPoint &, Qt::Alignment) const override { return nullptr; } KWin::EffectWindow *findWindow(WId) const override { return nullptr; } KWin::EffectWindow *findWindow(KWayland::Server::SurfaceInterface *) const override { return nullptr; } void *getProxy(QString) override { return nullptr; } bool grabKeyboard(KWin::Effect *) override { return false; } bool hasDecorationShadows() const override { return false; } bool isScreenLocked() const override { return false; } QVariant kwinOption(KWin::KWinOption) override { return QVariant(); } bool makeOpenGLContextCurrent() override { return false; } void moveWindow(KWin::EffectWindow *, const QPoint &, bool, double) override {} KWin::WindowQuadType newWindowQuadType() override { return KWin::WindowQuadError; } int numberOfDesktops() const override { return 0; } int numScreens() const override { return 0; } bool optionRollOverDesktops() const override { return false; } void paintEffectFrame(KWin::EffectFrame *, QRegion, double, double) override {} void paintScreen(int, QRegion, KWin::ScreenPaintData &) override {} void paintWindow(KWin::EffectWindow *, int, QRegion, KWin::WindowPaintData &) override {} void postPaintScreen() override {} void postPaintWindow(KWin::EffectWindow *) override {} void prePaintScreen(KWin::ScreenPrePaintData &, int) override {} void prePaintWindow(KWin::EffectWindow *, KWin::WindowPrePaintData &, int) override {} QByteArray readRootProperty(long int, long int, int) const override { return QByteArray(); } void reconfigure() override {} void refTabBox() override {} void registerAxisShortcut(Qt::KeyboardModifiers, KWin::PointerAxisDirection, QAction *) override {} void registerGlobalShortcut(const QKeySequence &, QAction *) override {} void registerPointerShortcut(Qt::KeyboardModifiers, Qt::MouseButton, QAction *) override {} void reloadEffect(KWin::Effect *) override {} void removeSupportProperty(const QByteArray &, KWin::Effect *) override {} void reserveElectricBorder(KWin::ElectricBorder, KWin::Effect *) override {} QPainter *scenePainter() override { return nullptr; } int screenNumber(const QPoint &) const override { return 0; } void setActiveFullScreenEffect(KWin::Effect *) override {} void setCurrentDesktop(int) override {} void setElevatedWindow(KWin::EffectWindow *, bool) override {} void setNumberOfDesktops(int) override {} void setShowingDesktop(bool) override {} void setTabBoxDesktop(int) override {} void setTabBoxWindow(KWin::EffectWindow*) override {} KWin::EffectWindowList stackingOrder() const override { return KWin::EffectWindowList(); } void startMouseInterception(KWin::Effect *, Qt::CursorShape) override {} void startMousePolling() override {} void stopMouseInterception(KWin::Effect *) override {} void stopMousePolling() override {} void ungrabKeyboard() override {} void unrefTabBox() override {} void unreserveElectricBorder(KWin::ElectricBorder, KWin::Effect *) override {} QRect virtualScreenGeometry() const override { return QRect(); } QSize virtualScreenSize() const override { return QSize(); } void windowToDesktop(KWin::EffectWindow *, int) override {} void windowToScreen(KWin::EffectWindow *, int) override {} int workspaceHeight() const override { return 0; } int workspaceWidth() const override { return 0; } long unsigned int xrenderBufferPicture() override { return 0; } xcb_connection_t *xcbConnection() const override { return QX11Info::connection(); } xcb_window_t x11RootWindow() const override { return QX11Info::appRootWindow(); } KWayland::Server::Display *waylandDisplay() const override { return nullptr; } bool animationsSupported() const override { return m_animationsSuported; } void setAnimationsSupported(bool set) { m_animationsSuported = set; } KWin::PlatformCursorImage cursorImage() const override { return KWin::PlatformCursorImage(); } void hideCursor() override {} void showCursor() override {} private: bool m_animationsSuported = true; }; #endif diff --git a/autotests/test_client_machine.cpp b/autotests/test_client_machine.cpp index e86247370..1d97f6d97 100644 --- a/autotests/test_client_machine.cpp +++ b/autotests/test_client_machine.cpp @@ -1,156 +1,157 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "testutils.h" // KWin #include "../client_machine.h" #include "../xcbutils.h" // Qt #include #include +#include // xcb #include // system #include #include #include #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core") using namespace KWin; class TestClientMachine : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void hostName_data(); void hostName(); void emptyHostName(); private: void setClientMachineProperty(xcb_window_t window, const QByteArray &hostname); QByteArray m_hostName; QByteArray m_fqdn; }; void TestClientMachine::setClientMachineProperty(xcb_window_t window, const QByteArray &hostname) { xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, hostname.length(), hostname.constData()); } void TestClientMachine::initTestCase() { #ifdef HOST_NAME_MAX char hostnamebuf[HOST_NAME_MAX]; #else char hostnamebuf[256]; #endif if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { hostnamebuf[sizeof(hostnamebuf)-1] = 0; m_hostName = hostnamebuf; } addrinfo *res; addrinfo addressHints; memset(&addressHints, 0, sizeof(addressHints)); addressHints.ai_family = PF_UNSPEC; addressHints.ai_socktype = SOCK_STREAM; addressHints.ai_flags |= AI_CANONNAME; if (getaddrinfo(m_hostName.constData(), nullptr, &addressHints, &res) == 0) { if (res->ai_canonname) { m_fqdn = QByteArray(res->ai_canonname); } } freeaddrinfo(res); qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); } void TestClientMachine::cleanupTestCase() { } void TestClientMachine::hostName_data() { QTest::addColumn("hostName"); QTest::addColumn("expectedHost"); QTest::addColumn("local"); QTest::newRow("empty") << QByteArray() << QByteArray("localhost") << true; QTest::newRow("localhost") << QByteArray("localhost") << QByteArray("localhost") << true; QTest::newRow("hostname") << m_hostName << m_hostName << true; QTest::newRow("HOSTNAME") << m_hostName.toUpper() << m_hostName.toUpper() << true; QByteArray cutted(m_hostName); cutted.remove(0, 1); QTest::newRow("ostname") << cutted << cutted << false; QByteArray domain("random.name.not.exist.tld"); QTest::newRow("domain") << domain << domain << false; QTest::newRow("fqdn") << m_fqdn << m_fqdn << true; QTest::newRow("FQDN") << m_fqdn.toUpper() << m_fqdn.toUpper() << true; cutted = m_fqdn; cutted.remove(0, 1); QTest::newRow("qdn") << cutted << cutted << false; } void TestClientMachine::hostName() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = { true }; Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); QFETCH(QByteArray, hostName); QFETCH(bool, local); setClientMachineProperty(window, hostName); ClientMachine clientMachine; QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); clientMachine.resolve(window, XCB_WINDOW_NONE); QTEST(clientMachine.hostName(), "expectedHost"); int i=0; while (clientMachine.isResolving() && i++ < 50) { // name is being resolved in an external thread, so let's wait a little bit QTest::qWait(250); } QCOMPARE(clientMachine.isLocal(), local); QCOMPARE(spy.isEmpty(), !local); } void TestClientMachine::emptyHostName() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = { true }; Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); ClientMachine clientMachine; QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); clientMachine.resolve(window, XCB_WINDOW_NONE); QCOMPARE(clientMachine.hostName(), ClientMachine::localhost()); QVERIFY(clientMachine.isLocal()); // should be local QCOMPARE(spy.isEmpty(), false); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestClientMachine) #include "test_client_machine.moc" diff --git a/autotests/test_screen_edges.cpp b/autotests/test_screen_edges.cpp index 87017bd71..0595f53c9 100644 --- a/autotests/test_screen_edges.cpp +++ b/autotests/test_screen_edges.cpp @@ -1,867 +1,868 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // kwin #include "../atoms.h" #include "../cursor.h" #include "../input.h" #include "../main.h" #include "../screenedge.h" #include "../screens.h" #include "../utils.h" #include "../virtualdesktops.h" #include "../xcbutils.h" #include "mock_client.h" #include "mock_screens.h" #include "mock_workspace.h" #include "testutils.h" // Frameworks #include // Qt #include +#include // xcb #include Q_DECLARE_METATYPE(KWin::ElectricBorder) Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core") namespace KWin { Atoms* atoms; int screen_number = 0; Cursor *Cursor::s_self = nullptr; static QPoint s_cursorPos = QPoint(); QPoint Cursor::pos() { return s_cursorPos; } void Cursor::setPos(const QPoint &pos) { s_cursorPos = pos; } void Cursor::setPos(int x, int y) { setPos(QPoint(x, y)); } void Cursor::startMousePolling() { } void Cursor::stopMousePolling() { } InputRedirection *InputRedirection::s_self = nullptr; void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) Q_UNUSED(action) } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { Q_UNUSED(modifiers) Q_UNUSED(axis) Q_UNUSED(action) } void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) { Q_UNUSED(action) } void updateXTime() { } class TestObject : public QObject { Q_OBJECT public Q_SLOTS: bool callback(ElectricBorder border); Q_SIGNALS: void gotCallback(KWin::ElectricBorder); }; bool TestObject::callback(KWin::ElectricBorder border) { emit gotCallback(border); return true; } } class TestScreenEdges : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testInit(); void testCreatingInitialEdges(); void testCallback(); void testCallbackWithCheck(); void testPushBack_data(); void testPushBack(); void testFullScreenBlocking(); void testClientEdge(); }; void TestScreenEdges::initTestCase() { qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); KWin::atoms = new KWin::Atoms; qRegisterMetaType(); } void TestScreenEdges::cleanupTestCase() { delete KWin::atoms; } void TestScreenEdges::init() { using namespace KWin; new MockWorkspace; auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); Screens::create(); auto vd = VirtualDesktopManager::create(); vd->setConfig(config); vd->load(); auto s = ScreenEdges::create(); s->setConfig(config); } void TestScreenEdges::cleanup() { using namespace KWin; delete ScreenEdges::self(); delete VirtualDesktopManager::self(); delete Screens::self(); delete workspace(); } void TestScreenEdges::testInit() { using namespace KWin; auto s = ScreenEdges::self(); s->init(); QCOMPARE(s->isDesktopSwitching(), false); QCOMPARE(s->isDesktopSwitchingMovingClients(), false); QCOMPARE(s->timeThreshold(), 150); QCOMPARE(s->reActivationThreshold(), 350); QCOMPARE(s->cursorPushBackDistance(), QSize(1, 1)); QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone); QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (auto e : edges) { QVERIFY(!e->isReserved()); QVERIFY(e->inherits("KWin::WindowBasedEdge")); QVERIFY(!e->inherits("KWin::AreaBasedEdge")); QVERIFY(!e->client()); QVERIFY(!e->isApproaching()); } Edge *te = edges.at(0); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTopLeft); te = edges.at(1); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottomLeft); te = edges.at(2); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricLeft); te = edges.at(3); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(te->isTop()); QVERIFY(te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTopRight); te = edges.at(4); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottomRight); te = edges.at(5); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricRight); te = edges.at(6); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTop); te = edges.at(7); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottom); // we shouldn't have any x windows, though QCOMPARE(s->windows().size(), 0); } void TestScreenEdges::testCreatingInitialEdges() { using namespace KWin; auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorders", 2/*ElectricAlways*/); config->sync(); auto s = ScreenEdges::self(); s->setConfig(config); s->init(); // we don't have multiple desktops, so it's returning false QCOMPARE(s->isDesktopSwitching(), true); QCOMPARE(s->isDesktopSwitchingMovingClients(), true); QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone); QEXPECT_FAIL("", "needs fixing", Continue); QCOMPARE(s->windows().size(), 0); // set some reasonable virtual desktops config->group("Desktops").writeEntry("Number", 4); config->sync(); auto vd = VirtualDesktopManager::self(); vd->setConfig(config); vd->load(); QCOMPARE(vd->count(), 4u); QCOMPARE(vd->grid().width(), 2); QCOMPARE(vd->grid().height(), 2); // approach windows for edges not created as screen too small s->updateLayout(); auto edgeWindows = s->windows(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 6, 1)) if (!Xcb::Extensions::self()->isRandrAvailable()) { QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort); } #endif #endif QCOMPARE(edgeWindows.size(), 12); auto testWindowGeometry = [&](int index) { Xcb::WindowGeometry geo(edgeWindows[index]); return geo.rect(); }; QRect sg = screens()->geometry(); const int co = s->cornerOffset(); QList expectedGeometries{ QRect(0, 0, 1, 1), QRect(0, 0, co, co), QRect(0, sg.bottom(), 1, 1), QRect(0, sg.height() - co, co, co), QRect(0, co, 1, sg.height() - co*2), // QRect(0, co * 2 + 1, co, sg.height() - co*4), QRect(sg.right(), 0, 1, 1), QRect(sg.right() - co + 1, 0, co, co), QRect(sg.right(), sg.bottom(), 1, 1), QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co), QRect(sg.right(), co, 1, sg.height() - co*2), // QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4), QRect(co, 0, sg.width() - co * 2, 1), // QRect(co * 2, 0, sg.width() - co * 4, co), QRect(co, sg.bottom(), sg.width() - co * 2, 1), // QRect(co * 2, sg.height() - co, sg.width() - co * 4, co) }; for (int i = 0; i < 12; ++i) { QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i)); } QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (auto e : edges) { QVERIFY(e->isReserved()); } static_cast(screens())->setGeometries(QList{QRect{0, 0, 1024, 768}}); QSignalSpy changedSpy(screens(), SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // first is before it's updated QVERIFY(changedSpy.wait()); // second is after it's updated QVERIFY(changedSpy.wait()); // let's update the layout and verify that we have edges s->recreateEdges(); edgeWindows = s->windows(); QCOMPARE(edgeWindows.size(), 16); sg = screens()->geometry(); expectedGeometries = QList{ QRect(0, 0, 1, 1), QRect(0, 0, co, co), QRect(0, sg.bottom(), 1, 1), QRect(0, sg.height() - co, co, co), QRect(0, co, 1, sg.height() - co*2), QRect(0, co * 2 + 1, co, sg.height() - co*4), QRect(sg.right(), 0, 1, 1), QRect(sg.right() - co + 1, 0, co, co), QRect(sg.right(), sg.bottom(), 1, 1), QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co), QRect(sg.right(), co, 1, sg.height() - co*2), QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4), QRect(co, 0, sg.width() - co * 2, 1), QRect(co * 2, 0, sg.width() - co * 4, co), QRect(co, sg.bottom(), sg.width() - co * 2, 1), QRect(co * 2, sg.height() - co, sg.width() - co * 4, co) }; for (int i = 0; i < 16; ++i) { QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i)); } // disable desktop switching again config->group("Windows").writeEntry("ElectricBorders", 1/*ElectricMoveOnly*/); s->reconfigure(); QCOMPARE(s->isDesktopSwitching(), false); QCOMPARE(s->isDesktopSwitchingMovingClients(), true); QCOMPARE(s->windows().size(), 0); edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (int i = 0; i < 8; ++i) { auto e = edges.at(i); QVERIFY(!e->isReserved()); QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1)); } } void TestScreenEdges::testCallback() { using namespace KWin; MockWorkspace ws; static_cast(screens())->setGeometries(QList{QRect{0, 0, 1024, 768}, QRect{200, 768, 1024, 768}}); QSignalSpy changedSpy(screens(), SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // first is before it's updated QVERIFY(changedSpy.wait()); // second is after it's updated QVERIFY(changedSpy.wait()); auto s = ScreenEdges::self(); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(ElectricLeft, &callback, "callback"); s->reserve(ElectricTopLeft, &callback, "callback"); s->reserve(ElectricTop, &callback, "callback"); s->reserve(ElectricTopRight, &callback, "callback"); s->reserve(ElectricRight, &callback, "callback"); s->reserve(ElectricBottomRight, &callback, "callback"); s->reserve(ElectricBottom, &callback, "callback"); s->reserve(ElectricBottomLeft, &callback, "callback"); QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 10); for (auto e: edges) { QVERIFY(e->isReserved()); } auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) { return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768; }); #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 6, 1)) if (!Xcb::Extensions::self()->isRandrAvailable()) { QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort); } #endif #endif QVERIFY(it != edges.constEnd()); xcb_enter_notify_event_t event; auto setPos = [&event] (const QPoint &pos) { Cursor::setPos(pos); event.root_x = pos.x(); event.root_y = pos.y(); event.event_x = pos.x(); event.event_y = pos.y(); }; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = (*it)->window(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); setPos(QPoint(0, 50)); QVERIFY(s->isEntered(&event)); // doesn't trigger as the edge was not triggered yet QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // test doesn't trigger due to too much offset QTest::qWait(160); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // doesn't trigger as we are waiting too long already QTest::qWait(200); setPos(QPoint(0, 101)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 101)); // doesn't activate as we are waiting too short QTest::qWait(50); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // and this one triggers QTest::qWait(110); setPos(QPoint(0, 101)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 101)); // now let's try to trigger again QTest::qWait(351); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 1); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // it's still under the reactivation QTest::qWait(50); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 1); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // now it should trigger again QTest::qWait(250); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 2); QCOMPARE(spy.first().first().value(), ElectricLeft); QCOMPARE(spy.last().first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // let's disable pushback auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 0); config->sync(); s->setConfig(config); s->reconfigure(); // it should trigger directly QTest::qWait(350); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 3); QCOMPARE(spy.at(0).first().value(), ElectricLeft); QCOMPARE(spy.at(1).first().value(), ElectricLeft); QCOMPARE(spy.at(2).first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(0, 100)); // now let's unreserve again s->unreserve(ElectricTopLeft, &callback); s->unreserve(ElectricTop, &callback); s->unreserve(ElectricTopRight, &callback); s->unreserve(ElectricRight, &callback); s->unreserve(ElectricBottomRight, &callback); s->unreserve(ElectricBottom, &callback); s->unreserve(ElectricBottomLeft, &callback); s->unreserve(ElectricLeft, &callback); for (auto e: s->findChildren(QString(), Qt::FindDirectChildrenOnly)) { QVERIFY(!e->isReserved()); } } void TestScreenEdges::testCallbackWithCheck() { using namespace KWin; auto s = ScreenEdges::self(); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(ElectricLeft, &callback, "callback"); // check activating a different edge doesn't do anything s->check(QPoint(50, 0), QDateTime::currentDateTime(), true); QVERIFY(spy.isEmpty()); // try a direct activate without pushback Cursor::setPos(0, 50); s->check(QPoint(0, 50), QDateTime::currentDateTime(), true); QCOMPARE(spy.count(), 1); QEXPECT_FAIL("", "Argument says force no pushback, but it gets pushed back. Needs investigation", Continue); QCOMPARE(Cursor::pos(), QPoint(0, 50)); // use a different edge, this time with pushback s->reserve(KWin::ElectricRight, &callback, "callback"); Cursor::setPos(99, 50); s->check(QPoint(99, 50), QDateTime::currentDateTime()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.last().first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(98, 50)); // and trigger it again QTest::qWait(160); Cursor::setPos(99, 50); s->check(QPoint(99, 50), QDateTime::currentDateTime()); QCOMPARE(spy.count(), 2); QCOMPARE(spy.last().first().value(), ElectricRight); QCOMPARE(Cursor::pos(), QPoint(98, 50)); } void TestScreenEdges::testPushBack_data() { QTest::addColumn("border"); QTest::addColumn("pushback"); QTest::addColumn("trigger"); QTest::addColumn("expected"); QTest::newRow("topleft-3") << KWin::ElectricTopLeft << 3 << QPoint(0, 0) << QPoint(3, 3); QTest::newRow("top-5") << KWin::ElectricTop << 5 << QPoint(50, 0) << QPoint(50, 5); QTest::newRow("toprigth-2") << KWin::ElectricTopRight << 2 << QPoint(99, 0) << QPoint(97, 2); QTest::newRow("right-10") << KWin::ElectricRight << 10 << QPoint(99, 50) << QPoint(89, 50); QTest::newRow("bottomright-5") << KWin::ElectricBottomRight << 5 << QPoint(99, 99) << QPoint(94, 94); QTest::newRow("bottom-10") << KWin::ElectricBottom << 10 << QPoint(50, 99) << QPoint(50, 89); QTest::newRow("bottomleft-3") << KWin::ElectricBottomLeft << 3 << QPoint(0, 99) << QPoint(3, 96); QTest::newRow("left-10") << KWin::ElectricLeft << 10 << QPoint(0, 50) << QPoint(10, 50); QTest::newRow("invalid") << KWin::ElectricLeft << 10 << QPoint(50, 0) << QPoint(50, 0); } void TestScreenEdges::testPushBack() { using namespace KWin; QFETCH(int, pushback); auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", pushback); config->sync(); // TODO: add screens auto s = ScreenEdges::self(); s->setConfig(config); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); QFETCH(ElectricBorder, border); s->reserve(border, &callback, "callback"); QFETCH(QPoint, trigger); Cursor::setPos(trigger); xcb_enter_notify_event_t event; event.root_x = trigger.x(); event.root_y = trigger.y(); event.event_x = trigger.x(); event.event_y = trigger.y(); event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QTEST(Cursor::pos(), "expected"); // do the same without the event, but the check method Cursor::setPos(trigger); s->check(trigger, QDateTime::currentDateTime()); QVERIFY(spy.isEmpty()); QTEST(Cursor::pos(), "expected"); } void TestScreenEdges::testFullScreenBlocking() { using namespace KWin; MockWorkspace ws; Client client(&ws); auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 1); config->sync(); auto s = ScreenEdges::self(); s->setConfig(config); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(KWin::ElectricLeft, &callback, "callback"); s->reserve(KWin::ElectricBottomRight, &callback, "callback"); // currently there is no active client yet, so check blocking shouldn't do anything emit s->checkBlocking(); xcb_enter_notify_event_t event; Cursor::setPos(0, 50); event.root_x = 0; event.root_y = 50; event.event_x = 0; event.event_y = 50; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); client.setGeometry(screens()->geometry()); client.setActive(true); client.setFullScreen(true); ws.setActiveClient(&client); emit s->checkBlocking(); // the signal doesn't trigger for corners, let's go over all windows just to be sure that it doesn't call for corners for (auto e: s->findChildren()) { e->checkBlocking(); } // calling again should not trigger QTest::qWait(160); Cursor::setPos(0, 50); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and no pushback QCOMPARE(Cursor::pos(), QPoint(0, 50)); // let's make the client not fullscreen, which should trigger client.setFullScreen(false); emit s->checkBlocking(); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // let's make the client fullscreen again, but with a geometry not intersecting the left edge QTest::qWait(351); client.setFullScreen(true); client.setGeometry(client.geometry().translated(10, 0)); emit s->checkBlocking(); spy.clear(); Cursor::setPos(0, 50); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and a pushback QCOMPARE(Cursor::pos(), QPoint(1, 50)); // just to be sure, let's set geometry back client.setGeometry(screens()->geometry()); emit s->checkBlocking(); Cursor::setPos(0, 50); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and no pushback QCOMPARE(Cursor::pos(), QPoint(0, 50)); // the corner should always trigger s->unreserve(KWin::ElectricLeft, &callback); event.event_x = 99; event.event_y = 99; event.root_x = 99; event.root_y = 99; event.event = s->windows().first(); event.time = QDateTime::currentMSecsSinceEpoch(); Cursor::setPos(99, 99); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and pushback QCOMPARE(Cursor::pos(), QPoint(98, 98)); QTest::qWait(160); event.time = QDateTime::currentMSecsSinceEpoch(); Cursor::setPos(99, 99); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); } void TestScreenEdges::testClientEdge() { using namespace KWin; Client client(workspace()); client.setGeometry(QRect(10, 50, 10, 50)); auto s = ScreenEdges::self(); s->init(); s->reserve(&client, KWin::ElectricBottom); // let's set the client to be hidden client.setHiddenInternal(true); QPointer edge = s->findChildren().last(); s->reserve(&client, KWin::ElectricBottom); QCOMPARE(edge.data(), s->findChildren().last()); QCOMPARE(edge->isReserved(), true); //remove old reserves and resize to be in the middle of the screen s->reserve(&client, KWin::ElectricNone); client.setGeometry(QRect(2, 2, 20, 20)); // for none of the edges it should be able to be set for (int i = 0; i < ELECTRIC_COUNT; ++i) { client.setHiddenInternal(true); s->reserve(&client, static_cast(i)); QCOMPARE(client.isHiddenInternal(), false); } // now let's try to set it and activate it client.setGeometry(screens()->geometry()); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricLeft); QCOMPARE(client.isHiddenInternal(), true); xcb_enter_notify_event_t event; Cursor::setPos(0, 50); event.root_x = 0; event.root_y = 50; event.event_x = 0; event.event_y = 50; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); // autohiding panels shall activate instantly QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // now let's reserve the client for each of the edges, in the end for the right one client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricTop); s->reserve(&client, KWin::ElectricBottom); QCOMPARE(client.isHiddenInternal(), true); // corners shouldn't get reserved s->reserve(&client, KWin::ElectricTopLeft); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricTopRight); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricBottomRight); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricBottomLeft); QCOMPARE(client.isHiddenInternal(), false); // now finally reserve on right one client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricRight); QCOMPARE(client.isHiddenInternal(), true); // now let's emulate the removal of a Client through Workspace emit workspace()->clientRemoved(&client); for (auto e : s->findChildren()) { QVERIFY(!e->client()); } QCOMPARE(client.isHiddenInternal(), true); // now let's try to trigger the client showing with the check method instead of enter notify s->reserve(&client, KWin::ElectricTop); QCOMPARE(client.isHiddenInternal(), true); Cursor::setPos(50, 0); s->check(QPoint(50, 0), QDateTime::currentDateTime()); QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(50, 1)); // unreserve by setting to none edge s->reserve(&client, KWin::ElectricNone); // check on previous edge again, should fail client.setHiddenInternal(true); Cursor::setPos(50, 0); s->check(QPoint(50, 0), QDateTime::currentDateTime()); QCOMPARE(client.isHiddenInternal(), true); QCOMPARE(Cursor::pos(), QPoint(50, 0)); // set to windows can cover client.setGeometry(screens()->geometry()); client.setHiddenInternal(false); client.setKeepBelow(true); s->reserve(&client, KWin::ElectricLeft); QCOMPARE(client.keepBelow(), true); QCOMPARE(client.isHiddenInternal(), false); xcb_enter_notify_event_t event2; Cursor::setPos(0, 50); event2.root_x = 0; event2.root_y = 50; event2.event_x = 0; event2.event_y = 50; event2.root = XCB_WINDOW_NONE; event2.child = XCB_WINDOW_NONE; event2.event = s->windows().first(); event2.same_screen_focus = 1; event2.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event2)); QCOMPARE(client.keepBelow(), false); QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(1, 50)); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestScreenEdges) #include "test_screen_edges.moc" diff --git a/autotests/test_xcb_size_hints.cpp b/autotests/test_xcb_size_hints.cpp index e759943a5..59b6bfc6c 100644 --- a/autotests/test_xcb_size_hints.cpp +++ b/autotests/test_xcb_size_hints.cpp @@ -1,376 +1,377 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "testutils.h" // KWin #include "../xcbutils.h" // Qt #include #include +#include #include // xcb #include #include using namespace KWin; using namespace KWin::Xcb; class TestXcbSizeHints : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testSizeHints_data(); void testSizeHints(); void testSizeHintsEmpty(); void testSizeHintsNotSet(); void geometryHintsBeforeInit(); void geometryHintsBeforeRead(); private: Window m_testWindow; }; void TestXcbSizeHints::initTestCase() { qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); } void TestXcbSizeHints::init() { const uint32_t values[] = { true }; m_testWindow.create(QRect(0, 0, 10, 10), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); QVERIFY(m_testWindow.isValid()); } void TestXcbSizeHints::cleanup() { m_testWindow.reset(); } void TestXcbSizeHints::testSizeHints_data() { // set QTest::addColumn("userPos"); QTest::addColumn("userSize"); QTest::addColumn("minSize"); QTest::addColumn("maxSize"); QTest::addColumn("resizeInc"); QTest::addColumn("minAspect"); QTest::addColumn("maxAspect"); QTest::addColumn("baseSize"); QTest::addColumn("gravity"); // read for SizeHints QTest::addColumn("expectedFlags"); QTest::addColumn("expectedPad0"); QTest::addColumn("expectedPad1"); QTest::addColumn("expectedPad2"); QTest::addColumn("expectedPad3"); QTest::addColumn("expectedMinWidth"); QTest::addColumn("expectedMinHeight"); QTest::addColumn("expectedMaxWidth"); QTest::addColumn("expectedMaxHeight"); QTest::addColumn("expectedWidthInc"); QTest::addColumn("expectedHeightInc"); QTest::addColumn("expectedMinAspectNum"); QTest::addColumn("expectedMinAspectDen"); QTest::addColumn("expectedMaxAspectNum"); QTest::addColumn("expectedMaxAspectDen"); QTest::addColumn("expectedBaseWidth"); QTest::addColumn("expectedBaseHeight"); // read for GeometryHints QTest::addColumn("expectedMinSize"); QTest::addColumn("expectedMaxSize"); QTest::addColumn("expectedResizeIncrements"); QTest::addColumn("expectedMinAspect"); QTest::addColumn("expectedMaxAspect"); QTest::addColumn("expectedBaseSize"); QTest::addColumn("expectedGravity"); QTest::newRow("userPos") << QPoint(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << 0 << 1 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("userSize") << QPoint() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << 0 << 2 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("minSize") << QPoint() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << 0 << 16 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(1, 2) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("maxSize") << QPoint() << QSize() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << 0 << 32 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(1, 2) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("maxSize0") << QPoint() << QSize() << QSize() << QSize(0, 0) << QSize() << QSize() << QSize() << QSize() << 0 << 32 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(1, 1) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("min/maxSize") << QPoint() << QSize() << QSize(1, 2) << QSize(3, 4) << QSize() << QSize() << QSize() << QSize() << 0 << 48 << 0 << 0 << 0 << 0 << 1 << 2 << 3 << 4 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(1, 2) << QSize(3, 4) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("resizeInc") << QPoint() << QSize() << QSize() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << 0 << 64 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 2) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("resizeInc0") << QPoint() << QSize() << QSize() << QSize() << QSize(0, 0) << QSize() << QSize() << QSize() << 0 << 64 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("aspect") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize(1, 2) << QSize(3, 4) << QSize() << 0 << 128 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 3 << 4 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, 2) << QSize(3, 4) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("aspectDivision0") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize(1, 0) << QSize(3, 0) << QSize() << 0 << 128 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 0 << 3 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, 1) << QSize(3, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("baseSize") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize(1, 2) << 0 << 256 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << QSize(1, 2) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(1, 2) << qint32(XCB_GRAVITY_NORTH_WEST); QTest::newRow("gravity") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << qint32(XCB_GRAVITY_STATIC) << 512 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_STATIC); QTest::newRow("all") << QPoint(1, 2) << QSize(3, 4) << QSize(5, 6) << QSize(7, 8) << QSize(9, 10) << QSize(11, 12) << QSize(13, 14) << QSize(15, 16) << 1 << 1011 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16 << QSize(5, 6) << QSize(7, 8) << QSize(9, 10) << QSize(11, 12) << QSize(13, 14) << QSize(15, 16) << qint32(XCB_GRAVITY_NORTH_WEST); } void TestXcbSizeHints::testSizeHints() { xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); QFETCH(QPoint, userPos); if (!userPos.isNull()) { xcb_icccm_size_hints_set_position(&hints, 1, userPos.x(), userPos.y()); } QFETCH(QSize, userSize); if (userSize.isValid()) { xcb_icccm_size_hints_set_size(&hints, 1, userSize.width(), userSize.height()); } QFETCH(QSize, minSize); if (minSize.isValid()) { xcb_icccm_size_hints_set_min_size(&hints, minSize.width(), minSize.height()); } QFETCH(QSize, maxSize); if (maxSize.isValid()) { xcb_icccm_size_hints_set_max_size(&hints, maxSize.width(), maxSize.height()); } QFETCH(QSize, resizeInc); if (resizeInc.isValid()) { xcb_icccm_size_hints_set_resize_inc(&hints, resizeInc.width(), resizeInc.height()); } QFETCH(QSize, minAspect); QFETCH(QSize, maxAspect); if (minAspect.isValid() && maxAspect.isValid()) { xcb_icccm_size_hints_set_aspect(&hints, minAspect.width(), minAspect.height(), maxAspect.width(), maxAspect.height()); } QFETCH(QSize, baseSize); if (baseSize.isValid()) { xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height()); } QFETCH(qint32, gravity); if (gravity != 0) { xcb_icccm_size_hints_set_win_gravity(&hints, (xcb_gravity_t)gravity); } xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &hints); xcb_flush(QX11Info::connection()); GeometryHints geoHints; geoHints.init(m_testWindow); geoHints.read(); QCOMPARE(geoHints.hasAspect(), minAspect.isValid() && maxAspect.isValid()); QCOMPARE(geoHints.hasBaseSize(), baseSize.isValid()); QCOMPARE(geoHints.hasMaxSize(), maxSize.isValid()); QCOMPARE(geoHints.hasMinSize(), minSize.isValid()); QCOMPARE(geoHints.hasPosition(), !userPos.isNull()); QCOMPARE(geoHints.hasResizeIncrements(), resizeInc.isValid()); QCOMPARE(geoHints.hasSize(), userSize.isValid()); QCOMPARE(geoHints.hasWindowGravity(), gravity != 0); QTEST(geoHints.baseSize(), "expectedBaseSize"); QTEST(geoHints.maxAspect(), "expectedMaxAspect"); QTEST(geoHints.maxSize(), "expectedMaxSize"); QTEST(geoHints.minAspect(), "expectedMinAspect"); QTEST(geoHints.minSize(), "expectedMinSize"); QTEST(geoHints.resizeIncrements(), "expectedResizeIncrements"); QTEST(qint32(geoHints.windowGravity()), "expectedGravity"); auto sizeHints = geoHints.m_sizeHints; QVERIFY(sizeHints); QTEST(sizeHints->flags, "expectedFlags"); QTEST(sizeHints->pad[0], "expectedPad0"); QTEST(sizeHints->pad[1], "expectedPad1"); QTEST(sizeHints->pad[2], "expectedPad2"); QTEST(sizeHints->pad[3], "expectedPad3"); QTEST(sizeHints->minWidth, "expectedMinWidth"); QTEST(sizeHints->minHeight, "expectedMinHeight"); QTEST(sizeHints->maxWidth, "expectedMaxWidth"); QTEST(sizeHints->maxHeight, "expectedMaxHeight"); QTEST(sizeHints->widthInc, "expectedWidthInc"); QTEST(sizeHints->heightInc, "expectedHeightInc"); QTEST(sizeHints->minAspect[0], "expectedMinAspectNum"); QTEST(sizeHints->minAspect[1], "expectedMinAspectDen"); QTEST(sizeHints->maxAspect[0], "expectedMaxAspectNum"); QTEST(sizeHints->maxAspect[1], "expectedMaxAspectDen"); QTEST(sizeHints->baseWidth, "expectedBaseWidth"); QTEST(sizeHints->baseHeight, "expectedBaseHeight"); QCOMPARE(sizeHints->winGravity, gravity); // copy GeometryHints::NormalHints::SizeHints sizeHints2 = *sizeHints; QTEST(sizeHints2.flags, "expectedFlags"); QTEST(sizeHints2.pad[0], "expectedPad0"); QTEST(sizeHints2.pad[1], "expectedPad1"); QTEST(sizeHints2.pad[2], "expectedPad2"); QTEST(sizeHints2.pad[3], "expectedPad3"); QTEST(sizeHints2.minWidth, "expectedMinWidth"); QTEST(sizeHints2.minHeight, "expectedMinHeight"); QTEST(sizeHints2.maxWidth, "expectedMaxWidth"); QTEST(sizeHints2.maxHeight, "expectedMaxHeight"); QTEST(sizeHints2.widthInc, "expectedWidthInc"); QTEST(sizeHints2.heightInc, "expectedHeightInc"); QTEST(sizeHints2.minAspect[0], "expectedMinAspectNum"); QTEST(sizeHints2.minAspect[1], "expectedMinAspectDen"); QTEST(sizeHints2.maxAspect[0], "expectedMaxAspectNum"); QTEST(sizeHints2.maxAspect[1], "expectedMaxAspectDen"); QTEST(sizeHints2.baseWidth, "expectedBaseWidth"); QTEST(sizeHints2.baseHeight, "expectedBaseHeight"); QCOMPARE(sizeHints2.winGravity, gravity); } void TestXcbSizeHints::testSizeHintsEmpty() { xcb_size_hints_t xcbHints; memset(&xcbHints, 0, sizeof(xcbHints)); xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &xcbHints); xcb_flush(QX11Info::connection()); GeometryHints hints; hints.init(m_testWindow); hints.read(); QVERIFY(!hints.hasAspect()); QVERIFY(!hints.hasBaseSize()); QVERIFY(!hints.hasMaxSize()); QVERIFY(!hints.hasMinSize()); QVERIFY(!hints.hasPosition()); QVERIFY(!hints.hasResizeIncrements()); QVERIFY(!hints.hasSize()); QVERIFY(!hints.hasWindowGravity()); QCOMPARE(hints.baseSize(), QSize(0, 0)); QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); QCOMPARE(hints.minSize(), QSize(0, 0)); QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); auto sizeHints = hints.m_sizeHints; QVERIFY(sizeHints); QCOMPARE(sizeHints->flags, 0); QCOMPARE(sizeHints->pad[0], 0); QCOMPARE(sizeHints->pad[1], 0); QCOMPARE(sizeHints->pad[2], 0); QCOMPARE(sizeHints->pad[3], 0); QCOMPARE(sizeHints->minWidth, 0); QCOMPARE(sizeHints->minHeight, 0); QCOMPARE(sizeHints->maxWidth, 0); QCOMPARE(sizeHints->maxHeight, 0); QCOMPARE(sizeHints->widthInc, 0); QCOMPARE(sizeHints->heightInc, 0); QCOMPARE(sizeHints->minAspect[0], 0); QCOMPARE(sizeHints->minAspect[1], 0); QCOMPARE(sizeHints->maxAspect[0], 0); QCOMPARE(sizeHints->maxAspect[1], 0); QCOMPARE(sizeHints->baseWidth, 0); QCOMPARE(sizeHints->baseHeight, 0); QCOMPARE(sizeHints->winGravity, 0); } void TestXcbSizeHints::testSizeHintsNotSet() { GeometryHints hints; hints.init(m_testWindow); hints.read(); QVERIFY(!hints.m_sizeHints); QVERIFY(!hints.hasAspect()); QVERIFY(!hints.hasBaseSize()); QVERIFY(!hints.hasMaxSize()); QVERIFY(!hints.hasMinSize()); QVERIFY(!hints.hasPosition()); QVERIFY(!hints.hasResizeIncrements()); QVERIFY(!hints.hasSize()); QVERIFY(!hints.hasWindowGravity()); QCOMPARE(hints.baseSize(), QSize(0, 0)); QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); QCOMPARE(hints.minSize(), QSize(0, 0)); QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); } void TestXcbSizeHints::geometryHintsBeforeInit() { GeometryHints hints; QVERIFY(!hints.hasAspect()); QVERIFY(!hints.hasBaseSize()); QVERIFY(!hints.hasMaxSize()); QVERIFY(!hints.hasMinSize()); QVERIFY(!hints.hasPosition()); QVERIFY(!hints.hasResizeIncrements()); QVERIFY(!hints.hasSize()); QVERIFY(!hints.hasWindowGravity()); QCOMPARE(hints.baseSize(), QSize(0, 0)); QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); QCOMPARE(hints.minSize(), QSize(0, 0)); QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); } void TestXcbSizeHints::geometryHintsBeforeRead() { xcb_size_hints_t xcbHints; memset(&xcbHints, 0, sizeof(xcbHints)); xcb_icccm_size_hints_set_position(&xcbHints, 1, 1, 2); xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &xcbHints); xcb_flush(QX11Info::connection()); GeometryHints hints; hints.init(m_testWindow); QVERIFY(!hints.hasAspect()); QVERIFY(!hints.hasBaseSize()); QVERIFY(!hints.hasMaxSize()); QVERIFY(!hints.hasMinSize()); QVERIFY(!hints.hasPosition()); QVERIFY(!hints.hasResizeIncrements()); QVERIFY(!hints.hasSize()); QVERIFY(!hints.hasWindowGravity()); QCOMPARE(hints.baseSize(), QSize(0, 0)); QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); QCOMPARE(hints.minSize(), QSize(0, 0)); QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestXcbSizeHints) #include "test_xcb_size_hints.moc" diff --git a/autotests/test_xcb_window.cpp b/autotests/test_xcb_window.cpp index 339be675a..ee703f3a8 100644 --- a/autotests/test_xcb_window.cpp +++ b/autotests/test_xcb_window.cpp @@ -1,212 +1,213 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "testutils.h" // KWin #include "../xcbutils.h" // Qt #include #include +#include // xcb #include using namespace KWin; class TestXcbWindow : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void defaultCtor(); void ctor(); void classCtor(); void create(); void mapUnmap(); void geometry(); void destroy(); void destroyNotManaged(); }; void TestXcbWindow::initTestCase() { qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); } void TestXcbWindow::defaultCtor() { Xcb::Window window; QCOMPARE(window.isValid(), false); xcb_window_t wId = window; QCOMPARE(wId, noneWindow()); xcb_window_t nativeWindow = createWindow(); Xcb::Window window2(nativeWindow); QCOMPARE(window2.isValid(), true); wId = window2; QCOMPARE(wId, nativeWindow); } void TestXcbWindow::ctor() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; Xcb::Window window(geometry, XCB_CW_OVERRIDE_REDIRECT, values); QCOMPARE(window.isValid(), true); QVERIFY(window != XCB_WINDOW_NONE); Xcb::WindowGeometry windowGeometry(window); QCOMPARE(windowGeometry.isNull(), false); QCOMPARE(windowGeometry.rect(), geometry); } void TestXcbWindow::classCtor() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); QCOMPARE(window.isValid(), true); QVERIFY(window != XCB_WINDOW_NONE); Xcb::WindowGeometry windowGeometry(window); QCOMPARE(windowGeometry.isNull(), false); QCOMPARE(windowGeometry.rect(), geometry); Xcb::WindowAttributes attribs(window); QCOMPARE(attribs.isNull(), false); QVERIFY(attribs->_class == XCB_WINDOW_CLASS_INPUT_ONLY); } void TestXcbWindow::create() { Xcb::Window window; QCOMPARE(window.isValid(), false); xcb_window_t wId = window; QCOMPARE(wId, noneWindow()); const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; window.create(geometry, XCB_CW_OVERRIDE_REDIRECT, values); QCOMPARE(window.isValid(), true); QVERIFY(window != XCB_WINDOW_NONE); // and reset again window.reset(); QCOMPARE(window.isValid(), false); QVERIFY(window == XCB_WINDOW_NONE); } void TestXcbWindow::mapUnmap() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); Xcb::WindowAttributes attribs(window); QCOMPARE(attribs.isNull(), false); QVERIFY(attribs->map_state == XCB_MAP_STATE_UNMAPPED); window.map(); Xcb::WindowAttributes attribs2(window); QCOMPARE(attribs2.isNull(), false); QVERIFY(attribs2->map_state != XCB_MAP_STATE_UNMAPPED); window.unmap(); Xcb::WindowAttributes attribs3(window); QCOMPARE(attribs3.isNull(), false); QVERIFY(attribs3->map_state == XCB_MAP_STATE_UNMAPPED); // map, unmap shouldn't fail for an invalid window, it's just ignored window.reset(); window.map(); window.unmap(); } void TestXcbWindow::geometry() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); Xcb::WindowGeometry windowGeometry(window); QCOMPARE(windowGeometry.isNull(), false); QCOMPARE(windowGeometry.rect(), geometry); const QRect geometry2(10, 20, 100, 200); window.setGeometry(geometry2); Xcb::WindowGeometry windowGeometry2(window); QCOMPARE(windowGeometry2.isNull(), false); QCOMPARE(windowGeometry2.rect(), geometry2); // setting a geometry on an invalid window should be ignored window.reset(); window.setGeometry(geometry2); Xcb::WindowGeometry windowGeometry3(window); QCOMPARE(windowGeometry3.isNull(), true); } void TestXcbWindow::destroy() { const QRect geometry(0, 0, 10, 10); const uint32_t values[] = {true}; Xcb::Window window(geometry, XCB_CW_OVERRIDE_REDIRECT, values); QCOMPARE(window.isValid(), true); xcb_window_t wId = window; window.create(geometry, XCB_CW_OVERRIDE_REDIRECT, values); // wId should now be invalid xcb_generic_error_t *error = nullptr; ScopedCPointer attribs(xcb_get_window_attributes_reply( connection(), xcb_get_window_attributes(connection(), wId), &error)); QVERIFY(attribs.isNull()); QCOMPARE(error->error_code, uint8_t(3)); QCOMPARE(error->resource_id, wId); free(error); // test the same for the dtor { Xcb::Window scopedWindow(geometry, XCB_CW_OVERRIDE_REDIRECT, values); QVERIFY(scopedWindow.isValid()); wId = scopedWindow; } error = nullptr; ScopedCPointer attribs2(xcb_get_window_attributes_reply( connection(), xcb_get_window_attributes(connection(), wId), &error)); QVERIFY(attribs2.isNull()); QCOMPARE(error->error_code, uint8_t(3)); QCOMPARE(error->resource_id, wId); free(error); } void TestXcbWindow::destroyNotManaged() { Xcb::Window window; // just destroy the non-existing window window.reset(); // now let's add a window window.reset(createWindow(), false); xcb_window_t w = window; window.reset(); Xcb::WindowAttributes attribs(w); QVERIFY(attribs); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestXcbWindow) #include "test_xcb_window.moc" diff --git a/autotests/test_xcb_wrapper.cpp b/autotests/test_xcb_wrapper.cpp index a1e312c3f..4a428c90f 100644 --- a/autotests/test_xcb_wrapper.cpp +++ b/autotests/test_xcb_wrapper.cpp @@ -1,530 +1,531 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "testutils.h" // KWin #include "../xcbutils.h" // Qt #include #include +#include #include // xcb #include using namespace KWin; using namespace KWin::Xcb; class TestXcbWrapper : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void defaultCtor(); void normalCtor(); void copyCtorEmpty(); void copyCtorBeforeRetrieve(); void copyCtorAfterRetrieve(); void assignementEmpty(); void assignmentBeforeRetrieve(); void assignmentAfterRetrieve(); void discard(); void testQueryTree(); void testCurrentInput(); void testTransientFor(); void testPropertyByteArray(); void testPropertyBool(); void testAtom(); void testMotifEmpty(); void testMotif_data(); void testMotif(); private: void testEmpty(WindowGeometry &geometry); void testGeometry(WindowGeometry &geometry, const QRect &rect); Window m_testWindow; }; void TestXcbWrapper::initTestCase() { qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); } void TestXcbWrapper::init() { const uint32_t values[] = { true }; m_testWindow.create(QRect(0, 0, 10, 10), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); QVERIFY(m_testWindow.isValid()); } void TestXcbWrapper::cleanup() { m_testWindow.reset(); } void TestXcbWrapper::testEmpty(WindowGeometry &geometry) { QCOMPARE(geometry.window(), noneWindow()); QVERIFY(!geometry.data()); QCOMPARE(geometry.isNull(), true); QCOMPARE(geometry.rect(), QRect()); QVERIFY(!geometry); } void TestXcbWrapper::testGeometry(WindowGeometry &geometry, const QRect &rect) { QCOMPARE(geometry.window(), (xcb_window_t)m_testWindow); // now lets retrieve some data QCOMPARE(geometry.rect(), rect); QVERIFY(geometry.isRetrieved()); QCOMPARE(geometry.isNull(), false); QVERIFY(geometry); QVERIFY(geometry.data()); QCOMPARE(geometry.data()->x, int16_t(rect.x())); QCOMPARE(geometry.data()->y, int16_t(rect.y())); QCOMPARE(geometry.data()->width, uint16_t(rect.width())); QCOMPARE(geometry.data()->height, uint16_t(rect.height())); } void TestXcbWrapper::defaultCtor() { WindowGeometry geometry; testEmpty(geometry); QVERIFY(!geometry.isRetrieved()); } void TestXcbWrapper::normalCtor() { WindowGeometry geometry(m_testWindow); QVERIFY(!geometry.isRetrieved()); testGeometry(geometry, QRect(0, 0, 10, 10)); } void TestXcbWrapper::copyCtorEmpty() { WindowGeometry geometry; WindowGeometry other(geometry); testEmpty(geometry); QVERIFY(geometry.isRetrieved()); testEmpty(other); QVERIFY(!other.isRetrieved()); } void TestXcbWrapper::copyCtorBeforeRetrieve() { WindowGeometry geometry(m_testWindow); QVERIFY(!geometry.isRetrieved()); WindowGeometry other(geometry); testEmpty(geometry); QVERIFY(geometry.isRetrieved()); QVERIFY(!other.isRetrieved()); testGeometry(other, QRect(0, 0, 10, 10)); } void TestXcbWrapper::copyCtorAfterRetrieve() { WindowGeometry geometry(m_testWindow); QVERIFY(geometry); QVERIFY(geometry.isRetrieved()); QCOMPARE(geometry.rect(), QRect(0, 0, 10, 10)); WindowGeometry other(geometry); testEmpty(geometry); QVERIFY(geometry.isRetrieved()); QVERIFY(other.isRetrieved()); testGeometry(other, QRect(0, 0, 10, 10)); } void TestXcbWrapper::assignementEmpty() { WindowGeometry geometry; WindowGeometry other; testEmpty(geometry); testEmpty(other); other = geometry; QVERIFY(geometry.isRetrieved()); testEmpty(geometry); testEmpty(other); QVERIFY(!other.isRetrieved()); // test assignment to self geometry = geometry; other = other; testEmpty(geometry); testEmpty(other); } void TestXcbWrapper::assignmentBeforeRetrieve() { WindowGeometry geometry(m_testWindow); WindowGeometry other = geometry; QVERIFY(geometry.isRetrieved()); testEmpty(geometry); QVERIFY(!other.isRetrieved()); testGeometry(other, QRect(0, 0, 10, 10)); other = WindowGeometry(m_testWindow); QVERIFY(!other.isRetrieved()); QCOMPARE(other.window(), (xcb_window_t)m_testWindow); other = WindowGeometry(); testEmpty(geometry); // test assignment to self geometry = geometry; other = other; testEmpty(geometry); } void TestXcbWrapper::assignmentAfterRetrieve() { WindowGeometry geometry(m_testWindow); QVERIFY(geometry); QVERIFY(geometry.isRetrieved()); WindowGeometry other = geometry; testEmpty(geometry); QVERIFY(other.isRetrieved()); testGeometry(other, QRect(0, 0, 10, 10)); // test assignment to self geometry = geometry; other = other; testEmpty(geometry); testGeometry(other, QRect(0, 0, 10, 10)); // set to empty again other = WindowGeometry(); testEmpty(other); } void TestXcbWrapper::discard() { // discard of reply cannot be tested properly as we cannot check whether the reply has been discarded // therefore it's more or less just a test to ensure that it doesn't crash and the code paths // are taken. WindowGeometry *geometry = new WindowGeometry(); delete geometry; geometry = new WindowGeometry(m_testWindow); delete geometry; geometry = new WindowGeometry(m_testWindow); QVERIFY(geometry->data()); delete geometry; } void TestXcbWrapper::testQueryTree() { Tree tree(m_testWindow); // should have root as parent QCOMPARE(tree.parent(), static_cast(QX11Info::appRootWindow())); // shouldn't have any children QCOMPARE(tree->children_len, uint16_t(0)); QVERIFY(!tree.children()); // query for root Tree root(QX11Info::appRootWindow()); // shouldn't have a parent QCOMPARE(root.parent(), xcb_window_t(XCB_WINDOW_NONE)); QVERIFY(root->children_len > 0); xcb_window_t *children = root.children(); bool found = false; for (int i = 0; i < xcb_query_tree_children_length(root.data()); ++i) { if (children[i] == tree.window()) { found = true; break; } } QVERIFY(found); // query for not existing window Tree doesntExist(XCB_WINDOW_NONE); QCOMPARE(doesntExist.parent(), xcb_window_t(XCB_WINDOW_NONE)); QVERIFY(doesntExist.isNull()); QVERIFY(doesntExist.isRetrieved()); } void TestXcbWrapper::testCurrentInput() { xcb_connection_t *c = QX11Info::connection(); m_testWindow.map(); QX11Info::setAppTime(QX11Info::getTimestamp()); // let's set the input focus m_testWindow.focus(XCB_INPUT_FOCUS_PARENT, QX11Info::appTime()); xcb_flush(c); CurrentInput input; QCOMPARE(input.window(), (xcb_window_t)m_testWindow); // creating a copy should make the input object have no window any more CurrentInput input2(input); QCOMPARE(input2.window(), (xcb_window_t)m_testWindow); QCOMPARE(input.window(), xcb_window_t(XCB_WINDOW_NONE)); } void TestXcbWrapper::testTransientFor() { TransientFor transient(m_testWindow); QCOMPARE(transient.window(), (xcb_window_t)m_testWindow); // our m_testWindow doesn't have a transient for hint xcb_window_t compareWindow = XCB_WINDOW_NONE; QVERIFY(!transient.getTransientFor(&compareWindow)); QCOMPARE(compareWindow, xcb_window_t(XCB_WINDOW_NONE)); bool ok = true; QCOMPARE(transient.value(32, XCB_ATOM_WINDOW, XCB_WINDOW_NONE, &ok), xcb_window_t(XCB_WINDOW_NONE)); QVERIFY(!ok); ok = true; QCOMPARE(transient.value(XCB_WINDOW_NONE, &ok), xcb_window_t(XCB_WINDOW_NONE)); QVERIFY(!ok); // Create a Window with a transient for hint Window transientWindow(createWindow()); xcb_window_t testWindowId = m_testWindow; transientWindow.changeProperty(XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, 1, &testWindowId); // let's get another transient object TransientFor realTransient(transientWindow); QVERIFY(realTransient.getTransientFor(&compareWindow)); QCOMPARE(compareWindow, (xcb_window_t)m_testWindow); ok = false; QCOMPARE(realTransient.value(32, XCB_ATOM_WINDOW, XCB_WINDOW_NONE, &ok), (xcb_window_t)m_testWindow); QVERIFY(ok); ok = false; QCOMPARE(realTransient.value(XCB_WINDOW_NONE, &ok), (xcb_window_t)m_testWindow); QVERIFY(ok); ok = false; QCOMPARE(realTransient.value(), (xcb_window_t)m_testWindow); QCOMPARE(realTransient.value(nullptr, &ok)[0], (xcb_window_t)m_testWindow); QVERIFY(ok); QCOMPARE(realTransient.value()[0], (xcb_window_t)m_testWindow); // test for a not existing window TransientFor doesntExist(XCB_WINDOW_NONE); QVERIFY(!doesntExist.getTransientFor(&compareWindow)); } void TestXcbWrapper::testPropertyByteArray() { Window testWindow(createWindow()); Property prop(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); QCOMPARE(prop.toByteArray(), QByteArray()); bool ok = true; QCOMPARE(prop.toByteArray(&ok), QByteArray()); QVERIFY(!ok); ok = true; QVERIFY(!prop.value()); QCOMPARE(prop.value("bar", &ok), "bar"); QVERIFY(!ok); QCOMPARE(QByteArray(StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); testWindow.changeProperty(XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 3, "foo"); prop = Property(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); QCOMPARE(prop.toByteArray(), QByteArrayLiteral("foo")); QCOMPARE(prop.toByteArray(&ok), QByteArrayLiteral("foo")); QVERIFY(ok); QCOMPARE(prop.value(nullptr, &ok), "foo"); QVERIFY(ok); QCOMPARE(QByteArray(StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArrayLiteral("foo")); // verify incorrect format and type QCOMPARE(prop.toByteArray(32), QByteArray()); QCOMPARE(prop.toByteArray(8, XCB_ATOM_CARDINAL), QByteArray()); // verify empty property testWindow.changeProperty(XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 0, nullptr); prop = Property(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); QCOMPARE(prop.toByteArray(), QByteArray()); QCOMPARE(prop.toByteArray(&ok), QByteArray()); //valid bytearray QVERIFY(ok); //The bytearray should be empty QVERIFY(prop.toByteArray().isEmpty()); //The bytearray should be not null QVERIFY(!prop.toByteArray().isNull()); QVERIFY(!prop.value()); QCOMPARE(QByteArray(StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); // verify non existing property Xcb::Atom invalid(QByteArrayLiteral("INVALID_ATOM")); prop = Property(false, testWindow, invalid, XCB_ATOM_STRING, 0, 100000); QCOMPARE(prop.toByteArray(), QByteArray()); QCOMPARE(prop.toByteArray(&ok), QByteArray()); //invalid bytearray QVERIFY(!ok); //The bytearray should be empty QVERIFY(prop.toByteArray().isEmpty()); //The bytearray should be not null QVERIFY(prop.toByteArray().isNull()); QVERIFY(!prop.value()); QCOMPARE(QByteArray(StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); } void TestXcbWrapper::testPropertyBool() { Window testWindow(createWindow()); Atom blockCompositing(QByteArrayLiteral("_KDE_NET_WM_BLOCK_COMPOSITING")); QVERIFY(blockCompositing != XCB_ATOM_NONE); NETWinInfo info(QX11Info::connection(), testWindow, QX11Info::appRootWindow(), NET::Properties(), NET::WM2BlockCompositing); Property prop(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); bool ok = true; QVERIFY(!prop.toBool()); QVERIFY(!prop.toBool(&ok)); QVERIFY(!ok); info.setBlockingCompositing(true); xcb_flush(QX11Info::connection()); prop = Property(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); QVERIFY(prop.toBool()); QVERIFY(prop.toBool(&ok)); QVERIFY(ok); // incorrect type and format QVERIFY(!prop.toBool(8)); QVERIFY(!prop.toBool(32, blockCompositing)); QVERIFY(!prop.toBool(32, blockCompositing, &ok)); QVERIFY(!ok); // incorrect value: uint32_t d[] = {1, 0}; testWindow.changeProperty(blockCompositing, XCB_ATOM_CARDINAL, 32, 2, d); prop = Property(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); QVERIFY(!prop.toBool()); ok = true; QVERIFY(!prop.toBool(&ok)); QVERIFY(!ok); } void TestXcbWrapper::testAtom() { Atom atom(QByteArrayLiteral("WM_CLIENT_MACHINE")); QCOMPARE(atom.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); QVERIFY(atom == XCB_ATOM_WM_CLIENT_MACHINE); QVERIFY(atom.isValid()); // test the const paths const Atom &atom2(atom); QVERIFY(atom2.isValid()); QVERIFY(atom2 == XCB_ATOM_WM_CLIENT_MACHINE); QCOMPARE(atom2.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); //destroy before retrieved Atom atom3(QByteArrayLiteral("WM_CLIENT_MACHINE")); QCOMPARE(atom3.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); } void TestXcbWrapper::testMotifEmpty() { Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS")); MotifHints hints(atom); // pre init QCOMPARE(hints.hasDecoration(), false); QCOMPARE(hints.noBorder(), false); QCOMPARE(hints.resize(), true); QCOMPARE(hints.move(), true); QCOMPARE(hints.minimize(), true); QCOMPARE(hints.maximize(), true); QCOMPARE(hints.close(), true); // post init, pre read hints.init(m_testWindow); QCOMPARE(hints.hasDecoration(), false); QCOMPARE(hints.noBorder(), false); QCOMPARE(hints.resize(), true); QCOMPARE(hints.move(), true); QCOMPARE(hints.minimize(), true); QCOMPARE(hints.maximize(), true); QCOMPARE(hints.close(), true); // post read hints.read(); QCOMPARE(hints.hasDecoration(), false); QCOMPARE(hints.noBorder(), false); QCOMPARE(hints.resize(), true); QCOMPARE(hints.move(), true); QCOMPARE(hints.minimize(), true); QCOMPARE(hints.maximize(), true); QCOMPARE(hints.close(), true); } void TestXcbWrapper::testMotif_data() { QTest::addColumn("flags"); QTest::addColumn("funtions"); QTest::addColumn("decorations"); QTest::addColumn("expectedHasDecoration"); QTest::addColumn("expectedNoBorder"); QTest::addColumn("expectedResize"); QTest::addColumn("expectedMove"); QTest::addColumn("expectedMinimize"); QTest::addColumn("expectedMaximize"); QTest::addColumn("expectedClose"); QTest::newRow("none") << 0u << 0u << 0u << false << false << true << true << true << true << true; QTest::newRow("noborder") << 2u << 5u << 0u << true << true << true << true << true << true << true; QTest::newRow("border") << 2u << 5u << 1u << true << false << true << true << true << true << true; QTest::newRow("resize") << 1u << 2u << 1u << false << false << true << false << false << false << false; QTest::newRow("move") << 1u << 4u << 1u << false << false << false << true << false << false << false; QTest::newRow("minimize") << 1u << 8u << 1u << false << false << false << false << true << false << false; QTest::newRow("maximize") << 1u << 16u << 1u << false << false << false << false << false << true << false; QTest::newRow("close") << 1u << 32u << 1u << false << false << false << false << false << false << true; QTest::newRow("resize/all") << 1u << 3u << 1u << false << false << false << true << true << true << true; QTest::newRow("move/all") << 1u << 5u << 1u << false << false << true << false << true << true << true; QTest::newRow("minimize/all") << 1u << 9u << 1u << false << false << true << true << false << true << true; QTest::newRow("maximize/all") << 1u << 17u << 1u << false << false << true << true << true << false << true; QTest::newRow("close/all") << 1u << 33u << 1u << false << false << true << true << true << true << false; QTest::newRow("all") << 1u << 62u << 1u << false << false << true << true << true << true << true; QTest::newRow("all/all") << 1u << 63u << 1u << false << false << false << false << false << false << false; QTest::newRow("all/all/deco") << 3u << 63u << 1u << true << false << false << false << false << false << false; } void TestXcbWrapper::testMotif() { Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS")); QFETCH(quint32, flags); QFETCH(quint32, funtions); QFETCH(quint32, decorations); quint32 data[] = { flags, funtions, decorations, 0, 0 }; xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, m_testWindow, atom, atom, 32, 5, data); xcb_flush(QX11Info::connection()); MotifHints hints(atom); hints.init(m_testWindow); hints.read(); QTEST(hints.hasDecoration(), "expectedHasDecoration"); QTEST(hints.noBorder(), "expectedNoBorder"); QTEST(hints.resize(), "expectedResize"); QTEST(hints.move(), "expectedMove"); QTEST(hints.minimize(), "expectedMinimize"); QTEST(hints.maximize(), "expectedMaximize"); QTEST(hints.close(), "expectedClose"); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestXcbWrapper) #include "test_xcb_wrapper.moc" diff --git a/kcmkwin/kwinrules/main.cpp b/kcmkwin/kwinrules/main.cpp index 885e7ab36..ae66a0372 100644 --- a/kcmkwin/kwinrules/main.cpp +++ b/kcmkwin/kwinrules/main.cpp @@ -1,273 +1,274 @@ /* * Copyright (c) 2004 Lubos Lunak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include +#include #include #include #include "ruleswidget.h" #include "../../rules.h" #include "../../client_machine.h" #include namespace KWin { static void loadRules(QList< Rules* >& rules) { KConfig _cfg("kwinrulesrc"); KConfigGroup cfg(&_cfg, "General"); int count = cfg.readEntry("count", 0); for (int i = 1; i <= count; ++i) { cfg = KConfigGroup(&_cfg, QString::number(i)); Rules* rule = new Rules(cfg); rules.append(rule); } } static void saveRules(const QList< Rules* >& rules) { KConfig cfg("kwinrulesrc"); QStringList groups = cfg.groupList(); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) cfg.deleteGroup(*it); cfg.group("General").writeEntry("count", rules.count()); int i = 1; for (QList< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) { KConfigGroup cg(&cfg, QString::number(i)); (*it)->write(cg); ++i; } } static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app) { // ClientMachine::resolve calls NETWinInfo::update() which requires properties // bug #348472 ./. bug #346748 if (QX11Info::isPlatformX11()) { qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); } KWindowInfo info = KWindowInfo(wid, NET::WMName | NET::WMWindowType, NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2ClientMachine); if (!info.valid()) // shouldn't really happen return nullptr; ClientMachine clientMachine; clientMachine.resolve(info.win(), info.groupLeader()); QByteArray wmclass_class = info.windowClassClass().toLower(); QByteArray wmclass_name = info.windowClassName().toLower(); QByteArray role = info.windowRole().toLower(); NET::WindowType type = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); QString title = info.name(); QByteArray machine = clientMachine.hostName(); Rules* best_match = nullptr; int match_quality = 0; for (QList< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) { // try to find an exact match, i.e. not a generic rule Rules* rule = *it; int quality = 0; bool generic = true; if (rule->wmclassmatch != Rules::ExactMatch) continue; // too generic if (!rule->matchWMClass(wmclass_class, wmclass_name)) continue; // from now on, it matches the app - now try to match for a specific window if (rule->wmclasscomplete) { quality += 1; generic = false; // this can be considered specific enough (old X apps) } if (!whole_app) { if (rule->windowrolematch != Rules::UnimportantMatch) { quality += rule->windowrolematch == Rules::ExactMatch ? 5 : 1; generic = false; } if (rule->titlematch != Rules::UnimportantMatch) { quality += rule->titlematch == Rules::ExactMatch ? 3 : 1; generic = false; } if (rule->types != NET::AllTypesMask) { int bits = 0; for (unsigned int bit = 1; bit < 1U << 31; bit <<= 1) if (rule->types & bit) ++bits; if (bits == 1) quality += 2; } if (generic) // ignore generic rules, use only the ones that are for this window continue; } else { if (rule->types == NET::AllTypesMask) quality += 2; } if (!rule->matchType(type) || !rule->matchRole(role) || !rule->matchTitle(title) || !rule->matchClientMachine(machine, clientMachine.isLocal())) continue; if (quality > match_quality) { best_match = rule; match_quality = quality; } } if (best_match != nullptr) return best_match; Rules* ret = new Rules; if (whole_app) { ret->description = i18n("Application settings for %1", QString::fromLatin1(wmclass_class)); // TODO maybe exclude some types? If yes, then also exclude them above // when searching. ret->types = NET::AllTypesMask; ret->titlematch = Rules::UnimportantMatch; ret->clientmachine = machine; // set, but make unimportant ret->clientmachinematch = Rules::UnimportantMatch; ret->windowrolematch = Rules::UnimportantMatch; if (wmclass_name == wmclass_class) { ret->wmclasscomplete = false; ret->wmclass = wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } else { // WM_CLASS components differ - perhaps the app got -name argument ret->wmclasscomplete = true; ret->wmclass = wmclass_name + ' ' + wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } return ret; } ret->description = i18n("Window settings for %1", QString::fromLatin1(wmclass_class)); if (type == NET::Unknown) ret->types = NET::NormalMask; else ret->types = NET::WindowTypeMask( 1 << type); // convert type to its mask ret->title = title; // set, but make unimportant ret->titlematch = Rules::UnimportantMatch; ret->clientmachine = machine; // set, but make unimportant ret->clientmachinematch = Rules::UnimportantMatch; if (!role.isEmpty() && role != "unknown" && role != "unnamed") { // Qt sets this if not specified ret->windowrole = role; ret->windowrolematch = Rules::ExactMatch; if (wmclass_name == wmclass_class) { ret->wmclasscomplete = false; ret->wmclass = wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } else { // WM_CLASS components differ - perhaps the app got -name argument ret->wmclasscomplete = true; ret->wmclass = wmclass_name + ' ' + wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } } else { // no role set if (wmclass_name != wmclass_class) { ret->wmclasscomplete = true; ret->wmclass = wmclass_name + ' ' + wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } else { // This is a window that has no role set, and both components of WM_CLASS // match (possibly only differing in case), which most likely means either // the application doesn't give a damn about distinguishing its various // windows, or it's an app that uses role for that, but this window // lacks it for some reason. Use non-complete WM_CLASS matching, also // include window title in the matching, and pray it causes many more positive // matches than negative matches. ret->titlematch = Rules::ExactMatch; ret->wmclasscomplete = false; ret->wmclass = wmclass_class; ret->wmclassmatch = Rules::ExactMatch; } } return ret; } static int edit(Window wid, bool whole_app) { QList< Rules* > rules; loadRules(rules); Rules* orig_rule = findRule(rules, wid, whole_app); RulesDialog dlg; if (whole_app) dlg.setWindowTitle(i18nc("Window caption for the application wide rules dialog", "Edit Application-Specific Settings")); // dlg.edit() creates new Rules instance if edited Rules* edited_rule = dlg.edit(orig_rule, wid, true); if (edited_rule == nullptr || edited_rule->isEmpty()) { rules.removeAll(orig_rule); delete orig_rule; if (orig_rule != edited_rule) delete edited_rule; } else if (edited_rule != orig_rule) { int pos = rules.indexOf(orig_rule); if (pos != -1) rules[ pos ] = edited_rule; else rules.prepend(edited_rule); delete orig_rule; } saveRules(rules); // Send signal to all kwin instances QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); QDBusConnection::sessionBus().send(message); return 0; } } // namespace extern "C" KWIN_EXPORT int kdemain(int argc, char* argv[]) { QApplication app(argc, argv); app.setApplicationDisplayName(i18n("KWin")); app.setApplicationName("kwin_rules_dialog"); app.setApplicationVersion("1.0"); bool whole_app = false; bool id_ok = false; Window id = None; { QCommandLineParser parser; parser.setApplicationDescription(i18n("KWin helper utility")); parser.addOption(QCommandLineOption("wid", i18n("WId of the window for special window settings."), "wid")); parser.addOption(QCommandLineOption("whole-app", i18n("Whether the settings should affect all windows of the application."))); parser.process(app); id = parser.value("wid").toULongLong(&id_ok); whole_app = parser.isSet("whole-app"); } if (!id_ok || id == None) { printf("%s\n", qPrintable(i18n("This helper utility is not supposed to be called directly."))); return 1; } return KWin::edit(id, whole_app); } diff --git a/libkwineffects/CMakeLists.txt b/libkwineffects/CMakeLists.txt index 306e380a0..165abc50f 100644 --- a/libkwineffects/CMakeLists.txt +++ b/libkwineffects/CMakeLists.txt @@ -1,122 +1,121 @@ ########### next target ############### include(ECMSetupVersion) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KWINEFFECTS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kwineffects_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KWinEffectsConfigVersion.cmake" SOVERSION 10 ) ### xrenderutils lib ### set(kwin_XRENDERUTILS_SRCS kwinxrenderutils.cpp logging.cpp ) add_library(kwinxrenderutils SHARED ${kwin_XRENDERUTILS_SRCS}) generate_export_header(kwinxrenderutils EXPORT_FILE_NAME kwinxrenderutils_export.h) target_link_libraries(kwinxrenderutils PUBLIC Qt5::Core Qt5::Gui XCB::XCB XCB::XFIXES XCB::RENDER KF5::WaylandServer ) set_target_properties(kwinxrenderutils PROPERTIES VERSION ${KWINEFFECTS_VERSION_STRING} SOVERSION ${KWINEFFECTS_SOVERSION} ) set_target_properties(kwinxrenderutils PROPERTIES OUTPUT_NAME ${KWIN_NAME}xrenderutils) install(TARGETS kwinxrenderutils EXPORT kdeworkspaceLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) ### effects lib ### set(kwin_EFFECTSLIB_SRCS kwineffects.cpp anidata.cpp kwinanimationeffect.cpp logging.cpp ) set(kwineffects_QT_LIBS Qt5::DBus Qt5::Widgets - Qt5::X11Extras ) set(kwineffects_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::WindowSystem ) set(kwineffects_XCB_LIBS XCB::XCB ) add_library(kwineffects SHARED ${kwin_EFFECTSLIB_SRCS}) generate_export_header(kwineffects EXPORT_FILE_NAME kwineffects_export.h) target_link_libraries(kwineffects PUBLIC ${kwineffects_QT_LIBS} ${kwineffects_KDE_LIBS} ${kwineffects_XCB_LIBS} ) if( KWIN_HAVE_XRENDER_COMPOSITING ) target_link_libraries(kwineffects PRIVATE kwinxrenderutils XCB::XFIXES) endif() set_target_properties(kwineffects PROPERTIES VERSION ${KWINEFFECTS_VERSION_STRING} SOVERSION ${KWINEFFECTS_SOVERSION} ) set_target_properties(kwineffects PROPERTIES OUTPUT_NAME ${KWIN_NAME}effects) install(TARGETS kwineffects EXPORT kdeworkspaceLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) # kwingl(es)utils library set(kwin_GLUTILSLIB_SRCS kwinglutils.cpp kwingltexture.cpp kwinglutils_funcs.cpp kwinglplatform.cpp kwinglcolorcorrection.cpp logging.cpp ) macro( KWIN4_ADD_GLUTILS_BACKEND name glinclude ) include_directories(${glinclude}) add_library(${name} SHARED ${kwin_GLUTILSLIB_SRCS}) generate_export_header(${name} BASE_NAME kwinglutils EXPORT_FILE_NAME kwinglutils_export.h) - target_link_libraries(${name} PUBLIC Qt5::DBus Qt5::X11Extras XCB::XCB KF5::CoreAddons KF5::WindowSystem) + target_link_libraries(${name} PUBLIC Qt5::DBus XCB::XCB KF5::CoreAddons KF5::WindowSystem) set_target_properties(${name} PROPERTIES VERSION ${KWINEFFECTS_VERSION_STRING} SOVERSION ${KWINEFFECTS_SOVERSION} ) target_link_libraries(${name} PUBLIC ${ARGN}) install(TARGETS ${name} EXPORT kdeworkspaceLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) endmacro() kwin4_add_glutils_backend(kwinglutils ${epoxy_INCLUDE_DIR} ${epoxy_LIBRARY}) set_target_properties(kwinglutils PROPERTIES OUTPUT_NAME ${KWIN_NAME}glutils) target_link_libraries(kwinglutils PUBLIC ${epoxy_LIBRARY}) install( FILES kwinglobals.h kwineffects.h kwinanimationeffect.h kwinglplatform.h kwinglutils.h kwinglutils_funcs.h kwingltexture.h kwinxrenderutils.h ${CMAKE_CURRENT_BINARY_DIR}/kwinconfig.h ${CMAKE_CURRENT_BINARY_DIR}/kwineffects_export.h ${CMAKE_CURRENT_BINARY_DIR}/kwinglutils_export.h ${CMAKE_CURRENT_BINARY_DIR}/kwinxrenderutils_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) diff --git a/libkwineffects/kwinglobals.h b/libkwineffects/kwinglobals.h index e755da1e9..ba57d2790 100644 --- a/libkwineffects/kwinglobals.h +++ b/libkwineffects/kwinglobals.h @@ -1,257 +1,246 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_LIB_KWINGLOBALS_H #define KWIN_LIB_KWINGLOBALS_H #include #include #include #include -#include #include #include #include #include #include #define KWIN_QT5_PORTING 0 namespace KWin { enum CompositingType { NoCompositing = 0, /** * Used as a flag whether OpenGL based compositing is used. * The flag is or-ed to the enum values of the specific OpenGL types. * The actual Compositors use the or @c OpenGL2Compositing * flags. If you need to know whether OpenGL is used, either and the flag or * use EffectsHandler::isOpenGLCompositing(). **/ OpenGLCompositing = 1, XRenderCompositing = 1<<1, QPainterCompositing = 1<< 2, OpenGL2Compositing = 1<<3 | OpenGLCompositing }; enum OpenGLPlatformInterface { NoOpenGLPlatformInterface = 0, GlxPlatformInterface, EglPlatformInterface }; enum clientAreaOption { PlacementArea, // geometry where a window will be initially placed after being mapped MovementArea, // ??? window movement snapping area? ignore struts MaximizeArea, // geometry to which a window will be maximized MaximizeFullArea, // like MaximizeArea, but ignore struts - used e.g. for topmenu FullScreenArea, // area for fullscreen windows // these below don't depend on xinerama settings WorkArea, // whole workarea (all screens together) FullArea, // whole area (all screens together), ignore struts ScreenArea // one whole screen, ignore struts }; enum ElectricBorder { ElectricTop, ElectricTopRight, ElectricRight, ElectricBottomRight, ElectricBottom, ElectricBottomLeft, ElectricLeft, ElectricTopLeft, ELECTRIC_COUNT, ElectricNone }; // TODO: Hardcoding is bad, need to add some way of registering global actions to these. // When designing the new system we must keep in mind that we have conditional actions // such as "only when moving windows" desktop switching that the current global action // system doesn't support. enum ElectricBorderAction { ElectricActionNone, // No special action, not set, desktop switch or an effect ElectricActionShowDesktop, // Show desktop or restore ElectricActionLockScreen, // Lock screen ElectricActionKRunner, // Open KRunner ElectricActionActivityManager, // Activity Manager ElectricActionApplicationLauncher, // Application Launcher ELECTRIC_ACTION_COUNT }; // DesktopMode and WindowsMode are based on the order in which the desktop // or window were viewed. // DesktopListMode lists them in the order created. enum TabBoxMode { TabBoxDesktopMode, // Focus chain of desktops TabBoxDesktopListMode, // Static desktop order TabBoxWindowsMode, // Primary window switching mode TabBoxWindowsAlternativeMode, // Secondary window switching mode TabBoxCurrentAppWindowsMode, // Same as primary window switching mode but only for windows of current application TabBoxCurrentAppWindowsAlternativeMode // Same as secondary switching mode but only for windows of current application }; enum KWinOption { CloseButtonCorner, SwitchDesktopOnScreenEdge, SwitchDesktopOnScreenEdgeMovingWindows }; /** * @brief The direction in which a pointer axis is moved. * */ enum PointerAxisDirection { PointerAxisUp, PointerAxisDown, PointerAxisLeft, PointerAxisRight }; -inline -KWIN_EXPORT Display* display() -{ - static Display *s_display = nullptr; - if (!s_display && QX11Info::isPlatformX11()) { - s_display = QX11Info::display(); - } - return s_display; -} - inline KWIN_EXPORT xcb_connection_t *connection() { static xcb_connection_t *s_con = nullptr; if (!s_con) { s_con = reinterpret_cast(qApp->property("x11Connection").value()); } Q_ASSERT(qApp); return s_con; } inline KWIN_EXPORT xcb_window_t rootWindow() { static xcb_window_t s_rootWindow = XCB_WINDOW_NONE; if (s_rootWindow == XCB_WINDOW_NONE) { s_rootWindow = qApp->property("x11RootWindow").value(); } return s_rootWindow; } inline KWIN_EXPORT xcb_timestamp_t xTime() { return qApp->property("x11Time").value(); } inline KWIN_EXPORT xcb_screen_t *defaultScreen() { static xcb_screen_t *s_screen = nullptr; if (s_screen) { return s_screen; } int screen = qApp->property("x11ScreenNumber").toInt(); for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(connection())); it.rem; --screen, xcb_screen_next(&it)) { if (screen == 0) { s_screen = it.data; } } return s_screen; } inline KWIN_EXPORT int displayWidth() { xcb_screen_t *screen = defaultScreen(); return screen ? screen->width_in_pixels : 0; } inline KWIN_EXPORT int displayHeight() { xcb_screen_t *screen = defaultScreen(); return screen ? screen->height_in_pixels : 0; } /** * Short wrapper for a cursor image provided by the Platform. * @since 5.9 **/ class PlatformCursorImage { public: explicit PlatformCursorImage() : m_image() , m_hotSpot() { } explicit PlatformCursorImage(const QImage &image, const QPoint &hotSpot) : m_image(image) , m_hotSpot(hotSpot) { } virtual ~PlatformCursorImage() = default; QImage image() const { return m_image; } QPoint hotSpot() const { return m_hotSpot; } private: QImage m_image; QPoint m_hotSpot; }; } // namespace #define KWIN_SINGLETON_VARIABLE(ClassName, variableName) \ public: \ static ClassName *create(QObject *parent = nullptr);\ static ClassName *self() { return variableName; }\ protected: \ explicit ClassName(QObject *parent = nullptr); \ private: \ static ClassName *variableName; #define KWIN_SINGLETON(ClassName) KWIN_SINGLETON_VARIABLE(ClassName, s_self) #define KWIN_SINGLETON_FACTORY_VARIABLE_FACTORED(ClassName, FactoredClassName, variableName) \ ClassName *ClassName::variableName = nullptr; \ ClassName *ClassName::create(QObject *parent) \ { \ Q_ASSERT(!variableName); \ variableName = new FactoredClassName(parent); \ return variableName; \ } #define KWIN_SINGLETON_FACTORY_VARIABLE(ClassName, variableName) KWIN_SINGLETON_FACTORY_VARIABLE_FACTORED(ClassName, ClassName, variableName) #define KWIN_SINGLETON_FACTORY_FACTORED(ClassName, FactoredClassName) KWIN_SINGLETON_FACTORY_VARIABLE_FACTORED(ClassName, FactoredClassName, s_self) #define KWIN_SINGLETON_FACTORY(ClassName) KWIN_SINGLETON_FACTORY_VARIABLE(ClassName, s_self) #endif diff --git a/main_x11.cpp b/main_x11.cpp index e4db348bf..da3c0452e 100644 --- a/main_x11.cpp +++ b/main_x11.cpp @@ -1,467 +1,468 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "main_x11.h" #include // kwin #include "platform.h" #include "sm.h" #include "workspace.h" #include "xcbutils.h" // KDE #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include +#include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg) namespace KWin { static void sighandler(int) { QApplication::exit(); } class AlternativeWMDialog : public QDialog { public: AlternativeWMDialog() : QDialog() { QWidget* mainWidget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(mainWidget); QString text = i18n( "KWin is unstable.\n" "It seems to have crashed several times in a row.\n" "You can select another window manager to run:"); QLabel* textLabel = new QLabel(text, mainWidget); layout->addWidget(textLabel); wmList = new QComboBox(mainWidget); wmList->setEditable(true); layout->addWidget(wmList); addWM(QStringLiteral("metacity")); addWM(QStringLiteral("openbox")); addWM(QStringLiteral("fvwm2")); addWM(QStringLiteral(KWIN_INTERNAL_NAME_X11)); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setDefault(true); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttons); raise(); } void addWM(const QString& wm) { // TODO: Check if WM is installed if (!QStandardPaths::findExecutable(wm).isEmpty()) wmList->addItem(wm); } QString selectedWM() const { return wmList->currentText(); } private: QComboBox* wmList; }; //************************************ // KWinSelectionOwner //************************************ KWinSelectionOwner::KWinSelectionOwner(int screen_P) : KSelectionOwner(make_selection_atom(screen_P), screen_P) { } xcb_atom_t KWinSelectionOwner::make_selection_atom(int screen_P) { if (screen_P < 0) screen_P = QX11Info::appScreen(); QByteArray screen(QByteArrayLiteral("WM_S")); screen.append(QByteArray::number(screen_P)); ScopedCPointer atom(xcb_intern_atom_reply( connection(), xcb_intern_atom_unchecked(connection(), false, screen.length(), screen.constData()), nullptr)); if (atom.isNull()) { return XCB_ATOM_NONE; } return atom->atom; } void KWinSelectionOwner::getAtoms() { KSelectionOwner::getAtoms(); if (xa_version == XCB_ATOM_NONE) { const QByteArray name(QByteArrayLiteral("VERSION")); ScopedCPointer atom(xcb_intern_atom_reply( connection(), xcb_intern_atom_unchecked(connection(), false, name.length(), name.constData()), nullptr)); if (!atom.isNull()) { xa_version = atom->atom; } } } void KWinSelectionOwner::replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) { KSelectionOwner::replyTargets(property_P, requestor_P); xcb_atom_t atoms[ 1 ] = { xa_version }; // PropModeAppend ! xcb_change_property(connection(), XCB_PROP_MODE_APPEND, requestor_P, property_P, XCB_ATOM_ATOM, 32, 1, atoms); } bool KWinSelectionOwner::genericReply(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) { if (target_P == xa_version) { int32_t version[] = { 2, 0 }; xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, requestor_P, property_P, XCB_ATOM_INTEGER, 32, 2, version); } else return KSelectionOwner::genericReply(target_P, property_P, requestor_P); return true; } xcb_atom_t KWinSelectionOwner::xa_version = XCB_ATOM_NONE; //************************************ // ApplicationX11 //************************************ ApplicationX11::ApplicationX11(int &argc, char **argv) : Application(OperationModeX11, argc, argv) , owner() , m_replace(false) { setX11Connection(QX11Info::connection()); setX11RootWindow(QX11Info::appRootWindow()); } ApplicationX11::~ApplicationX11() { destroyCompositor(); destroyWorkspace(); if (!owner.isNull() && owner->ownerWindow() != XCB_WINDOW_NONE) // If there was no --replace (no new WM) Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); } void ApplicationX11::setReplace(bool replace) { m_replace = replace; } void ApplicationX11::lostSelection() { sendPostedEvents(); destroyCompositor(); destroyWorkspace(); // Remove windowmanager privileges Xcb::selectInput(rootWindow(), XCB_EVENT_MASK_PROPERTY_CHANGE); quit(); } void ApplicationX11::performStartup() { crashChecking(); if (Application::x11ScreenNumber() == -1) { Application::setX11ScreenNumber(QX11Info::appScreen()); } // QSessionManager for some reason triggers a very early commitDataRequest // and updates the key - before we create the workspace and load the session // data -> store and pass to the workspace constructor m_originalSessionKey = sessionKey(); owner.reset(new KWinSelectionOwner(Application::x11ScreenNumber())); connect(owner.data(), &KSelectionOwner::failedToClaimOwnership, []{ fputs(i18n("kwin: unable to claim manager selection, another wm running? (try using --replace)\n").toLocal8Bit().constData(), stderr); ::exit(1); }); connect(owner.data(), SIGNAL(lostOwnership()), SLOT(lostSelection())); connect(owner.data(), &KSelectionOwner::claimedOwnership, [this]{ setupEventFilters(); // first load options - done internally by a different thread createOptions(); // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; ScopedCPointer redirectCheck(xcb_request_check(connection(), xcb_change_window_attributes_checked(connection(), rootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (!redirectCheck.isNull()) { fputs(i18n("kwin: another window manager is running (try using --replace)\n").toLocal8Bit().constData(), stderr); if (!wasCrash()) // if this is a crash-restart, DrKonqi may have stopped the process w/o killing the connection ::exit(1); } createInput(); connect(platform(), &Platform::screensQueried, this, [this] { createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); } ); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform()->init(); }); // we need to do an XSync here, otherwise the QPA might crash us later on Xcb::sync(); owner->claim(m_replace || wasCrash(), true); createAtoms(); } bool ApplicationX11::notify(QObject* o, QEvent* e) { if (Workspace::self()->workspaceEvent(e)) return true; return QApplication::notify(o, e); } void ApplicationX11::setupCrashHandler() { KCrash::setEmergencySaveFunction(ApplicationX11::crashHandler); } void ApplicationX11::crashChecking() { setupCrashHandler(); if (crashes >= 4) { // Something has gone seriously wrong AlternativeWMDialog dialog; QString cmd = QStringLiteral(KWIN_INTERNAL_NAME_X11); if (dialog.exec() == QDialog::Accepted) cmd = dialog.selectedWM(); else ::exit(1); if (cmd.length() > 500) { qCDebug(KWIN_CORE) << "Command is too long, truncating"; cmd = cmd.left(500); } qCDebug(KWIN_CORE) << "Starting" << cmd << "and exiting"; char buf[1024]; sprintf(buf, "%s &", cmd.toAscii().data()); system(buf); ::exit(1); } if (crashes >= 2) { // Disable compositing if we have had too many crashes qCDebug(KWIN_CORE) << "Too many crashes recently, disabling compositing"; KConfigGroup compgroup(KSharedConfig::openConfig(), "Compositing"); compgroup.writeEntry("Enabled", false); } // Reset crashes count if we stay up for more that 15 seconds QTimer::singleShot(15 * 1000, this, SLOT(resetCrashesCount())); } void ApplicationX11::crashHandler(int signal) { crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes); char cmd[1024]; sprintf(cmd, "%s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes); sleep(1); system(cmd); } } // namespace extern "C" KWIN_EXPORT int kdemain(int argc, char * argv[]) { KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); int primaryScreen = 0; xcb_connection_t *c = xcb_connect(nullptr, &primaryScreen); if (!c || xcb_connection_has_error(c)) { fprintf(stderr, "%s: FATAL ERROR while trying to open display %s\n", argv[0], qgetenv("DISPLAY").constData()); exit(1); } const int number_of_screens = xcb_setup_roots_length(xcb_get_setup(c)); xcb_disconnect(c); c = nullptr; // multi head auto isMultiHead = []() -> bool { QByteArray multiHead = qgetenv("KDE_MULTIHEAD"); if (!multiHead.isEmpty()) { return (multiHead.toLower() == "true"); } return true; }; if (number_of_screens != 1 && isMultiHead()) { KWin::Application::setX11MultiHead(true); KWin::Application::setX11ScreenNumber(primaryScreen); int pos; // Temporarily needed to reconstruct DISPLAY var if multi-head QByteArray display_name = qgetenv("DISPLAY"); if ((pos = display_name.lastIndexOf('.')) != -1) display_name.remove(pos, 10); // 10 is enough to be sure we removed ".s" QString envir; for (int i = 0; i < number_of_screens; i++) { // If execution doesn't pass by here, then kwin // acts exactly as previously if (i != KWin::Application::x11ScreenNumber() && fork() == 0) { KWin::Application::setX11ScreenNumber(i); QByteArray dBusSuffix = qgetenv("KWIN_DBUS_SERVICE_SUFFIX"); if (!dBusSuffix.isNull()) { dBusSuffix.append("."); } dBusSuffix.append(QByteArrayLiteral("head-")).append(QByteArray::number(i)); qputenv("KWIN_DBUS_SERVICE_SUFFIX", dBusSuffix); // Break here because we are the child process, we don't // want to fork() anymore break; } } // In the next statement, display_name shouldn't contain a screen // number. If it had it, it was removed at the "pos" check envir.sprintf("DISPLAY=%s.%d", display_name.data(), KWin::Application::x11ScreenNumber()); if (putenv(strdup(envir.toAscii().constData()))) { fprintf(stderr, "%s: WARNING: unable to set DISPLAY environment variable\n", argv[0]); perror("putenv()"); } } if (signal(SIGTERM, KWin::sighandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); if (signal(SIGINT, KWin::sighandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); // Disable the glib event loop integration, since it seems to be responsible // for several bug reports about high CPU usage (bug #239963) setenv("QT_NO_GLIB", "1", true); // enforce xcb plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "xcb", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); #endif KWin::ApplicationX11 a(argc, argv); a.setupTranslator(); KWin::Application::createAboutData(); QCommandLineOption replaceOption(QStringLiteral("replace"), i18n("Replace already-running ICCCM2.0-compliant window manager")); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(replaceOption); #ifdef KWIN_BUILD_ACTIVITIES QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), i18n("Disable KActivities integration.")); parser.addOption(noActivitiesOption); #endif parser.process(a); a.processCommandLine(&parser); a.setReplace(parser.isSet(replaceOption)); #ifdef KWIN_BUILD_ACTIVITIES if (parser.isSet(noActivitiesOption)) { a.setUseKActivities(false); } #endif // perform sanity checks if (a.platformName().toLower() != QStringLiteral("xcb")) { fprintf(stderr, "%s: FATAL ERROR expecting platform xcb but got platform %s\n", argv[0], qPrintable(a.platformName())); exit(1); } - if (!KWin::display()) { + if (!QX11Info::display()) { fprintf(stderr, "%s: FATAL ERROR KWin requires Xlib support in the xcb plugin. Do not configure Qt with -no-xcb-xlib\n", argv[0]); exit(1); } // find and load the X11 platform plugin const auto plugins = KPluginLoader::findPluginsById(QStringLiteral("org.kde.kwin.platforms"), QStringLiteral("KWinX11Platform")); if (plugins.isEmpty()) { std::cerr << "FATAL ERROR: KWin could not find the KWinX11Platform plugin" << std::endl; return 1; } a.initPlatform(plugins.first()); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate the platform plugin" << std::endl; return 1; } a.start(); KWin::SessionSaveDoneHelper helper; Q_UNUSED(helper); // The sessionsavedonehelper opens a side channel to the smserver, // listens for events and talks to it, so it needs to be created. return a.exec(); } diff --git a/plugins/platforms/x11/common/eglonxbackend.cpp b/plugins/platforms/x11/common/eglonxbackend.cpp index 78e0ce4de..077d9b831 100644 --- a/plugins/platforms/x11/common/eglonxbackend.cpp +++ b/plugins/platforms/x11/common/eglonxbackend.cpp @@ -1,543 +1,543 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010, 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "eglonxbackend.h" // kwin #include "main.h" #include "options.h" #include "overlaywindow.h" #include "platform.h" #include "screens.h" #include "xcbutils.h" // kwin libs #include // Qt #include #include // system #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg) namespace KWin { -EglOnXBackend::EglOnXBackend() +EglOnXBackend::EglOnXBackend(Display *display) : AbstractEglBackend() , m_overlayWindow(new OverlayWindow()) , surfaceHasSubPost(0) , m_bufferAge(0) , m_usesOverlayWindow(true) , m_connection(connection()) - , m_x11Display(display()) + , m_x11Display(display) , m_rootWindow(rootWindow()) , m_x11ScreenNumber(kwinApp()->x11ScreenNumber()) { // Egl is always direct rendering setIsDirectRendering(true); } EglOnXBackend::EglOnXBackend(xcb_connection_t *connection, Display *display, xcb_window_t rootWindow, int screenNumber, xcb_window_t renderingWindow) : AbstractEglBackend() , m_overlayWindow(nullptr) , surfaceHasSubPost(0) , m_bufferAge(0) , m_usesOverlayWindow(false) , m_connection(connection) , m_x11Display(display) , m_rootWindow(rootWindow) , m_x11ScreenNumber(screenNumber) , m_renderingWindow(renderingWindow) { // Egl is always direct rendering setIsDirectRendering(true); } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; EglOnXBackend::~EglOnXBackend() { if (isFailed() && m_overlayWindow) { m_overlayWindow->destroy(); } cleanup(); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (m_overlayWindow) { if (overlayWindow()->window()) { overlayWindow()->destroy(); } delete m_overlayWindow; } } void EglOnXBackend::init() { qputenv("EGL_PLATFORM", "x11"); initEGL(); // required to toggle initBufferAge(); // EGL_SWAP_BEHAVIOR_PRESERVED_BIT if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } initKWinGL(); if (!hasGLExtension(QByteArrayLiteral("EGL_KHR_image")) && (!hasGLExtension(QByteArrayLiteral("EGL_KHR_image_base")) || !hasGLExtension(QByteArrayLiteral("EGL_KHR_image_pixmap")))) { setFailed(QStringLiteral("Required support for binding pixmaps to EGLImages not found, disabling compositing")); return; } if (!hasGLExtension(QByteArrayLiteral("GL_OES_EGL_image"))) { setFailed(QStringLiteral("Required extension GL_OES_EGL_image not found, disabling compositing")); return; } // check for EGL_NV_post_sub_buffer and whether it can be used on the surface if (hasGLExtension(QByteArrayLiteral("EGL_NV_post_sub_buffer"))) { if (eglQuerySurface(eglDisplay(), surface(), EGL_POST_SUB_BUFFER_SUPPORTED_NV, &surfaceHasSubPost) == EGL_FALSE) { EGLint error = eglGetError(); if (error != EGL_SUCCESS && error != EGL_BAD_ATTRIBUTE) { setFailed(QStringLiteral("query surface failed")); return; } else { surfaceHasSubPost = EGL_FALSE; } } } initBufferAge(); setSyncsToVBlank(false); setBlocksForRetrace(false); gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); if (surfaceHasSubPost) { qCDebug(KWIN_CORE) << "EGL implementation and surface support eglPostSubBufferNV, let's use it"; if (options->glPreferBufferSwap() != Options::NoSwapEncourage) { // check if swap interval 1 is supported EGLint val; eglGetConfigAttrib(eglDisplay(), config(), EGL_MAX_SWAP_INTERVAL, &val); if (val >= 1) { if (eglSwapInterval(eglDisplay(), 1)) { qCDebug(KWIN_CORE) << "Enabled v-sync"; setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } } else { qCWarning(KWIN_CORE) << "Cannot enable v-sync as max. swap interval is" << val; } } else { // disable v-sync eglSwapInterval(eglDisplay(), 0); } } else { /* In the GLX backend, we fall back to using glCopyPixels if we have no extension providing support for partial screen updates. * However, that does not work in EGL - glCopyPixels with glDrawBuffer(GL_FRONT); does nothing. * Hence we need EGL to preserve the backbuffer for us, so that we can draw the partial updates on it and call * eglSwapBuffers() for each frame. eglSwapBuffers() then does the copy (no page flip possible in this mode), * which means it is slow and not synced to the v-blank. */ qCWarning(KWIN_CORE) << "eglPostSubBufferNV not supported, have to enable buffer preservation - which breaks v-sync and performance"; eglSurfaceAttrib(eglDisplay(), surface(), EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); } initWayland(); } bool EglOnXBackend::initRenderingContext() { initClientExtensions(); EGLDisplay dpy = kwinApp()->platform()->sceneEglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (dpy == EGL_NO_DISPLAY) { const bool havePlatformBase = hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_base")); setHavePlatformBase(havePlatformBase); if (havePlatformBase) { // Make sure that the X11 platform is supported if (!hasClientExtension(QByteArrayLiteral("EGL_EXT_platform_x11")) && !hasClientExtension(QByteArrayLiteral("EGL_KHR_platform_x11"))) { qCWarning(KWIN_CORE) << "EGL_EXT_platform_base is supported, but neither EGL_EXT_platform_x11 nor EGL_KHR_platform_x11 is supported." << "Cannot create EGLDisplay on X11"; return false; } const int attribs[] = { EGL_PLATFORM_X11_SCREEN_EXT, m_x11ScreenNumber, EGL_NONE }; dpy = eglGetPlatformDisplayEXT(EGL_PLATFORM_X11_EXT, m_x11Display, attribs); } else { dpy = eglGetDisplay(m_x11Display); } } if (dpy == EGL_NO_DISPLAY) { qCWarning(KWIN_CORE) << "Failed to get the EGLDisplay"; return false; } setEglDisplay(dpy); initEglAPI(); initBufferConfigs(); if (m_usesOverlayWindow) { if (!overlayWindow()->create()) { qCCritical(KWIN_CORE) << "Could not get overlay window"; return false; } else { overlayWindow()->setup(None); } } if (!createSurfaces()) { qCCritical(KWIN_CORE) << "Creating egl surface failed"; return false; } if (!createContext()) { qCCritical(KWIN_CORE) << "Create OpenGL context failed"; return false; } if (!makeContextCurrent(surface())) { qCCritical(KWIN_CORE) << "Make Context Current failed"; return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_CORE) << "Error occurred while creating context " << error; return false; } return true; } bool EglOnXBackend::createSurfaces() { xcb_window_t window = XCB_WINDOW_NONE; if (m_overlayWindow) { window = m_overlayWindow->window(); } else if (m_renderingWindow) { window = m_renderingWindow; } EGLSurface surface = createSurface(window); if (surface == EGL_NO_SURFACE) { return false; } setSurface(surface); return true; } EGLSurface EglOnXBackend::createSurface(xcb_window_t window) { if (window == XCB_WINDOW_NONE) { return EGL_NO_SURFACE; } EGLSurface surface = EGL_NO_SURFACE; if (havePlatformBase()) { // Note: Window is 64 bits on a 64-bit architecture whereas xcb_window_t is // always 32 bits. eglCreatePlatformWindowSurfaceEXT() expects the // native_window parameter to be pointer to a Window, so this variable // cannot be an xcb_window_t. surface = eglCreatePlatformWindowSurfaceEXT(eglDisplay(), config(), (void *) &window, nullptr); } else { surface = eglCreateWindowSurface(eglDisplay(), config(), window, nullptr); } return surface; } bool EglOnXBackend::initBufferConfigs() { const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT | (supportsBufferAge() ? 0 : EGL_SWAP_BEHAVIOR_PRESERVED_BIT), EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, }; EGLint count; EGLConfig configs[1024]; if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1024, &count) == EGL_FALSE) { qCCritical(KWIN_CORE) << "choose config failed"; return false; } ScopedCPointer attribs(xcb_get_window_attributes_reply(m_connection, xcb_get_window_attributes_unchecked(m_connection, m_rootWindow), nullptr)); if (!attribs) { qCCritical(KWIN_CORE) << "Failed to get window attributes of root window"; return false; } setConfig(configs[0]); for (int i = 0; i < count; i++) { EGLint val; if (eglGetConfigAttrib(eglDisplay(), configs[i], EGL_NATIVE_VISUAL_ID, &val) == EGL_FALSE) { qCCritical(KWIN_CORE) << "egl get config attrib failed"; } if (uint32_t(val) == attribs->visual) { setConfig(configs[i]); break; } } return true; } void EglOnXBackend::present() { if (lastDamage().isEmpty()) return; presentSurface(surface(), lastDamage(), screens()->geometry()); setLastDamage(QRegion()); if (!supportsBufferAge()) { eglWaitGL(); xcb_flush(m_connection); } } void EglOnXBackend::presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry) { if (damage.isEmpty()) { return; } const bool fullRepaint = supportsBufferAge() || (damage == screenGeometry); if (fullRepaint || !surfaceHasSubPost) { if (gs_tripleBufferNeedsDetection) { eglWaitGL(); m_swapProfiler.begin(); } // the entire screen changed, or we cannot do partial updates (which implies we enabled surface preservation) eglSwapBuffers(eglDisplay(), surface); if (gs_tripleBufferNeedsDetection) { eglWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) { // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it if (qstrcmp(qgetenv("__GL_YIELD"), "USLEEP")) { options->setGlPreferBufferSwap(0); eglSwapInterval(eglDisplay(), 0); result = 0; // hint proper behavior qCWarning(KWIN_CORE) << "\nIt seems you are using the nvidia driver without triple buffering\n" "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n" "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n" "For this reason, the tearing prevention has been disabled.\n" "See https://bugs.kde.org/show_bug.cgi?id=322060\n"; } } setBlocksForRetrace(result == 'd'); } } if (supportsBufferAge()) { eglQuerySurface(eglDisplay(), surface, EGL_BUFFER_AGE_EXT, &m_bufferAge); } } else { // a part of the screen changed, and we can use eglPostSubBufferNV to copy the updated area foreach (const QRect & r, damage.rects()) { eglPostSubBufferNV(eglDisplay(), surface, r.left(), screenGeometry.height() - r.bottom() - 1, r.width(), r.height()); } } } void EglOnXBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // TODO: base implementation in OpenGLBackend // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGL::TexturePrivate *EglOnXBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new EglTexture(texture, this); } QRegion EglOnXBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); eglWaitNative(EGL_CORE_NATIVE_ENGINE); return repaint; } void EglOnXBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (m_overlayWindow && overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool EglOnXBackend::usesOverlayWindow() const { return m_usesOverlayWindow; } OverlayWindow* EglOnXBackend::overlayWindow() { return m_overlayWindow; } bool EglOnXBackend::makeContextCurrent(const EGLSurface &surface) { return eglMakeCurrent(eglDisplay(), surface, surface, context()) == EGL_TRUE; } /************************************************ * EglTexture ************************************************/ EglTexture::EglTexture(KWin::SceneOpenGL::Texture *texture, KWin::EglOnXBackend *backend) : AbstractEglTexture(texture, backend) , m_backend(backend) { } EglTexture::~EglTexture() = default; bool EglTexture::loadTexture(WindowPixmap *pixmap) { // first try the Wayland enabled loading if (AbstractEglTexture::loadTexture(pixmap)) { return true; } // did not succeed, try on X11 return loadTexture(pixmap->pixmap(), pixmap->toplevel()->size()); } bool EglTexture::loadTexture(xcb_pixmap_t pix, const QSize &size) { if (!m_backend->isX11TextureFromPixmapSupported()) { return false; } if (pix == XCB_NONE) return false; glGenTextures(1, &m_texture); auto q = texture(); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); const EGLint attribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; setImage(eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)pix, attribs)); if (EGL_NO_IMAGE_KHR == image()) { qCDebug(KWIN_CORE) << "failed to create egl image"; q->unbind(); q->discard(); return false; } glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image()); q->unbind(); q->setYInverted(true); m_size = size; updateMatrix(); return true; } void KWin::EglTexture::onDamage() { if (options->isGlStrictBinding()) { // This is just implemented to be consistent with // the example in mesa/demos/src/egl/opengles1/texture_from_pixmap.c eglWaitNative(EGL_CORE_NATIVE_ENGINE); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES) image()); } GLTexturePrivate::onDamage(); } } // namespace diff --git a/plugins/platforms/x11/common/eglonxbackend.h b/plugins/platforms/x11/common/eglonxbackend.h index d123fee19..941f660d6 100644 --- a/plugins/platforms/x11/common/eglonxbackend.h +++ b/plugins/platforms/x11/common/eglonxbackend.h @@ -1,105 +1,105 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_EGL_ON_X_BACKEND_H #define KWIN_EGL_ON_X_BACKEND_H #include "abstract_egl_backend.h" #include "scene_opengl.h" namespace KWin { /** * @brief OpenGL Backend using Egl windowing system over an X overlay window. **/ class KWIN_EXPORT EglOnXBackend : public AbstractEglBackend { public: - EglOnXBackend(); + EglOnXBackend(Display *display); explicit EglOnXBackend(xcb_connection_t *connection, Display *display, xcb_window_t rootWindow, int screenNumber, xcb_window_t renderingWindow); virtual ~EglOnXBackend(); virtual void screenGeometryChanged(const QSize &size); virtual SceneOpenGL::TexturePrivate *createBackendTexture(SceneOpenGL::Texture *texture); virtual QRegion prepareRenderingFrame(); virtual void endRenderingFrame(const QRegion &damage, const QRegion &damagedRegion); virtual OverlayWindow* overlayWindow() override; virtual bool usesOverlayWindow() const override; void init() override; bool isX11TextureFromPixmapSupported() const { return m_x11TextureFromPixmapSupported; } protected: virtual void present(); void presentSurface(EGLSurface surface, const QRegion &damage, const QRect &screenGeometry); virtual bool createSurfaces(); EGLSurface createSurface(xcb_window_t window); void setHavePlatformBase(bool have) { m_havePlatformBase = have; } bool havePlatformBase() const { return m_havePlatformBase; } bool makeContextCurrent(const EGLSurface &surface); void setX11TextureFromPixmapSupported(bool set) { m_x11TextureFromPixmapSupported = set; } private: bool initBufferConfigs(); bool initRenderingContext(); /** * @brief The OverlayWindow used by this Backend. **/ OverlayWindow *m_overlayWindow; int surfaceHasSubPost; int m_bufferAge; bool m_usesOverlayWindow; xcb_connection_t *m_connection; Display *m_x11Display; xcb_window_t m_rootWindow; int m_x11ScreenNumber; xcb_window_t m_renderingWindow = XCB_WINDOW_NONE; bool m_havePlatformBase = false; bool m_x11TextureFromPixmapSupported = true; friend class EglTexture; }; /** * @brief Texture using an EGLImageKHR. **/ class EglTexture : public AbstractEglTexture { public: virtual ~EglTexture(); virtual void onDamage(); bool loadTexture(WindowPixmap *pixmap) override; private: bool loadTexture(xcb_pixmap_t pix, const QSize &size); friend class EglOnXBackend; EglTexture(SceneOpenGL::Texture *texture, EglOnXBackend *backend); EglOnXBackend *m_backend; }; } // namespace #endif // KWIN_EGL_ON_X_BACKEND_H diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index d2dbada88..59599acd6 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,914 +1,916 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "glxbackend.h" #include "logging.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "screens.h" #include "xcbutils.h" // kwin libs #include #include // Qt #include #include +#include // system #include #include #include #include #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #if __cplusplus <= 201103L namespace std { // C++-14 template unique_ptr make_unique(Args&&... args) { return unique_ptr(new T(std::forward(args)...)); } } #endif namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- -GlxBackend::GlxBackend() +GlxBackend::GlxBackend(Display *display) : OpenGLBackend() , m_overlayWindow(new OverlayWindow()) , window(None) , fbconfig(NULL) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , haveSwapInterval(false) + , m_x11Display(display) { } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } typedef void (*glXFuncPtr)(); static glXFuncPtr getProcAddress(const char* name) { glXFuncPtr ret = nullptr; #if HAVE_EPOXY_GLX ret = glXGetProcAddress((const GLubyte*) name); #endif if (ret == nullptr) ret = (glXFuncPtr) dlsym(RTLD_DEFAULT, name); return ret; } glXSwapIntervalMESA_func glXSwapIntervalMESA; void GlxBackend::init() { // Require at least GLX 1.3 if (!checkVersion()) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initExtensions(); // resolve glXSwapIntervalMESA if available if (hasExtension(QByteArrayLiteral("GLX_MESA_swap_control"))) { glXSwapIntervalMESA = (glXSwapIntervalMESA_func) getProcAddress("glXSwapIntervalMESA"); } else { glXSwapIntervalMESA = nullptr; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(&getProcAddress); // Check whether certain features are supported m_haveMESACopySubBuffer = hasExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasExtension(QByteArrayLiteral("GLX_EXT_swap_control")); m_haveSGISwapControl = hasExtension(QByteArrayLiteral("GLX_SGI_swap_control")); // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; setSupportsBufferAge(false); if (hasExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } setSyncsToVBlank(false); setBlocksForRetrace(false); haveWaitSync = false; gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable setSwapInterval(1); setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } else if (hasExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { unsigned int sync; if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { setSyncsToVBlank(true); setBlocksForRetrace(true); haveWaitSync = true; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. glXQueryDrawable = NULL; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::checkVersion() { int major, minor; glXQueryVersion(display(), &major, &minor); return kVersionNumber(major, minor) >= kVersionNumber(1, 3); } void GlxBackend::initExtensions() { const QByteArray string = (const char *) glXQueryExtensionsString(display(), QX11Info::appScreen()); setExtensions(string.split(' ')); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const int attribs_31_core_robustness[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, 0 }; const int attribs_31_core[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 1, 0 }; const int attribs_legacy_robustness[] = { GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, 0 }; const int attribs_legacy[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 1, GLX_CONTEXT_MINOR_VERSION_ARB, 2, 0 }; const bool have_robustness = hasExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); // Try to create a 3.1 context first if (options->glCoreProfile()) { if (have_robustness) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core_robustness); if (!ctx) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core); } if (!ctx && have_robustness) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy_robustness); if (!ctx) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy); } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); ctx = 0; return false; } return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); glxWindow = glXCreateWindow(display(), fbconfig, window, NULL); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; // Try to find a double buffered configuration int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); if (!direct) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::setSwapInterval(int interval) { if (m_haveEXTSwapControl) glXSwapIntervalEXT(display(), glxWindow, interval); else if (m_haveMESASwapControl) glXSwapIntervalMESA(interval); else if (m_haveSGISwapControl) glXSwapIntervalSGI(interval); } void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering if (haveWaitSync) { uint sync; #if 0 // TODO: why precisely is this important? // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync // but this only leads to waiting for two frames??!? glXGetVideoSync(&sync); glXWaitVideoSync(2, (sync + 1) % 2, &sync); #else glXWaitVideoSyncSGI(1, 0, &sync); #endif } } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); if (fullRepaint) { if (m_haveINTELSwapEvent) Compositor::self()->aboutToSwapBuffers(); if (haveSwapInterval) { if (gs_tripleBufferNeedsDetection) { glXWaitGL(); m_swapProfiler.begin(); } glXSwapBuffers(display(), glxWindow); if (gs_tripleBufferNeedsDetection) { glXWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) { // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it if (qstrcmp(qgetenv("__GL_YIELD"), "USLEEP")) { options->setGlPreferBufferSwap(0); setSwapInterval(0); result = 0; // hint proper behavior qCWarning(KWIN_X11STANDALONE) << "\nIt seems you are using the nvidia driver without triple buffering\n" "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n" "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n" "For this reason, the tearing prevention has been disabled.\n" "See https://bugs.kde.org/show_bug.cgi?id=322060\n"; } } setBlocksForRetrace(result == 'd'); } } else if (blocksForRetrace()) { // at least the nvidia blob manages to swap async, ie. return immediately on double // buffering - what messes our timing calculation and leads to laggy behavior #346275 glXWaitGL(); } } else { waitSync(); glXSwapBuffers(display(), glxWindow); } if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { foreach (const QRect & r, lastDamage().rects()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); SceneOpenGL::copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGL::TexturePrivate *GlxBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); glXWaitX(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGL::Texture *texture, GlxBackend *backend) : SceneOpenGL::TexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { assert(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace diff --git a/plugins/platforms/x11/standalone/glxbackend.h b/plugins/platforms/x11/standalone/glxbackend.h index 1f801df7b..59787419e 100644 --- a/plugins/platforms/x11/standalone/glxbackend.h +++ b/plugins/platforms/x11/standalone/glxbackend.h @@ -1,139 +1,146 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_GLX_BACKEND_H #define KWIN_GLX_BACKEND_H #include "scene_opengl.h" #include "x11eventfilter.h" #include #include #include namespace KWin { // GLX_MESA_swap_interval using glXSwapIntervalMESA_func = int (*)(unsigned int interval); extern glXSwapIntervalMESA_func glXSwapIntervalMESA; class FBConfigInfo { public: GLXFBConfig fbconfig; int bind_texture_format; int texture_targets; int y_inverted; int mipmap; }; // ------------------------------------------------------------------ class SwapEventFilter : public X11EventFilter { public: SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable); bool event(xcb_generic_event_t *event) override; private: xcb_drawable_t m_drawable; xcb_glx_drawable_t m_glxDrawable; }; /** * @brief OpenGL Backend using GLX over an X overlay window. **/ class GlxBackend : public OpenGLBackend { public: - GlxBackend(); + GlxBackend(Display *display); virtual ~GlxBackend(); virtual void screenGeometryChanged(const QSize &size); virtual SceneOpenGL::TexturePrivate *createBackendTexture(SceneOpenGL::Texture *texture); virtual QRegion prepareRenderingFrame(); virtual void endRenderingFrame(const QRegion &damage, const QRegion &damagedRegion); virtual bool makeCurrent() override; virtual void doneCurrent() override; virtual OverlayWindow* overlayWindow() override; virtual bool usesOverlayWindow() const override; void init() override; protected: virtual void present(); private: bool initBuffer(); bool checkVersion(); void initExtensions(); void waitSync(); bool initRenderingContext(); bool initFbConfig(); void initVisualDepthHashTable(); void setSwapInterval(int interval); + Display *display() const { + return m_x11Display; + } int visualDepth(xcb_visualid_t visual) const; FBConfigInfo *infoForVisual(xcb_visualid_t visual); /** * @brief The OverlayWindow used by this Backend. **/ OverlayWindow *m_overlayWindow; Window window; GLXFBConfig fbconfig; GLXWindow glxWindow; GLXContext ctx; QHash m_fbconfigHash; QHash m_visualDepthHash; std::unique_ptr m_swapEventFilter; int m_bufferAge; bool m_haveMESACopySubBuffer = false; bool m_haveMESASwapControl = false; bool m_haveEXTSwapControl = false; bool m_haveSGISwapControl = false; bool m_haveINTELSwapEvent = false; bool haveSwapInterval = false; bool haveWaitSync = false; + Display *m_x11Display; friend class GlxTexture; }; /** * @brief Texture using an GLXPixmap. **/ class GlxTexture : public SceneOpenGL::TexturePrivate { public: virtual ~GlxTexture(); virtual void onDamage(); virtual bool loadTexture(WindowPixmap *pixmap) override; virtual OpenGLBackend *backend(); private: friend class GlxBackend; GlxTexture(SceneOpenGL::Texture *texture, GlxBackend *backend); bool loadTexture(xcb_pixmap_t pix, const QSize &size, xcb_visualid_t visual); + Display *display() const { + return m_backend->m_x11Display; + } SceneOpenGL::Texture *q; GlxBackend *m_backend; GLXPixmap m_glxpixmap; // the glx pixmap the texture is bound to }; } // namespace #endif // KWIN_GLX_BACKEND_H diff --git a/plugins/platforms/x11/standalone/x11_platform.cpp b/plugins/platforms/x11/standalone/x11_platform.cpp index 0c1dc1945..73db0921f 100644 --- a/plugins/platforms/x11/standalone/x11_platform.cpp +++ b/plugins/platforms/x11/standalone/x11_platform.cpp @@ -1,275 +1,276 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11_platform.h" #include "x11cursor.h" #include "edge.h" #include #include #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif #if HAVE_X11_XINPUT #include "xinputintegration.h" #endif #include "eglonxbackend.h" #include "keyboard_input.h" #include "logging.h" #include "screens_xrandr.h" #include "options.h" #include #include #include #include #include namespace KWin { X11StandalonePlatform::X11StandalonePlatform(QObject *parent) : Platform(parent) + , m_x11Display(QX11Info::display()) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { - m_xinputIntegration = new XInputIntegration(this); + m_xinputIntegration = new XInputIntegration(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { delete m_xinputIntegration; m_xinputIntegration = nullptr; } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening); } } #endif } X11StandalonePlatform::~X11StandalonePlatform() = default; void X11StandalonePlatform::init() { if (!QX11Info::isPlatformX11()) { emit initFailed(); return; } setReady(true); emit screensQueried(); } Screens *X11StandalonePlatform::createScreens(QObject *parent) { return new XRandRScreens(parent); } OpenGLBackend *X11StandalonePlatform::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_EPOXY_GLX case GlxPlatformInterface: if (hasGlx()) { - return new GlxBackend(); + return new GlxBackend(m_x11Display); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through } #endif case EglPlatformInterface: - return new EglOnXBackend(); + return new EglOnXBackend(m_x11Display); default: // no backend available return nullptr; } } Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges) { return new WindowBasedEdge(edges); } void X11StandalonePlatform::createPlatformCursor(QObject *parent) { auto c = new X11Cursor(parent, m_xinputIntegration != nullptr); #if HAVE_X11_XINPUT if (m_xinputIntegration) { m_xinputIntegration->setCursor(c); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); m_xinputIntegration->setXkb(xkb); xkb->reconfigure(); } #endif } bool X11StandalonePlatform::requiresCompositing() const { return false; } bool X11StandalonePlatform::openGLCompositingIsBroken() const { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false); } QString X11StandalonePlatform::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

If you think that you have meanwhile upgraded to a stable driver,
" "you can reset this protection but be aware that this might result in an immediate crash!

" "

Alternatively, you might want to use the XRender backend instead.

"); if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } #if !defined( KWIN_HAVE_XRENDER_COMPOSITING ) if (!hasGlx()) return i18n("GLX/OpenGL are not available and only OpenGL support is compiled."); #else if (!(hasGlx() || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) { return i18n("GLX/OpenGL and XRender/XFixes are not available."); } #endif return QString(); } bool X11StandalonePlatform::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return false; if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCDebug(KWIN_CORE) << "No composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCDebug(KWIN_CORE) << "No damage extension available"; return false; } if (hasGlx()) return true; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()) return true; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support"; return false; } bool X11StandalonePlatform::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); switch (safePoint) { case OpenGLSafePoint::PreInit: group.writeEntry(unsafeKey, true); group.sync(); // Deliberately continue with PreFrame case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = new QThread(this); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = new QTimer; m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread); connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection, [] { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); group.writeEntry(unsafeKey, true); group.sync(); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.writeEntry(unsafeKey, false); group.sync(); // Deliberately continue with PostFrame case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtection->deleteLater(); m_openGLFreezeProtection = nullptr; m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; m_openGLFreezeProtectionThread = nullptr; break; } } PlatformCursorImage X11StandalonePlatform::cursorImage() const { auto c = kwinApp()->x11Connection(); QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (cursor.isNull()) { return PlatformCursorImage(); } QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandalonePlatform::doHideCursor() { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::doShowCursor() { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } } diff --git a/plugins/platforms/x11/standalone/x11_platform.h b/plugins/platforms/x11/standalone/x11_platform.h index 4a17f881f..e4811f1ae 100644 --- a/plugins/platforms/x11/standalone/x11_platform.h +++ b/plugins/platforms/x11/standalone/x11_platform.h @@ -1,78 +1,79 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_X11STANDALONE_PLATFORM_H #define KWIN_X11STANDALONE_PLATFORM_H #include "platform.h" #include #include namespace KWin { class XInputIntegration; class KWIN_EXPORT X11StandalonePlatform : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") public: X11StandalonePlatform(QObject *parent = nullptr); virtual ~X11StandalonePlatform(); void init() override; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; Edge *createScreenEdge(ScreenEdges *parent) override; void createPlatformCursor(QObject *parent = nullptr) override; bool requiresCompositing() const override; bool compositingPossible() const override; QString compositingNotPossibleReason() const override; bool openGLCompositingIsBroken() const override; void createOpenGLSafePoint(OpenGLSafePoint safePoint) override; PlatformCursorImage cursorImage() const override; protected: void doHideCursor() override; void doShowCursor() override; private: /** * Tests whether GLX is supported and returns @c true * in case KWin is compiled with OpenGL support and GLX * is available. * * If KWin is compiled with OpenGL ES or without OpenGL at * all, @c false is returned. * @returns @c true if GLX is available, @c false otherwise and if not build with OpenGL support. **/ static bool hasGlx(); XInputIntegration *m_xinputIntegration = nullptr; QThread *m_openGLFreezeProtectionThread = nullptr; QTimer *m_openGLFreezeProtection = nullptr; + Display *m_x11Display; }; } #endif diff --git a/plugins/platforms/x11/standalone/xinputintegration.cpp b/plugins/platforms/x11/standalone/xinputintegration.cpp index 490f15081..a944a55c1 100644 --- a/plugins/platforms/x11/standalone/xinputintegration.cpp +++ b/plugins/platforms/x11/standalone/xinputintegration.cpp @@ -1,245 +1,246 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "xinputintegration.h" #include "main.h" #include "logging.h" #include "platform.h" #include "x11cursor.h" #include "keyboard_input.h" #include "x11eventfilter.h" #include #include #include #include namespace KWin { class XInputEventFilter : public X11EventFilter { public: XInputEventFilter(int xi_opcode) : X11EventFilter(XCB_GE_GENERIC, xi_opcode, QVector{XI_RawMotion, XI_RawButtonPress, XI_RawButtonRelease, XI_RawKeyPress, XI_RawKeyRelease}) {} virtual ~XInputEventFilter() = default; bool event(xcb_generic_event_t *event) override { xcb_ge_generic_event_t *ge = reinterpret_cast(event); switch (ge->event_type) { case XI_RawKeyPress: if (m_xkb) { m_xkb->updateKey(reinterpret_cast(event)->detail - 8, InputRedirection::KeyboardKeyPressed); } break; case XI_RawKeyRelease: if (m_xkb) { m_xkb->updateKey(reinterpret_cast(event)->detail - 8, InputRedirection::KeyboardKeyReleased); } break; case XI_RawButtonPress: if (m_xkb) { auto e = reinterpret_cast(event); switch (e->detail) { // TODO: this currently ignores left handed settings, for current usage not needed // if we want to use also for global mouse shortcuts, this needs to reflect state correctly case XCB_BUTTON_INDEX_1: kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, e->time); break; case XCB_BUTTON_INDEX_2: kwinApp()->platform()->pointerButtonPressed(BTN_MIDDLE, e->time); break; case XCB_BUTTON_INDEX_3: kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, e->time); break; case XCB_BUTTON_INDEX_4: case XCB_BUTTON_INDEX_5: // vertical axis, ignore on press break; // TODO: further buttons, horizontal scrolling? } } if (m_x11Cursor) { m_x11Cursor->schedulePoll(); } break; case XI_RawButtonRelease: if (m_xkb) { auto e = reinterpret_cast(event); switch (e->detail) { // TODO: this currently ignores left handed settings, for current usage not needed // if we want to use also for global mouse shortcuts, this needs to reflect state correctly case XCB_BUTTON_INDEX_1: kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, e->time); break; case XCB_BUTTON_INDEX_2: kwinApp()->platform()->pointerButtonReleased(BTN_MIDDLE, e->time); break; case XCB_BUTTON_INDEX_3: kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, e->time); break; case XCB_BUTTON_INDEX_4: kwinApp()->platform()->pointerAxisVertical(120, e->time); break; case XCB_BUTTON_INDEX_5: kwinApp()->platform()->pointerAxisVertical(-120, e->time); break; // TODO: further buttons, horizontal scrolling? } } if (m_x11Cursor) { m_x11Cursor->schedulePoll(); } break; default: if (m_x11Cursor) { m_x11Cursor->schedulePoll(); } break; } return false; } void setCursor(const QPointer &cursor) { m_x11Cursor = cursor; } void setXkb(Xkb *xkb) { m_xkb = xkb; } private: QPointer m_x11Cursor; // TODO: QPointer Xkb *m_xkb = nullptr; }; class XKeyPressReleaseEventFilter : public X11EventFilter { public: XKeyPressReleaseEventFilter(uint32_t type) : X11EventFilter(type) {} ~XKeyPressReleaseEventFilter() = default; bool event(xcb_generic_event_t *event) override { xcb_key_press_event_t *ke = reinterpret_cast(event); if (m_xkb && ke->event == ke->root) { const uint8_t eventType = event->response_type & ~0x80; if (eventType == XCB_KEY_PRESS) { m_xkb->updateKey(ke->detail - 8, InputRedirection::KeyboardKeyPressed); } else { m_xkb->updateKey(ke->detail - 8, InputRedirection::KeyboardKeyReleased); } } return false; } void setXkb(Xkb *xkb) { m_xkb = xkb; } private: // TODO: QPointer Xkb *m_xkb = nullptr; }; -XInputIntegration::XInputIntegration(QObject *parent) +XInputIntegration::XInputIntegration(Display *display, QObject *parent) : QObject(parent) + , m_x11Display(display) { } XInputIntegration::~XInputIntegration() = default; void XInputIntegration::init() { Display *dpy = display(); int xi_opcode, event, error; // init XInput extension if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) { qCDebug(KWIN_X11STANDALONE) << "XInputExtension not present"; return; } // verify that the XInput extension is at at least version 2.0 int major = 2, minor = 0; int result = XIQueryVersion(dpy, &major, &minor); if (result == BadImplementation) { // Xinput 2.2 returns BadImplementation if checked against 2.0 major = 2; minor = 2; if (XIQueryVersion(dpy, &major, &minor) != Success) { qCDebug(KWIN_X11STANDALONE) << "Failed to init XInput"; return; } } else if (result != Success) { qCDebug(KWIN_X11STANDALONE) << "Failed to init XInput"; return; } m_hasXInput = true; m_xiOpcode = xi_opcode; m_majorVersion = major; m_minorVersion = minor; qCDebug(KWIN_X11STANDALONE) << "Has XInput support" << m_majorVersion << "." << m_minorVersion; } void XInputIntegration::setCursor(X11Cursor *cursor) { m_x11Cursor = QPointer(cursor); } void XInputIntegration::setXkb(Xkb *xkb) { m_xkb = xkb; } void XInputIntegration::startListening() { // this assumes KWin is the only one setting events on the root window // given Qt's source code this seems to be true. If it breaks, we need to change XIEventMask evmasks[1]; unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; memset(mask1, 0, sizeof(mask1)); XISetMask(mask1, XI_RawMotion); XISetMask(mask1, XI_RawButtonPress); XISetMask(mask1, XI_RawButtonRelease); if (m_majorVersion >= 2 && m_minorVersion >= 1) { // we need to listen to all events, which is only available with XInput 2.1 XISetMask(mask1, XI_RawKeyPress); XISetMask(mask1, XI_RawKeyRelease); } evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask1); evmasks[0].mask = mask1; XISelectEvents(display(), rootWindow(), evmasks, 1); m_xiEventFilter.reset(new XInputEventFilter(m_xiOpcode)); m_xiEventFilter->setCursor(m_x11Cursor); m_xiEventFilter->setXkb(m_xkb); m_keyPressFilter.reset(new XKeyPressReleaseEventFilter(XCB_KEY_PRESS)); m_keyPressFilter->setXkb(m_xkb); m_keyReleaseFilter.reset(new XKeyPressReleaseEventFilter(XCB_KEY_RELEASE)); m_keyReleaseFilter->setXkb(m_xkb); } } diff --git a/plugins/platforms/x11/standalone/xinputintegration.h b/plugins/platforms/x11/standalone/xinputintegration.h index a81856917..ba1ff4b38 100644 --- a/plugins/platforms/x11/standalone/xinputintegration.h +++ b/plugins/platforms/x11/standalone/xinputintegration.h @@ -1,68 +1,73 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_XINPUTINTEGRATION_H #define KWIN_XINPUTINTEGRATION_H #include #include #include +typedef struct _XDisplay Display; namespace KWin { class XInputEventFilter; class XKeyPressReleaseEventFilter; class X11Cursor; class Xkb; class XInputIntegration : public QObject { Q_OBJECT public: - explicit XInputIntegration(QObject *parent); + explicit XInputIntegration(Display *display, QObject *parent); virtual ~XInputIntegration(); void init(); void startListening(); bool hasXinput() const { return m_hasXInput; } void setCursor(X11Cursor *cursor); void setXkb(Xkb *xkb); private: + Display *display() const { + return m_x11Display; + } bool m_hasXInput = false; int m_xiOpcode = 0; int m_majorVersion = 0; int m_minorVersion = 0; QPointer m_x11Cursor; // TODO: QPointer Xkb *m_xkb = nullptr; + Display *m_x11Display; QScopedPointer m_xiEventFilter; QScopedPointer m_keyPressFilter; QScopedPointer m_keyReleaseFilter; }; } #endif diff --git a/tests/screenedgeshowtest.cpp b/tests/screenedgeshowtest.cpp index ec3f2de8f..804fd366d 100644 --- a/tests/screenedgeshowtest.cpp +++ b/tests/screenedgeshowtest.cpp @@ -1,360 +1,361 @@ /* * Copyright 2014 Martin Gräßlin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include +#include #include "../xcbutils.h" #include #include #include #include #include class ScreenEdgeHelper : public QObject { Q_OBJECT protected: ScreenEdgeHelper(QWidget *widget, QObject *parent = nullptr); QWindow *window() const { return m_widget->windowHandle(); } virtual void restore() = 0; public: virtual ~ScreenEdgeHelper(); virtual void hide() = 0; virtual void raiseOrShow(bool raise) = 0; virtual void init() {}; virtual void moveToTop(); virtual void moveToRight(); virtual void moveToBottom(); virtual void moveToLeft(); virtual void moveToFloating(); void hideAndRestore() { hide(); m_timer->start(10000); } private: QWidget *m_widget; QTimer *m_timer; }; class ScreenEdgeHelperX11 : public ScreenEdgeHelper { Q_OBJECT public: ScreenEdgeHelperX11(QWidget *widget, QObject *parent = nullptr); virtual ~ScreenEdgeHelperX11() = default; void hide() override; void raiseOrShow(bool raise) override; void moveToTop() override; void moveToRight() override; void moveToBottom() override; void moveToLeft() override; void moveToFloating() override; protected: void restore() override; private: uint32_t m_locationValue = 2; uint32_t m_actionValue = 0; KWin::Xcb::Atom m_atom; }; class ScreenEdgeHelperWayland : public ScreenEdgeHelper { Q_OBJECT public: ScreenEdgeHelperWayland(QWidget *widget, QObject *parent = nullptr); virtual ~ScreenEdgeHelperWayland() = default; void hide() override; void raiseOrShow(bool raise) override; void init() override; bool eventFilter(QObject * watched, QEvent * event) override; protected: void restore() override; private: void setupSurface(); KWayland::Client::PlasmaShell *m_shell = nullptr; KWayland::Client::PlasmaShellSurface *m_shellSurface = nullptr; bool m_autoHide = true; }; ScreenEdgeHelper::ScreenEdgeHelper(QWidget *widget, QObject *parent) : QObject(parent) , m_widget(widget) , m_timer(new QTimer(this)) { m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &ScreenEdgeHelper::restore); } ScreenEdgeHelper::~ScreenEdgeHelper() = default; void ScreenEdgeHelper::moveToTop() { const QRect geo = QGuiApplication::primaryScreen()->geometry(); m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100); } void ScreenEdgeHelper::moveToRight() { const QRect geo = QGuiApplication::primaryScreen()->geometry(); m_widget->setGeometry(geo.x(), geo.y(), geo.width(), 100); } void ScreenEdgeHelper::moveToBottom() { const QRect geo = QGuiApplication::primaryScreen()->geometry(); m_widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100); } void ScreenEdgeHelper::moveToLeft() { const QRect geo = QGuiApplication::primaryScreen()->geometry(); m_widget->setGeometry(geo.x(), geo.y(), 100, geo.height()); } void ScreenEdgeHelper::moveToFloating() { const QRect geo = QGuiApplication::primaryScreen()->geometry(); m_widget->setGeometry(QRect(geo.center(), QSize(100, 100))); } ScreenEdgeHelperX11::ScreenEdgeHelperX11(QWidget *widget, QObject *parent) : ScreenEdgeHelper(widget, parent) , m_atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW")) { } void ScreenEdgeHelperX11::hide() { uint32_t value = m_locationValue | (m_actionValue << 8); xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window()->winId(), m_atom, XCB_ATOM_CARDINAL, 32, 1, &value); } void ScreenEdgeHelperX11::restore() { xcb_delete_property(QX11Info::connection(), window()->winId(), m_atom); } void ScreenEdgeHelperX11::raiseOrShow(bool raise) { m_actionValue = raise ? 1: 0; } void ScreenEdgeHelperX11::moveToBottom() { ScreenEdgeHelper::moveToBottom(); m_locationValue = 2; } void ScreenEdgeHelperX11::moveToFloating() { ScreenEdgeHelper::moveToFloating(); m_locationValue = 4; } void ScreenEdgeHelperX11::moveToLeft() { ScreenEdgeHelper::moveToLeft(); m_locationValue = 3; } void ScreenEdgeHelperX11::moveToRight() { ScreenEdgeHelper::moveToRight(); m_locationValue = 1; } void ScreenEdgeHelperX11::moveToTop() { ScreenEdgeHelper::moveToTop(); m_locationValue = 0; } using namespace KWayland::Client; ScreenEdgeHelperWayland::ScreenEdgeHelperWayland(QWidget *widget, QObject *parent) : ScreenEdgeHelper(widget, parent) { ConnectionThread *connection = ConnectionThread::fromApplication(this); Registry *registry = new Registry(connection); registry->create(connection); connect(registry, &Registry::interfacesAnnounced, this, [registry, this] { const auto interface = registry->interface(Registry::Interface::PlasmaShell); if (interface.name == 0) { return; } m_shell = registry->createPlasmaShell(interface.name, interface.version); } ); registry->setup(); connection->roundtrip(); } void ScreenEdgeHelperWayland::init() { window()->installEventFilter(this); setupSurface(); } void ScreenEdgeHelperWayland::setupSurface() { if (!m_shell) { return; } if (auto s = Surface::fromWindow(window())) { m_shellSurface = m_shell->createSurface(s, window()); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); m_shellSurface->setPosition(window()->position()); } } void ScreenEdgeHelperWayland::hide() { if (m_shellSurface && m_autoHide) { m_shellSurface->requestHideAutoHidingPanel(); } } void ScreenEdgeHelperWayland::restore() { if (m_shellSurface && m_autoHide) { m_shellSurface->requestShowAutoHidingPanel(); } } void ScreenEdgeHelperWayland::raiseOrShow(bool raise) { m_autoHide = !raise; if (m_shellSurface) { if (raise) { m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsCanCover); } else { m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); } } } bool ScreenEdgeHelperWayland::eventFilter(QObject *watched, QEvent *event) { if (watched != window() || !m_shell) { return false; } if (event->type() == QEvent::PlatformSurface) { QPlatformSurfaceEvent *pe = static_cast(event); if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { setupSurface(); } else { delete m_shellSurface; m_shellSurface = nullptr; } } if (event->type() == QEvent::Move) { if (m_shellSurface) { m_shellSurface->setPosition(window()->position()); } } return false; } int main(int argc, char **argv) { QApplication app(argc, argv); QApplication::setApplicationDisplayName(QStringLiteral("Screen Edge Show Test App")); ScreenEdgeHelper *helper = nullptr; QScopedPointer widget(new QWidget(nullptr, Qt::FramelessWindowHint)); if (KWindowSystem::isPlatformX11()) { app.setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); helper = new ScreenEdgeHelperX11(widget.data(), &app); } else if (KWindowSystem::isPlatformWayland()) { helper = new ScreenEdgeHelperWayland(widget.data(), &app); } if (!helper) { return 2; } QPushButton *hideWindowButton = new QPushButton(QStringLiteral("Hide"), widget.data()); QObject::connect(hideWindowButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hide); QPushButton *hideAndRestoreButton = new QPushButton(QStringLiteral("Hide and Restore after 10 sec"), widget.data()); QObject::connect(hideAndRestoreButton, &QPushButton::clicked, helper, &ScreenEdgeHelper::hideAndRestore); QToolButton *edgeButton = new QToolButton(widget.data()); QCheckBox *raiseCheckBox = new QCheckBox("Raise:", widget.data()); QObject::connect(raiseCheckBox, &QCheckBox::toggled, helper, &ScreenEdgeHelper::raiseOrShow); edgeButton->setText(QStringLiteral("Edge")); edgeButton->setPopupMode(QToolButton::MenuButtonPopup); QMenu *edgeButtonMenu = new QMenu(edgeButton); QObject::connect(edgeButtonMenu->addAction("Top"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToTop); QObject::connect(edgeButtonMenu->addAction("Right"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToRight); QObject::connect(edgeButtonMenu->addAction("Bottom"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToBottom); QObject::connect(edgeButtonMenu->addAction("Left"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToLeft); edgeButtonMenu->addSeparator(); QObject::connect(edgeButtonMenu->addAction("Floating"), &QAction::triggered, helper, &ScreenEdgeHelper::moveToFloating); edgeButton->setMenu(edgeButtonMenu); QHBoxLayout *layout = new QHBoxLayout(widget.data()); layout->addWidget(hideWindowButton); layout->addWidget(hideAndRestoreButton); layout->addWidget(edgeButton); widget->setLayout(layout); const QRect geo = QGuiApplication::primaryScreen()->geometry(); widget->setGeometry(geo.x(), geo.y() + geo.height() - 100, geo.width(), 100); widget->show(); helper->init(); return app.exec(); } #include "screenedgeshowtest.moc" diff --git a/utils.cpp b/utils.cpp index ad45406e5..afa631c09 100644 --- a/utils.cpp +++ b/utils.cpp @@ -1,246 +1,247 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file is for (very) small utility functions/classes. */ #include "utils.h" #include #include #ifndef KCMRULES #include #include #include +#include #include #include #include "atoms.h" #include "workspace.h" #include #endif Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtCriticalMsg) namespace KWin { #ifndef KCMRULES //************************************ // StrutRect //************************************ StrutRect::StrutRect(QRect rect, StrutArea area) : QRect(rect) , m_area(area) { } StrutRect::StrutRect(const StrutRect& other) : QRect(other) , m_area(other.area()) { } #endif #ifndef KCMRULES /* Updates xTime(). This used to simply fetch current timestamp from the server, but that can cause xTime() to be newer than timestamp of events that are still in our events queue, thus e.g. making XSetInputFocus() caused by such event to be ignored. Therefore events queue is searched for first event with timestamp, and extra PropertyNotify is generated in order to make sure such event is found. */ void updateXTime() { // NOTE: QX11Info::getTimestamp does not yet search the event queue as the old // solution did. This means there might be regressions currently. See the // documentation above on how it should be done properly. kwinApp()->setX11Time(QX11Info::getTimestamp()); } static int server_grab_count = 0; void grabXServer() { if (++server_grab_count == 1) xcb_grab_server(connection()); } void ungrabXServer() { assert(server_grab_count > 0); if (--server_grab_count == 0) { xcb_ungrab_server(connection()); xcb_flush(connection()); } } bool grabbedXServer() { return server_grab_count > 0; } static bool keyboard_grabbed = false; bool grabXKeyboard(xcb_window_t w) { if (QWidget::keyboardGrabber() != NULL) return false; if (keyboard_grabbed) return false; if (qApp->activePopupWidget() != NULL) return false; if (w == XCB_WINDOW_NONE) w = rootWindow(); const xcb_grab_keyboard_cookie_t c = xcb_grab_keyboard_unchecked(connection(), false, w, xTime(), XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); ScopedCPointer grab(xcb_grab_keyboard_reply(connection(), c, NULL)); if (grab.isNull()) { return false; } if (grab->status != XCB_GRAB_STATUS_SUCCESS) { return false; } keyboard_grabbed = true; return true; } void ungrabXKeyboard() { if (!keyboard_grabbed) { // grabXKeyboard() may fail sometimes, so don't fail, but at least warn anyway qCDebug(KWIN_CORE) << "ungrabXKeyboard() called but keyboard not grabbed!"; } keyboard_grabbed = false; xcb_ungrab_keyboard(connection(), XCB_TIME_CURRENT_TIME); } Process::Process(QObject *parent) : QProcess(parent) { } Process::~Process() = default; void Process::setupChildProcess() { sigset_t userSiganls; sigemptyset(&userSiganls); sigaddset(&userSiganls, SIGUSR1); sigaddset(&userSiganls, SIGUSR2); pthread_sigmask(SIG_UNBLOCK, &userSiganls, nullptr); } #endif // converting between X11 mouse/keyboard state mask and Qt button/keyboard states int qtToX11Button(Qt::MouseButton button) { if (button == Qt::LeftButton) return XCB_BUTTON_INDEX_1; else if (button == Qt::MidButton) return XCB_BUTTON_INDEX_2; else if (button == Qt::RightButton) return XCB_BUTTON_INDEX_3; return XCB_BUTTON_INDEX_ANY; // 0 } Qt::MouseButton x11ToQtMouseButton(int button) { if (button == XCB_BUTTON_INDEX_1) return Qt::LeftButton; if (button == XCB_BUTTON_INDEX_2) return Qt::MidButton; if (button == XCB_BUTTON_INDEX_3) return Qt::RightButton; if (button == XCB_BUTTON_INDEX_4) return Qt::XButton1; if (button == XCB_BUTTON_INDEX_5) return Qt::XButton2; return Qt::NoButton; } int qtToX11State(Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) { int ret = 0; if (buttons & Qt::LeftButton) ret |= XCB_KEY_BUT_MASK_BUTTON_1; if (buttons & Qt::MidButton) ret |= XCB_KEY_BUT_MASK_BUTTON_2; if (buttons & Qt::RightButton) ret |= XCB_KEY_BUT_MASK_BUTTON_3; if (modifiers & Qt::ShiftModifier) ret |= XCB_KEY_BUT_MASK_SHIFT; if (modifiers & Qt::ControlModifier) ret |= XCB_KEY_BUT_MASK_CONTROL; if (modifiers & Qt::AltModifier) ret |= KKeyServer::modXAlt(); if (modifiers & Qt::MetaModifier) ret |= KKeyServer::modXMeta(); return ret; } Qt::MouseButtons x11ToQtMouseButtons(int state) { Qt::MouseButtons ret = 0; if (state & XCB_KEY_BUT_MASK_BUTTON_1) ret |= Qt::LeftButton; if (state & XCB_KEY_BUT_MASK_BUTTON_2) ret |= Qt::MidButton; if (state & XCB_KEY_BUT_MASK_BUTTON_3) ret |= Qt::RightButton; if (state & XCB_KEY_BUT_MASK_BUTTON_4) ret |= Qt::XButton1; if (state & XCB_KEY_BUT_MASK_BUTTON_5) ret |= Qt::XButton2; return ret; } Qt::KeyboardModifiers x11ToQtKeyboardModifiers(int state) { Qt::KeyboardModifiers ret = 0; if (state & XCB_KEY_BUT_MASK_SHIFT) ret |= Qt::ShiftModifier; if (state & XCB_KEY_BUT_MASK_CONTROL) ret |= Qt::ControlModifier; if (state & KKeyServer::modXAlt()) ret |= Qt::AltModifier; if (state & KKeyServer::modXMeta()) ret |= Qt::MetaModifier; return ret; } } // namespace #ifndef KCMRULES #endif