diff --git a/CMakeLists.txt b/CMakeLists.txt index 55054e21a..4ba9970b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,626 +1,626 @@ project(KWIN) set(PROJECT_VERSION "5.6.90") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.12.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) find_package(ECM 0.0.11 REQUIRED NO_MODULE) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(GenerateExportHeader) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Concurrent Core DBus Quick QuickWidgets Script UiTools Widgets X11Extras ) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER) # require at least gcc 4.8 if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") if ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "4.8") message(SEND_ERROR "Version ${CMAKE_CXX_COMPILER_VERSION} of the ${CMAKE_CXX_COMPILER_ID} C++ compiler is not supported. Please use version 4.8 or later.") endif() endif() find_package(Qt5Multimedia QUIET) set_package_properties(Qt5Multimedia PROPERTIES PURPOSE "Runtime-only dependency for effect video playback" TYPE RUNTIME ) # required frameworks by Core find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash GlobalAccel I18n Init Notifications Package Plasma WidgetsAddons WindowSystem IconThemes IdleTime ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service XmlGui ) find_package(Threads) set_package_properties(Threads PROPERTIES PURPOSE "Needed for VirtualTerminal support in KWin Wayland" TYPE REQUIRED ) # optional frameworks find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Activities PROPERTIES PURPOSE "Enable building of KWin with kactivities support" TYPE OPTIONAL ) add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Enable building documentation" TYPE OPTIONAL ) add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") find_package(KDecoration2 CONFIG REQUIRED) find_package(KF5Wayland CONFIG REQUIRED) set_package_properties(KF5Wayland PROPERTIES TYPE REQUIRED ) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland") find_package(EGL) set_package_properties(EGL PROPERTIES TYPE RUNTIME PURPOSE "Required to build KWin with EGL support" ) find_package(epoxy) set_package_properties(epoxy PROPERTIES DESCRIPTION "libepoxy" URL "http://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) find_package(Wayland 1.2 REQUIRED COMPONENTS Cursor OPTIONAL_COMPONENTS Egl) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend and QPA with EGL support.") set(HAVE_WAYLAND_EGL FALSE) if(Wayland_Egl_FOUND) set(HAVE_WAYLAND_EGL TRUE) endif() find_package(XKB 0.4.1) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 0.10) set_package_properties(Libinput PROPERTIES TYPE OPTIONAL PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software/systemd/libudev/" DESCRIPTION "Linux device library." TYPE OPTIONAL PURPOSE "Required for input handling on Wayland." ) set(HAVE_INPUT FALSE) if (Libinput_FOUND AND UDEV_FOUND) set(HAVE_INPUT TRUE) endif() find_package(Libdrm) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if(Libdrm_FOUND AND UDEV_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl ouput of drm backend.") set(HAVE_GBM FALSE) if(HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() find_package(libhybris) set_package_properties(libhybris PROPERTIES TYPE OPTIONAL PURPOSE "Required for libhybris backend") set(HAVE_LIBHYBRIS ${libhybris_FOUND}) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED ) add_feature_info("XInput" X11_Xinput_FOUND "Required for poll-free mouse cursor updates") set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) # All the required XCB components find_package(XCB 1.10 REQUIRED COMPONENTS XCB XFIXES DAMAGE COMPOSITE SHAPE SYNC RENDER RANDR KEYSYMS IMAGE SHM GLX CURSOR OPTIONAL_COMPONENTS ICCCM ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) # and the optional XCB dependencies if (XCB_ICCCM_VERSION VERSION_LESS "0.4") set(XCB_ICCCM_FOUND FALSE) endif() add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") find_package(X11_XCB) set_package_properties(X11_XCB PROPERTIES PURPOSE "Required for building X11 windowed backend of kwin_wayland" TYPE OPTIONAL) # dependencies for QPA plugin find_package(Qt5PlatformSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "http://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "http://www.freedesktop.org/wiki/Software/fontconfig" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "http://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) ########### configure tests ############### include(CMakeDependentOption) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) option(KWIN_BUILD_XRENDER_COMPOSITING "Enable building of KWin with XRender Compositing support" ON) cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) # Binary name of KWin set(KWIN_NAME "kwin") set(KWIN_INTERNAL_NAME_X11 "kwin_x11") set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") set(KWIN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # KWIN_HAVE_XRENDER_COMPOSITING - whether XRender-based compositing support is available: may be disabled if( KWIN_BUILD_XRENDER_COMPOSITING ) set( KWIN_HAVE_XRENDER_COMPOSITING 1 ) endif() include_directories(${XKB_INCLUDE_DIR}) include_directories(${epoxy_INCLUDE_DIR}) set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) # for things that are also used by kwin libraries configure_file(libkwineffects/kwinconfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects/kwinconfig.h ) # for kwin internal things set(HAVE_X11_XCB ${X11_XCB_FOUND}) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckSymbolExists) check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(malloc.h HAVE_MALLOC_H) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) add_feature_info("prctl-dumpable" HAVE_PR_SET_DUMPABLE "Required for disallow ptrace on kwin_wayland process") configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h ) ########### global ############### set(kwin_effects_dbus_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kwin.Effects.xml) include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/libkwineffects ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libkwineffects ${CMAKE_CURRENT_SOURCE_DIR}/effects ${CMAKE_CURRENT_SOURCE_DIR}/tabbox ) add_subdirectory( libkwineffects ) add_subdirectory( killer ) if(KWIN_BUILD_KCMS) add_subdirectory( kcmkwin ) endif() if( KWIN_BUILD_DECORATIONS ) add_subdirectory( clients ) endif() add_subdirectory( data ) add_subdirectory( effects ) add_subdirectory( scripts ) add_subdirectory( tabbox ) add_subdirectory(scripting) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1212) ########### next target ############### set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp abstract_client.cpp client.cpp client_machine.cpp cursor.cpp debug_console.cpp tabgroup.cpp focuschain.cpp globalshortcuts.cpp input.cpp keyboard_input.cpp pointer_input.cpp touch_input.cpp netinfo.cpp placement.cpp atoms.cpp utils.cpp layers.cpp main.cpp options.cpp outline.cpp events.cpp killwindow.cpp geometrytip.cpp screens.cpp screens_xrandr.cpp shadow.cpp sm.cpp group.cpp manage.cpp overlaywindow.cpp activation.cpp useractions.cpp geometry.cpp rules.cpp composite.cpp toplevel.cpp unmanaged.cpp scene.cpp scene_xrender.cpp scene_opengl.cpp scene_qpainter.cpp thumbnailitem.cpp lanczosfilter.cpp deleted.cpp effects.cpp effectloader.cpp compositingprefs.cpp virtualdesktops.cpp xcbutils.cpp x11eventfilter.cpp logind.cpp screenedge.cpp scripting/scripting.cpp scripting/workspace_wrapper.cpp scripting/meta.cpp scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/scripting_model.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scripting_logging.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/settings.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp abstract_egl_backend.cpp eglonxbackend.cpp - abstract_backend.cpp + platform.cpp shell_client.cpp wayland_server.cpp wayland_cursor_theme.cpp ) if(HAVE_EPOXY_GLX) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} glxbackend.cpp) endif() if(KWIN_BUILD_TABBOX) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/tabbox.cpp tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/tabbox_logging.cpp ) endif() if(KWIN_BUILD_ACTIVITIES) set( kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if(UDEV_FOUND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} udev.cpp ) endif() if(HAVE_INPUT) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp libinput/events.cpp libinput/libinput_logging.cpp virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.xml dbusinterface.h KWin::DBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterface.h KWin::CompositorDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc ) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwinxrenderutils kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::X11Extras ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::CoreAddons KF5::ConfigWidgets KF5::Crash KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_X11_LIB} ${X11_ICE_LIB} ${X11_SM_LIB} ) if(X11_Xinput_FOUND) set(kwin_XLIB_LIBS ${kwin_XLIB_LIBS} ${X11_Xinput_LIB}) endif() set(kwin_XCB_LIBS XCB::XCB XCB::XFIXES XCB::DAMAGE XCB::COMPOSITE XCB::SHAPE XCB::SYNC XCB::RENDER XCB::RANDR XCB::KEYSYMS XCB::SHM XCB::GLX XCB::CURSOR ) set(kwin_WAYLAND_LIBS XKB::XKB KF5::WaylandClient KF5::WaylandServer Wayland::Cursor ${CMAKE_THREAD_LIBS_INIT} ) if(KWIN_BUILD_ACTIVITIES) set(kwin_KDE_LIBS ${kwin_KDE_LIBS} KF5::Activities) endif() set(kwinLibs ${kwin_OWN_LIBS} ${kwin_QT_LIBS} ${kwin_KDE_LIBS} ${kwin_XLIB_LIBS} ${kwin_XCB_LIBS} ${kwin_WAYLAND_LIBS} ) if(UDEV_FOUND) set(kwinLibs ${kwinLibs} ${UDEV_LIBS}) endif() if(HAVE_INPUT) set(kwinLibs ${kwinLibs} Libinput::Libinput) endif() add_library(kwin SHARED ${kwin_KDEINIT_SRCS}) set_target_properties(kwin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) target_link_libraries(kwin ${kwinLibs}) generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h) target_link_libraries(kwin kwinglutils ${epoxy_LIBRARY}) # -ldl used by OpenGL code find_library(DL_LIBRARY dl) if (DL_LIBRARY) target_link_libraries(kwin ${DL_LIBRARY}) endif() kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin) install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP ) install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_executable(kwin_wayland main_wayland.cpp) target_link_libraries(kwin_wayland kwin) install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_subdirectory(backends) add_subdirectory(plugins) ########### install files ############### install( FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg ) install( FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.xml org.kde.kwin.Compositing.xml org.kde.kwin.Effects.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kwin_export.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) # Install the KWin/Script service type install( FILES scripting/kwinscript.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) ecm_install_icons( ICONS 16-apps-kwin.png 32-apps-kwin.png 48-apps-kwin.png sc-apps-kwin.svgz DESTINATION ${ICON_INSTALL_DIR} THEME hicolor ) add_subdirectory(qml) add_subdirectory(autotests) add_subdirectory(tests) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(ECMPackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") ecm_configure_package_config_file(KWinDBusInterfaceConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) diff --git a/autotests/wayland/debug_console_test.cpp b/autotests/wayland/debug_console_test.cpp index 2b263583c..56c844d4c 100644 --- a/autotests/wayland/debug_console_test.cpp +++ b/autotests/wayland/debug_console_test.cpp @@ -1,535 +1,535 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "debug_console.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_debug_console-0"); class DebugConsoleTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void topLevelTest_data(); void topLevelTest(); void testX11Client(); void testX11Unmanaged(); void testWaylandClient(); void testInternalWindow(); }; void DebugConsoleTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DebugConsoleTest::topLevelTest_data() { QTest::addColumn("row"); QTest::addColumn("column"); QTest::addColumn("expectedValid"); // this tests various combinations of row/column on the top level whether they are valid // valid are rows 0-4 with column 0, everything else is invalid QTest::newRow("0/0") << 0 << 0 << true; QTest::newRow("0/1") << 0 << 1 << false; QTest::newRow("0/3") << 0 << 3 << false; QTest::newRow("1/0") << 1 << 0 << true; QTest::newRow("1/1") << 1 << 1 << false; QTest::newRow("1/3") << 1 << 3 << false; QTest::newRow("2/0") << 2 << 0 << true; QTest::newRow("3/0") << 3 << 0 << true; QTest::newRow("4/0") << 4 << 0 << false; QTest::newRow("100/0") << 4 << 0 << false; } void DebugConsoleTest::topLevelTest() { DebugConsoleModel model; QCOMPARE(model.rowCount(QModelIndex()), 4); QCOMPARE(model.columnCount(QModelIndex()), 2); QFETCH(int, row); QFETCH(int, column); const QModelIndex index = model.index(row, column, QModelIndex()); QTEST(index.isValid(), "expectedValid"); if (index.isValid()) { QVERIFY(!model.parent(index).isValid()); QVERIFY(model.data(index, Qt::DisplayRole).isValid()); QCOMPARE(model.data(index, Qt::DisplayRole).userType(), int(QMetaType::QString)); for (int i = Qt::DecorationRole; i <= Qt::UserRole; i++) { QVERIFY(!model.data(index, i).isValid()); } } } void DebugConsoleTest::testX11Client() { DebugConsoleModel model; QModelIndex x11TopLevelIndex = model.index(0, 0, QModelIndex()); QVERIFY(x11TopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(x11TopLevelIndex), 0); QVERIFY(!model.hasChildren(x11TopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // start glxgears, to get a window, which should be added to the model QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(x11TopLevelIndex)); QCOMPARE(model.rowCount(x11TopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), x11TopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, x11TopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), x11TopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(0, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); glxgears.terminate(); QVERIFY(glxgears.waitForFinished()); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), x11TopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(x11TopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(0, 0, QModelIndex()))); } void DebugConsoleTest::testX11Unmanaged() { DebugConsoleModel model; QModelIndex unmanagedTopLevelIndex = model.index(1, 0, QModelIndex()); QVERIFY(unmanagedTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 0); QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); // we need to create an unmanaged window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // let's create an override redirect window const uint32_t values[] = {true}; Xcb::Window window(QRect(0, 0, 10, 10), XCB_CW_OVERRIDE_REDIRECT, values); window.map(); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(unmanagedTopLevelIndex)); QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), unmanagedTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, unmanagedTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), unmanagedTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); QCOMPARE(model.data(clientIndex, Qt::DisplayRole).toString(), QString::number(window)); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(1, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); window.unmap(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), unmanagedTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(1, 0, QModelIndex()))); } void DebugConsoleTest::testWaylandClient() { DebugConsoleModel model; QModelIndex waylandTopLevelIndex = model.index(2, 0, QModelIndex()); QVERIFY(waylandTopLevelIndex.isValid()); // we don't have any windows yet QCOMPARE(model.rowCount(waylandTopLevelIndex), 0); QVERIFY(!model.hasChildren(waylandTopLevelIndex)); // child index must be invalid QVERIFY(!model.index(0, 0, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // we need to create a wayland window QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); // create our connection using namespace KWayland::Client; // setup connection QScopedPointer connection(new ConnectionThread); QSignalSpy connectedSpy(connection.data(), &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); connection->setSocketName(s_socketName); QScopedPointer thread(new QThread); connection->moveToThread(thread.data()); thread->start(); connection->initConnection(); QVERIFY(connectedSpy.wait()); QScopedPointer queue(new EventQueue); QVERIFY(!queue->isValid()); queue->setup(connection.data()); QVERIFY(queue->isValid()); Registry registry; registry.setEventQueue(queue.data()); QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); registry.create(connection.data()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(interfacesAnnouncedSpy.wait()); QScopedPointer compositor(registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version)); QVERIFY(compositor->isValid()); QScopedPointer shm(registry.createShmPool(registry.interface(Registry::Interface::Shm).name, registry.interface(Registry::Interface::Shm).version)); QVERIFY(shm->isValid()); QScopedPointer shell(registry.createShell(registry.interface(Registry::Interface::Shell).name, registry.interface(Registry::Interface::Shell).version)); QVERIFY(shell->isValid()); // create the Surface and ShellSurface QScopedPointer surface(compositor->createSurface()); QVERIFY(surface->isValid()); QScopedPointer shellSurface(shell->createSurface(surface.data())); QVERIFY(shellSurface->isValid()); QImage img(10, 10, QImage::Format_ARGB32); img.fill(Qt::red); surface->attachBuffer(shm->createBuffer(img)); surface->damage(QRect(0, 0, 10, 10)); surface->commit(Surface::CommitFlag::None); // now we have the window, it should be added to our model QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QVERIFY(model.hasChildren(waylandTopLevelIndex)); QCOMPARE(model.rowCount(waylandTopLevelIndex), 1); QCOMPARE(rowsInsertedSpy.first().at(0).value(), waylandTopLevelIndex); QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); QModelIndex clientIndex = model.index(0, 0, waylandTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), waylandTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // creating a second model should be initialized directly with the X11 child DebugConsoleModel model2; QVERIFY(model2.hasChildren(model2.index(2, 0, QModelIndex()))); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); surface->attachBuffer(Buffer::Ptr()); surface->commit(Surface::CommitFlag::None); shellSurface.reset(); connection->flush(); qDebug() << rowsRemovedSpy.count(); QEXPECT_FAIL("", "Deleting a ShellSurface does not result in the server removing the ShellClient", Continue); QVERIFY(rowsRemovedSpy.wait()); // also destroy the connection to ensure the ShellSurface goes away surface.reset(); shell.reset(); shm.reset(); compositor.reset(); connection.take()->deleteLater(); thread->quit(); QVERIFY(thread->wait()); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), waylandTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); // the child should be gone again QVERIFY(!model.hasChildren(waylandTopLevelIndex)); QVERIFY(!model2.hasChildren(model2.index(2, 0, QModelIndex()))); } class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow() : QRasterWindow(nullptr) {} ~HelperWindow() = default; Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } }; void DebugConsoleTest::testInternalWindow() { DebugConsoleModel model; QModelIndex internalTopLevelIndex = model.index(3, 0, QModelIndex()); QVERIFY(internalTopLevelIndex.isValid()); // there might already be some internal windows, so we cannot reliable test whether there are children // given that we just test whether adding a window works. QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); QVERIFY(rowsInsertedSpy.isValid()); QScopedPointer w(new HelperWindow); w->setGeometry(0, 0, 100, 100); w->show(); QVERIFY(rowsInsertedSpy.wait()); QCOMPARE(rowsInsertedSpy.count(), 1); QCOMPARE(rowsInsertedSpy.first().first().value(), internalTopLevelIndex); QModelIndex clientIndex = model.index(rowsInsertedSpy.first().last().toInt(), 0, internalTopLevelIndex); QVERIFY(clientIndex.isValid()); QCOMPARE(model.parent(clientIndex), internalTopLevelIndex); QVERIFY(model.hasChildren(clientIndex)); QVERIFY(model.rowCount(clientIndex) != 0); QCOMPARE(model.columnCount(clientIndex), 2); // other indexes are still invalid QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 1, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 2, internalTopLevelIndex).isValid()); QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt() + 1, 0, internalTopLevelIndex).isValid()); // the wayland shell client top level should not have gained this window QVERIFY(!model.hasChildren(model.index(2, 0, QModelIndex()))); // the clientIndex has children and those are properties for (int i = 0; i < model.rowCount(clientIndex); i++) { const QModelIndex propNameIndex = model.index(i, 0, clientIndex); QVERIFY(propNameIndex.isValid()); QCOMPARE(model.parent(propNameIndex), clientIndex); QVERIFY(!model.hasChildren(propNameIndex)); QVERIFY(!model.index(0, 0, propNameIndex).isValid()); QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); // and the value const QModelIndex propValueIndex = model.index(i, 1, clientIndex); QVERIFY(propValueIndex.isValid()); QCOMPARE(model.parent(propValueIndex), clientIndex); QVERIFY(!model.index(0, 0, propValueIndex).isValid()); QVERIFY(!model.hasChildren(propValueIndex)); // TODO: how to test whether the values actually work? // and on third column we should not get an index any more QVERIFY(!model.index(i, 2, clientIndex).isValid()); } // row after count should be invalid QVERIFY(!model.index(model.rowCount(clientIndex), 0, clientIndex).isValid()); // now close the window again, it should be removed from the model QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); QVERIFY(rowsRemovedSpy.isValid()); w->hide(); w.reset(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), internalTopLevelIndex); } } WAYLANDTEST_MAIN(KWin::DebugConsoleTest) #include "debug_console_test.moc" diff --git a/autotests/wayland/decoration_input_test.cpp b/autotests/wayland/decoration_input_test.cpp index 56474cb96..fa000ed65 100644 --- a/autotests/wayland/decoration_input_test.cpp +++ b/autotests/wayland/decoration_input_test.cpp @@ -1,391 +1,391 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); class DecorationInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testAxis(); void testDoubleClick(); void testHover(); void testPressToMove_data(); void testPressToMove(); private: AbstractClient *showWindow(); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ServerSideDecorationManager *m_deco = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) AbstractClient *DecorationInputTest::showWindow() { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); VERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); VERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); VERIFY(shellSurface); auto deco = m_deco->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); // let's render QImage img(QSize(500, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 500, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); VERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); VERIFY(c); COMPARE(clientAddedSpy.first().first().value(), c); #undef VERIFY #undef COMPARE return c; } void DecorationInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); // change some options KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); config->sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void DecorationInputTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy decorationSpy(®istry, &Registry::serverSideDecorationManagerAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); QVERIFY(decorationSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); QVERIFY(!decorationSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); m_deco = registry.createServerSideDecorationManager(decorationSpy.first().first().value(), decorationSpy.first().last().value()); QVERIFY(m_deco->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DecorationInputTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_deco; m_deco = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void DecorationInputTest::testAxis() { AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QCOMPARE(c->titlebarPosition(), AbstractClient::PositionTop); QVERIFY(!c->keepAbove()); QVERIFY(!c->keepBelow()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); // TODO: mouse wheel direction looks wrong to me // simulate wheel kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(!c->keepAbove()); kwinApp()->platform()->pointerAxisVertical(-5.0, timestamp++); QVERIFY(!c->keepBelow()); QVERIFY(c->keepAbove()); } void KWin::DecorationInputTest::testDoubleClick() { AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); QVERIFY(!c->isOnAllDesktops()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); // double click PRESS; RELEASE; PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); // double click again PRESS; RELEASE; QVERIFY(c->isOnAllDesktops()); PRESS; RELEASE; QVERIFY(!c->isOnAllDesktops()); } void DecorationInputTest::testHover() { AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); // our left border is moved out of the visible area, so move the window to a better place c->move(QPoint(20, 0)); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); MOTION(QPoint(20, 0)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, 0)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, 0)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() - 1, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeFDiagCursor); MOTION(QPoint(c->geometry().x() + c->geometry().width() / 2, c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeVerCursor); MOTION(QPoint(c->geometry().x(), c->height() - 1)); QCOMPARE(c->cursor(), Qt::SizeBDiagCursor); MOTION(QPoint(c->geometry().x(), c->height() / 2)); QCOMPARE(c->cursor(), Qt::SizeHorCursor); MOTION(c->geometry().center()); QEXPECT_FAIL("", "Cursor not set back on leave", Continue); QCOMPARE(c->cursor(), Qt::ArrowCursor); } void DecorationInputTest::testPressToMove_data() { QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); } void DecorationInputTest::testPressToMove() { AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); quint32 timestamp = 1; MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); QCOMPARE(c->cursor(), Qt::ArrowCursor); PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset); const QPoint oldPos = c->pos(); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 1); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); QCOMPARE(c->pos(), oldPos + offset); // again PRESS; QVERIFY(!c->isMove()); QFETCH(QPoint, offset2); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset2); QVERIFY(c->isMove()); QCOMPARE(startMoveResizedSpy.count(), 2); QFETCH(QPoint, offset3); MOTION(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2) + offset3); RELEASE; QTRY_VERIFY(!c->isMove()); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); // TODO: the offset should also be included QCOMPARE(c->pos(), oldPos + offset2 + offset3); } } WAYLANDTEST_MAIN(KWin::DecorationInputTest) #include "decoration_input_test.moc" diff --git a/autotests/wayland/dont_crash_cancel_animation.cpp b/autotests/wayland/dont_crash_cancel_animation.cpp index 8e2bb8cd0..6106b9e24 100644 --- a/autotests/wayland/dont_crash_cancel_animation.cpp +++ b/autotests/wayland/dont_crash_cancel_animation.cpp @@ -1,206 +1,206 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "effectloader.h" #include "screens.h" #include "shell_client.h" #include "wayland_server.h" #include "workspace.h" #include "scripting/scriptedeffect.h" #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_cancel_animation-0"); class DontCrashCancelAnimationFromAnimationEndedTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testScript(); private: void unlock(); AbstractClient *showWindow(); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void DontCrashCancelAnimationFromAnimationEndedTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(Compositor::self()); QSignalSpy compositorToggledSpy(Compositor::self(), &Compositor::compositingToggled); QVERIFY(compositorToggledSpy.isValid()); QVERIFY(compositorToggledSpy.wait()); QVERIFY(effects); } void DontCrashCancelAnimationFromAnimationEndedTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); } void DontCrashCancelAnimationFromAnimationEndedTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void DontCrashCancelAnimationFromAnimationEndedTest::testScript() { // load a scripted effect which deletes animation data ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10); QVERIFY(effect); const auto children = effects->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { continue; } QVERIFY(QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect*, effect), Q_ARG(QString, QStringLiteral("crashy")))); break; } QVERIFY(static_cast(effects)->isEffectLoaded(QStringLiteral("crashy"))); using namespace KWayland::Client; // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); // make sure we animate QTest::qWait(200); // wait for the window to be passed to Deleted QSignalSpy windowDeletedSpy(c, &AbstractClient::windowClosed); QVERIFY(windowDeletedSpy.isValid()); surface->deleteLater(); QVERIFY(windowDeletedSpy.wait()); // make sure we animate QTest::qWait(200); } } WAYLANDTEST_MAIN(KWin::DontCrashCancelAnimationFromAnimationEndedTest) #include "dont_crash_cancel_animation.moc" diff --git a/autotests/wayland/dont_crash_glxgears.cpp b/autotests/wayland/dont_crash_glxgears.cpp index 4dfee5fad..8b522d03d 100644 --- a/autotests/wayland/dont_crash_glxgears.cpp +++ b/autotests/wayland/dont_crash_glxgears.cpp @@ -1,104 +1,104 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "client.h" #include "deleted.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_glxgears-0"); class DontCrashGlxgearsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testGlxgears(); }; void DontCrashGlxgearsTest::initTestCase() { qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void DontCrashGlxgearsTest::testGlxgears() { // closing a glxgears window through Aurorae themes used to crash KWin // Let's make sure that doesn't happen anymore QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QProcess glxgears; glxgears.start(QStringLiteral("glxgears")); QVERIFY(glxgears.waitForStarted()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(workspace()->clientList().count(), 1); Client *glxgearsClient = workspace()->clientList().first(); QVERIFY(glxgearsClient->isDecorated()); QSignalSpy closedSpy(glxgearsClient, &Client::windowClosed); QVERIFY(closedSpy.isValid()); KDecoration2::Decoration *decoration = glxgearsClient->decoration(); QVERIFY(decoration); // send a mouse event to the position of the close button QPointF pos = decoration->rect().topRight() + QPointF(-decoration->borderRight() * 2, decoration->borderRight() * 2); QHoverEvent event(QEvent::HoverMove, pos, pos); QCoreApplication::instance()->sendEvent(decoration, &event); // mouse press QMouseEvent mousePressevent(QEvent::MouseButtonPress, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mousePressevent.setAccepted(false); QCoreApplication::sendEvent(decoration, &mousePressevent); QVERIFY(mousePressevent.isAccepted()); // mouse Release QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); mouseReleaseEvent.setAccepted(false); QCoreApplication::sendEvent(decoration, &mouseReleaseEvent); QVERIFY(mouseReleaseEvent.isAccepted()); QVERIFY(closedSpy.wait()); QCOMPARE(closedSpy.count(), 1); xcb_flush(connection()); if (glxgears.state() == QProcess::Running) { QVERIFY(glxgears.waitForFinished()); } } } WAYLANDTEST_MAIN(KWin::DontCrashGlxgearsTest) #include "dont_crash_glxgears.moc" diff --git a/autotests/wayland/input_stacking_order.cpp b/autotests/wayland/input_stacking_order.cpp index 8d410c295..507d1d749 100644 --- a/autotests/wayland/input_stacking_order.cpp +++ b/autotests/wayland/input_stacking_order.cpp @@ -1,256 +1,256 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "deleted.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_input_stacking_order-0"); class InputStackingOrderTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testPointerFocusUpdatesOnStackingOrderChange(); private: void render(KWayland::Client::Surface *surface); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void InputStackingOrderTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void InputStackingOrderTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void InputStackingOrderTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void InputStackingOrderTest::render(KWayland::Client::Surface *surface) { QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(KWayland::Client::Surface::CommitFlag::None); m_connection->flush(); } void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange() { // this test creates two windows which overlap // the pointer is in the overlapping area which means the top most window has focus // as soon as the top most window gets lowered the window should lose focus and the // other window should gain focus without a mouse event in between using namespace KWayland::Client; // create pointer and signal spy for enter and leave signals auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); // now create the two windows and make them overlap QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = m_compositor->createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = m_compositor->createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); // now make windows overlap window2->move(window1->pos()); QCOMPARE(window1->geometry(), window2->geometry()); // enter kwinApp()->platform()->pointerMotion(QPointF(25, 25), 1); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); // window 2 should have focus QCOMPARE(pointer->enteredSurface(), surface2); // also on the server QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); // raise window 1 above window 2 QVERIFY(leftSpy.isEmpty()); workspace()->raiseClient(window1); // should send leave to window2 QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // and an enter to window1 QCOMPARE(enteredSpy.count(), 2); QCOMPARE(pointer->enteredSurface(), surface1); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window1->surface()); // let's destroy window1, that should pass focus to window2 again QSignalSpy windowClosedSpy(window1, &Toplevel::windowClosed); QVERIFY(windowClosedSpy.isValid()); surface1->deleteLater(); QVERIFY(windowClosedSpy.wait()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); QCOMPARE(pointer->enteredSurface(), surface2); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); } } WAYLANDTEST_MAIN(KWin::InputStackingOrderTest) #include "input_stacking_order.moc" diff --git a/autotests/wayland/internal_window.cpp b/autotests/wayland/internal_window.cpp index 6c49bfd16..f252b9a58 100644 --- a/autotests/wayland/internal_window.cpp +++ b/autotests/wayland/internal_window.cpp @@ -1,278 +1,278 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0"); class InternalWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testEnterLeave(); void testPointerPressRelease(); void testPointerAxis(); void testKeyboard_data(); void testKeyboard(); }; class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow(); ~HelperWindow(); Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override; bool event(QEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; }; HelperWindow::HelperWindow() : QRasterWindow(nullptr) { } HelperWindow::~HelperWindow() = default; void HelperWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } bool HelperWindow::event(QEvent *event) { if (event->type() == QEvent::Enter) { emit entered(); } if (event->type() == QEvent::Leave) { emit left(); } return QRasterWindow::event(event); } void HelperWindow::mouseMoveEvent(QMouseEvent *event) { emit mouseMoved(event->globalPos()); } void HelperWindow::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event) emit mousePressed(); } void HelperWindow::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) emit mouseReleased(); } void HelperWindow::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) emit wheel(); } void HelperWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyPressed(); } void HelperWindow::keyReleaseEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyReleased(); } void InternalWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); } void InternalWindowTest::testEnterLeave() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(!workspace()->activeClient()); ShellClient *c = clientAddedSpy.first().first().value(); QVERIFY(c->isInternal()); QCOMPARE(c->geometry(), QRect(0, 0, 100, 100)); QSignalSpy enterSpy(&win, &HelperWindow::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leaveSpy(&win, &HelperWindow::left); QVERIFY(leaveSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); QTRY_COMPARE(enterSpy.count(), 1); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); QTRY_COMPARE(moveSpy.count(), 1); QCOMPARE(moveSpy.first().first().toPoint(), QPoint(60, 50)); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); QTRY_COMPARE(leaveSpy.count(), 1); } void InternalWindowTest::testPointerPressRelease() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); } void InternalWindowTest::testPointerAxis() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy wheelSpy(&win, &HelperWindow::wheel); QVERIFY(wheelSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 1); kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 2); } void InternalWindowTest::testKeyboard_data() { QTest::addColumn("cursorPos"); QTest::newRow("on Window") << QPoint(50, 50); QTest::newRow("outside Window") << QPoint(250, 250); } void InternalWindowTest::testKeyboard() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; QFETCH(QPoint, cursorPos); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); QCOMPARE(pressSpy.count(), 1); } } WAYLANDTEST_MAIN(KWin::InternalWindowTest) #include "internal_window.moc" diff --git a/autotests/wayland/kwin_wayland_test.cpp b/autotests/wayland/kwin_wayland_test.cpp index d5f5386bb..4001abb6e 100644 --- a/autotests/wayland/kwin_wayland_test.cpp +++ b/autotests/wayland/kwin_wayland_test.cpp @@ -1,279 +1,279 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "../../abstract_backend.h" +#include "../../platform.h" #include "../../effects.h" #include "../../wayland_server.h" #include "../../workspace.h" #include "../../xcbutils.h" #include #include #include #include #include #include // system #include #include #include namespace KWin { static void readDisplay(int pipe); WaylandTestApplication::WaylandTestApplication(int &argc, char **argv) : Application(OperationModeXwayland, argc, argv) { #ifdef KWIN_BUILD_ACTIVITIES setUseKActivities(false); #endif qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); initPlatform(KPluginMetaData(QStringLiteral(KWINBACKENDPATH))); WaylandServer::create(this); } WaylandTestApplication::~WaylandTestApplication() { kwinApp()->platform()->setOutputsEnabled(false); destroyWorkspace(); waylandServer()->dispatch(); // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } disconnect(m_xwaylandFailConnection); if (x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); destroyAtoms(); emit x11ConnectionAboutToBeDestroyed(); xcb_disconnect(x11Connection()); setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } waylandServer()->terminateClientConnections(); destroyCompositor(); } void WaylandTestApplication::performStartup() { // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); createBackend(); } void WaylandTestApplication::createBackend() { Platform *platform = kwinApp()->platform(); connect(platform, &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); connect(platform, &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform->init(); } void WaylandTestApplication::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &WaylandTestApplication::continueStartupWithScreens); createScreens(); waylandServer()->initOutputs(); createCompositor(); startXwaylandServer(); } void WaylandTestApplication::continueStartupWithX() { createX11Connection(); xcb_connection_t *c = x11Connection(); if (!c) { // about to quit return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); auto processXcbEvents = [this, c] { while (auto event = xcb_poll_for_event(c)) { updateX11Time(event); long result = 0; if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { free(event); continue; } if (Workspace::self()) { Workspace::self()->workspaceEvent(event); } free(event); } xcb_flush(c); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", c, x11RootWindow()); owner.claim(true); createAtoms(); setupEventFilters(); // 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()) { ::exit(1); } createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort } void WaylandTestApplication::createX11Connection() { int screenNumber = 0; xcb_connection_t *c = nullptr; if (m_xcbConnectionFd == -1) { c = xcb_connect(nullptr, &screenNumber); } else { c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); } if (int error = xcb_connection_has_error(c)) { std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; exit(1); return; } setX11Connection(c); // we don't support X11 multi-head in Wayland setX11ScreenNumber(screenNumber); setX11RootWindow(defaultScreen()->root); } void WaylandTestApplication::startXwaylandServer() { int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; exit(1); return; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new QProcess(kwinApp()); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), QString::number(pipeFds[1]), QStringLiteral("-rootless"), QStringLiteral("-wm"), QString::number(fd)}); m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast(&QProcess::error), this, [] (QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; } else { std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; } exit(1); } ); const int xDisplayPipe = pipeFds[0]; connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, &WaylandTestApplication::continueStartupWithX, Qt::QueuedConnection); QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater, Qt::QueuedConnection); watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); } ); m_xwaylandProcess->start(); close(pipeFds[1]); } static void readDisplay(int pipe) { QFile readPipe; if (!readPipe.open(pipe, QIODevice::ReadOnly)) { std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; exit(1); } QByteArray displayNumber = readPipe.readLine(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; setenv("DISPLAY", displayNumber.constData(), true); // close our pipe close(pipe); } } diff --git a/autotests/wayland/lockscreen.cpp b/autotests/wayland/lockscreen.cpp index 49014915f..0aeaa7941 100644 --- a/autotests/wayland/lockscreen.cpp +++ b/autotests/wayland/lockscreen.cpp @@ -1,820 +1,820 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //screenlocker #include #include Q_DECLARE_METATYPE(Qt::Orientation) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_lock_screen-0"); class LockScreenTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testPointer(); void testPointerButton(); void testPointerAxis(); void testKeyboard(); void testScreenEdge(); void testEffects(); void testEffectsKeyboard(); void testEffectsKeyboardAutorepeat(); void testMoveWindow(); void testPointerShortcut(); void testAxisShortcut_data(); void testAxisShortcut(); void testKeyboardShortcut(); void testTouch(); private: void unlock(); AbstractClient *showWindow(); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; class HelperEffect : public Effect { Q_OBJECT public: HelperEffect() {} ~HelperEffect() {} void windowInputMouseEvent(QEvent*) override { emit inputEvent(); } void grabbedKeyboardEvent(QKeyEvent *e) override { emit keyEvent(e->text()); } Q_SIGNALS: void inputEvent(); void keyEvent(const QString&); }; #define LOCK \ QVERIFY(!waylandServer()->isScreenLocked()); \ QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); \ QVERIFY(lockStateChangedSpy.isValid()); \ ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); \ QCOMPARE(lockStateChangedSpy.count(), 1); \ QVERIFY(waylandServer()->isScreenLocked()); #define UNLOCK \ int expectedLockCount = 1; \ if (ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked) { \ expectedLockCount = 2; \ } \ QCOMPARE(lockStateChangedSpy.count(), expectedLockCount); \ unlock(); \ if (lockStateChangedSpy.count() < expectedLockCount + 1) { \ QVERIFY(lockStateChangedSpy.wait()); \ } \ QCOMPARE(lockStateChangedSpy.count(), expectedLockCount + 1); \ QVERIFY(!waylandServer()->isScreenLocked()); #define MOTION(target) \ kwinApp()->platform()->pointerMotion(target, timestamp++) #define PRESS \ kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++) #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) #define KEYPRESS( key ) \ kwinApp()->platform()->keyboardKeyPressed(key, timestamp++) #define KEYRELEASE( key ) \ kwinApp()->platform()->keyboardKeyReleased(key, timestamp++) void LockScreenTest::unlock() { using namespace ScreenLocker; const auto children = KSldApp::self()->children(); for (auto it = children.begin(); it != children.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { continue; } QMetaObject::invokeMethod(*it, "requestUnlock"); break; } } AbstractClient *LockScreenTest::showWindow() { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); VERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); VERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); VERIFY(shellSurface); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); VERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); VERIFY(c); COMPARE(clientAddedSpy.first().first().value(), c); #undef VERIFY #undef COMPARE return c; } void LockScreenTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void LockScreenTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void LockScreenTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void LockScreenTest::testPointer() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer.data(), &Pointer::left); QVERIFY(leftSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); LOCK QVERIFY(leftSpy.wait(100)); QCOMPARE(leftSpy.count(), 1); // simulate moving out in and out again MOTION(c->geometry().center()); MOTION(c->geometry().bottomRight() + QPoint(100, 100)); MOTION(c->geometry().bottomRight() + QPoint(100, 100)); QVERIFY(!leftSpy.wait(100)); QCOMPARE(leftSpy.count(), 1); QCOMPARE(enteredSpy.count(), 1); // go back on the window MOTION(c->geometry().center()); // and unlock UNLOCK QVERIFY(enteredSpy.wait(100)); QCOMPARE(enteredSpy.count(), 2); // move on the window MOTION(c->geometry().center() + QPoint(100, 100)); QVERIFY(leftSpy.wait()); MOTION(c->geometry().center()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); } void LockScreenTest::testPointerButton() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy buttonChangedSpy(pointer.data(), &Pointer::buttonStateChanged); QVERIFY(buttonChangedSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); // and simulate a click PRESS; QVERIFY(buttonChangedSpy.wait()); RELEASE; QVERIFY(buttonChangedSpy.wait()); LOCK // and simulate a click PRESS; QVERIFY(!buttonChangedSpy.wait(100)); RELEASE; QVERIFY(!buttonChangedSpy.wait(100)); UNLOCK // and click again PRESS; QVERIFY(buttonChangedSpy.wait()); RELEASE; QVERIFY(buttonChangedSpy.wait()); } void LockScreenTest::testPointerAxis() { using namespace KWayland::Client; QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QSignalSpy axisChangedSpy(pointer.data(), &Pointer::axisChanged); QVERIFY(axisChangedSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); // first move cursor into the center of the window quint32 timestamp = 1; MOTION(c->geometry().center()); // and simulate axis kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); LOCK // and simulate axis kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(!axisChangedSpy.wait(100)); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(!axisChangedSpy.wait(100)); // and unlock UNLOCK // and move axis again kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QVERIFY(axisChangedSpy.wait()); } void LockScreenTest::testKeyboard() { using namespace KWayland::Client; QScopedPointer keyboard(m_seat->createKeyboard()); QVERIFY(!keyboard.isNull()); QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); QSignalSpy keyChangedSpy(keyboard.data(), &Keyboard::keyChanged); QVERIFY(keyChangedSpy.isValid()); AbstractClient *c = showWindow(); QVERIFY(c); QVERIFY(enteredSpy.wait()); QTRY_COMPARE(enteredSpy.count(), 1); quint32 timestamp = 1; KEYPRESS(KEY_A); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 1); QCOMPARE(keyChangedSpy.at(0).at(0).value(), quint32(KEY_A)); QCOMPARE(keyChangedSpy.at(0).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(0).at(2).value(), quint32(1)); KEYRELEASE(KEY_A); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 2); QCOMPARE(keyChangedSpy.at(1).at(0).value(), quint32(KEY_A)); QCOMPARE(keyChangedSpy.at(1).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(1).at(2).value(), quint32(2)); LOCK QVERIFY(leftSpy.wait()); KEYPRESS(KEY_B); KEYRELEASE(KEY_B); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyChangedSpy.count(), 2); UNLOCK QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); KEYPRESS(KEY_C); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 3); KEYRELEASE(KEY_C); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 4); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(keyChangedSpy.at(2).at(0).value(), quint32(KEY_C)); QCOMPARE(keyChangedSpy.at(3).at(0).value(), quint32(KEY_C)); QCOMPARE(keyChangedSpy.at(2).at(2).value(), quint32(5)); QCOMPARE(keyChangedSpy.at(3).at(2).value(), quint32(6)); QCOMPARE(keyChangedSpy.at(2).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(3).at(1).value(), Keyboard::KeyState::Released); } void LockScreenTest::testScreenEdge() { QSignalSpy screenEdgeSpy(ScreenEdges::self(), &ScreenEdges::approaching); QVERIFY(screenEdgeSpy.isValid()); QCOMPARE(screenEdgeSpy.count(), 0); quint32 timestamp = 1; MOTION(QPoint(5, 5)); QCOMPARE(screenEdgeSpy.count(), 1); LOCK MOTION(QPoint(4, 4)); QCOMPARE(screenEdgeSpy.count(), 1); // and unlock UNLOCK MOTION(QPoint(5, 5)); QCOMPARE(screenEdgeSpy.count(), 2); } void LockScreenTest::testEffects() { QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::inputEvent); QVERIFY(inputSpy.isValid()); effects->startMouseInterception(effect.data(), Qt::ArrowCursor); quint32 timestamp = 1; QCOMPARE(inputSpy.count(), 0); MOTION(QPoint(5, 5)); QCOMPARE(inputSpy.count(), 1); // simlate click PRESS; QCOMPARE(inputSpy.count(), 2); RELEASE; QCOMPARE(inputSpy.count(), 3); LOCK MOTION(QPoint(6, 6)); QCOMPARE(inputSpy.count(), 3); // simlate click PRESS; QCOMPARE(inputSpy.count(), 3); RELEASE; QCOMPARE(inputSpy.count(), 3); UNLOCK MOTION(QPoint(5, 5)); QCOMPARE(inputSpy.count(), 4); // simlate click PRESS; QCOMPARE(inputSpy.count(), 5); RELEASE; QCOMPARE(inputSpy.count(), 6); effects->stopMouseInterception(effect.data()); } void LockScreenTest::testEffectsKeyboard() { QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::keyEvent); QVERIFY(inputSpy.isValid()); effects->grabKeyboard(effect.data()); quint32 timestamp = 1; KEYPRESS(KEY_A); QCOMPARE(inputSpy.count(), 1); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); KEYRELEASE(KEY_A); QCOMPARE(inputSpy.count(), 2); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); LOCK KEYPRESS(KEY_B); QCOMPARE(inputSpy.count(), 2); KEYRELEASE(KEY_B); QCOMPARE(inputSpy.count(), 2); UNLOCK KEYPRESS(KEY_C); QCOMPARE(inputSpy.count(), 3); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); KEYRELEASE(KEY_C); QCOMPARE(inputSpy.count(), 4); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); QCOMPARE(inputSpy.at(3).first().toString(), QStringLiteral("c")); effects->ungrabKeyboard(); } void LockScreenTest::testEffectsKeyboardAutorepeat() { // this test is just like testEffectsKeyboard, but tests auto repeat key events // while the key is pressed the Effect should get auto repeated events // but the lock screen should filter them out QScopedPointer effect(new HelperEffect); QSignalSpy inputSpy(effect.data(), &HelperEffect::keyEvent); QVERIFY(inputSpy.isValid()); effects->grabKeyboard(effect.data()); // we need to configure the key repeat first. It is only enabled on libinput waylandServer()->seat()->setKeyRepeatInfo(25, 300); quint32 timestamp = 1; KEYPRESS(KEY_A); QCOMPARE(inputSpy.count(), 1); QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); QVERIFY(inputSpy.wait()); QVERIFY(inputSpy.count() > 1); // and still more events QVERIFY(inputSpy.wait()); QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); // now release inputSpy.clear(); KEYRELEASE(KEY_A); QCOMPARE(inputSpy.count(), 1); // while locked key repeat should not pass any events to the Effect LOCK KEYPRESS(KEY_B); QVERIFY(!inputSpy.wait(200)); KEYRELEASE(KEY_B); QVERIFY(!inputSpy.wait(200)); UNLOCK // don't test again, that's covered by testEffectsKeyboard effects->ungrabKeyboard(); } void LockScreenTest::testMoveWindow() { using namespace KWayland::Client; AbstractClient *c = showWindow(); QVERIFY(c); QSignalSpy clientStepUserMovedResizedSpy(c, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); quint32 timestamp = 1; workspace()->slotWindowMove(); QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); // TODO adjust once the expected fail is fixed kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); // while locking our window should continue to be in move resize LOCK QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); UNLOCK QCOMPARE(workspace()->getMovingClient(), c); QVERIFY(c->isMove()); kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, timestamp++); QVERIFY(!c->isMove()); } void LockScreenTest::testPointerShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.data()); // try to trigger the shortcut quint32 timestamp = 1; #define PERFORM(expectedCount) \ kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ PRESS; \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); \ RELEASE; \ kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); PERFORM(1) // now the same thing with a locked screen LOCK PERFORM(1) // and as unlocked UNLOCK PERFORM(2) #undef PERFORM } void LockScreenTest::testAxisShortcut_data() { QTest::addColumn("direction"); QTest::addColumn("sign"); QTest::newRow("up") << Qt::Vertical << 1; QTest::newRow("down") << Qt::Vertical << -1; QTest::newRow("left") << Qt::Horizontal << 1; QTest::newRow("right") << Qt::Horizontal << -1; } void LockScreenTest::testAxisShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); QFETCH(Qt::Orientation, direction); QFETCH(int, sign); PointerAxisDirection axisDirection = PointerAxisUp; if (direction == Qt::Vertical) { axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown; } else { axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight; } input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.data()); // try to trigger the shortcut quint32 timestamp = 1; #define PERFORM(expectedCount) \ kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ if (direction == Qt::Vertical) \ kwinApp()->platform()->pointerAxisVertical(sign * 5.0, timestamp++); \ else \ kwinApp()->platform()->pointerAxisHorizontal(sign * 5.0, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); \ kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ QCoreApplication::instance()->processEvents(); \ QCOMPARE(actionSpy.count(), expectedCount); PERFORM(1) // now the same thing with a locked screen LOCK PERFORM(1) // and as unlocked UNLOCK PERFORM(2) #undef PERFORM } void LockScreenTest::testKeyboardShortcut() { using namespace KWayland::Client; QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); input()->registerShortcut(Qt::CTRL + Qt::META + Qt::ALT + Qt::Key_Space, action.data()); // try to trigger the shortcut quint32 timestamp = 1; KEYPRESS(KEY_LEFTCTRL); KEYPRESS(KEY_LEFTMETA); KEYPRESS(KEY_LEFTALT); KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); LOCK KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 1); UNLOCK KEYPRESS(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 2); KEYRELEASE(KEY_SPACE); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 2); KEYRELEASE(KEY_LEFTCTRL); KEYRELEASE(KEY_LEFTMETA); KEYRELEASE(KEY_LEFTALT); } void LockScreenTest::testTouch() { using namespace KWayland::Client; auto touch = m_seat->createTouch(m_seat); QVERIFY(touch); QVERIFY(touch->isValid()); AbstractClient *c = showWindow(); QVERIFY(c); QSignalSpy sequenceStartedSpy(touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy cancelSpy(touch, &Touch::sequenceCanceled); QVERIFY(cancelSpy.isValid()); QSignalSpy pointRemovedSpy(touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); LOCK QVERIFY(cancelSpy.wait()); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(!pointRemovedSpy.wait(100)); kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); kwinApp()->platform()->touchMotion(1, QPointF(26, 26), timestamp++); kwinApp()->platform()->touchUp(1, timestamp++); UNLOCK kwinApp()->platform()->touchDown(1, QPointF(25, 25), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 2); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 1); } } WAYLANDTEST_MAIN(KWin::LockScreenTest) #include "lockscreen.moc" diff --git a/autotests/wayland/move_resize_window_test.cpp b/autotests/wayland/move_resize_window_test.cpp index bf31b3f29..4a87ffff3 100644 --- a/autotests/wayland/move_resize_window_test.cpp +++ b/autotests/wayland/move_resize_window_test.cpp @@ -1,577 +1,577 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "effects.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::AbstractClient::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class MoveResizeWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMove(); void testPackTo_data(); void testPackTo(); void testPackAgainstClient_data(); void testPackAgainstClient(); void testGrowShrink_data(); void testGrowShrink(); void testPointerMoveEnd_data(); void testPointerMoveEnd(); void testPlasmaShellSurfaceMovable_data(); void testPlasmaShellSurfaceMovable(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void MoveResizeWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); } void MoveResizeWindowTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_plasmaShell = registry.createPlasmaShell(registry.interface(Registry::Interface::PlasmaShell).name, registry.interface(Registry::Interface::PlasmaShell).version, this); QVERIFY(m_plasmaShell->isValid()); screens()->setCurrent(0); } void MoveResizeWindowTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_plasmaShell; m_plasmaShell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } } void MoveResizeWindowTest::testMove() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy startMoveResizedSpy(c, &AbstractClient::clientStartUserMovedResized); QVERIFY(startMoveResizedSpy.isValid()); QSignalSpy moveResizedChangedSpy(c, &AbstractClient::moveResizedChanged); QVERIFY(moveResizedChangedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(c, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(c, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); // effects signal handlers QSignalSpy windowStartUserMovedResizedSpy(effects, &EffectsHandler::windowStartUserMovedResized); QVERIFY(windowStartUserMovedResizedSpy.isValid()); QSignalSpy windowStepUserMovedResizedSpy(effects, &EffectsHandler::windowStepUserMovedResized); QVERIFY(windowStepUserMovedResizedSpy.isValid()); QSignalSpy windowFinishUserMovedResizedSpy(effects, &EffectsHandler::windowFinishUserMovedResized); QVERIFY(windowFinishUserMovedResizedSpy.isValid()); // begin move QVERIFY(workspace()->getMovingClient() == nullptr); QCOMPARE(c->isMove(), false); workspace()->slotWindowMove(); QCOMPARE(workspace()->getMovingClient(), c); QCOMPARE(startMoveResizedSpy.count(), 1); QCOMPARE(moveResizedChangedSpy.count(), 1); QCOMPARE(windowStartUserMovedResizedSpy.count(), 1); QCOMPARE(c->isMove(), true); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // send some key events, not going through input redirection const QPoint cursorPos = Cursor::pos(); c->keyPressEvent(Qt::Key_Right); c->updateMoveResize(Cursor::pos()); QCOMPARE(Cursor::pos(), cursorPos + QPoint(8, 0)); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); c->keyPressEvent(Qt::Key_Right); c->updateMoveResize(Cursor::pos()); QCOMPARE(Cursor::pos(), cursorPos + QPoint(16, 0)); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(windowStepUserMovedResizedSpy.count(), 2); c->keyPressEvent(Qt::Key_Down | Qt::ALT); c->updateMoveResize(Cursor::pos()); QEXPECT_FAIL("", "First event is ignored", Continue); QCOMPARE(clientStepUserMovedResizedSpy.count(), 3); QCOMPARE(c->geometry(), QRect(16, 32, 100, 50)); QCOMPARE(Cursor::pos(), cursorPos + QPoint(16, 32)); // let's end QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); c->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(moveResizedChangedSpy.count(), 2); QCOMPARE(windowFinishUserMovedResizedSpy.count(), 1); QCOMPARE(c->geometry(), QRect(16, 32, 100, 50)); QCOMPARE(c->isMove(), false); QVERIFY(workspace()->getMovingClient() == nullptr); } void MoveResizeWindowTest::testPackTo_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("left") << QStringLiteral("slotWindowPackLeft") << QRect(0, 487, 100, 50); QTest::newRow("up") << QStringLiteral("slotWindowPackUp") << QRect(590, 0, 100, 50); QTest::newRow("right") << QStringLiteral("slotWindowPackRight") << QRect(1180, 487, 100, 50); QTest::newRow("down") << QStringLiteral("slotWindowPackDown") << QRect(590, 974, 100, 50); } void MoveResizeWindowTest::testPackTo() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QTEST(c->geometry(), "expectedGeometry"); } void MoveResizeWindowTest::testPackAgainstClient_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("left") << QStringLiteral("slotWindowPackLeft") << QRect(10, 487, 100, 50); QTest::newRow("up") << QStringLiteral("slotWindowPackUp") << QRect(590, 10, 100, 50); QTest::newRow("right") << QStringLiteral("slotWindowPackRight") << QRect(1170, 487, 100, 50); QTest::newRow("down") << QStringLiteral("slotWindowPackDown") << QRect(590, 964, 100, 50); } void MoveResizeWindowTest::testPackAgainstClient() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface1(m_compositor->createSurface()); QVERIFY(!surface1.isNull()); QScopedPointer surface2(m_compositor->createSurface()); QVERIFY(!surface2.isNull()); QScopedPointer surface3(m_compositor->createSurface()); QVERIFY(!surface3.isNull()); QScopedPointer surface4(m_compositor->createSurface()); QVERIFY(!surface4.isNull()); QScopedPointer shellSurface1(m_shell->createSurface(surface1.data())); QVERIFY(!shellSurface1.isNull()); QScopedPointer shellSurface2(m_shell->createSurface(surface2.data())); QVERIFY(!shellSurface2.isNull()); QScopedPointer shellSurface3(m_shell->createSurface(surface3.data())); QVERIFY(!shellSurface3.isNull()); QScopedPointer shellSurface4(m_shell->createSurface(surface4.data())); QVERIFY(!shellSurface4.isNull()); auto renderWindow = [this, &clientAddedSpy] (Surface *surface, const QString &methodCall, const QRect &expectedGeometry) { // let's render QImage img(QSize(10, 10), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 10, 10)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry().size(), QSize(10, 10)); clientAddedSpy.clear(); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(635, 507, 10, 10)); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QCOMPARE(c->geometry(), expectedGeometry); }; renderWindow(surface1.data(), QStringLiteral("slotWindowPackLeft"), QRect(0, 507, 10, 10)); renderWindow(surface2.data(), QStringLiteral("slotWindowPackUp"), QRect(635, 0, 10, 10)); renderWindow(surface3.data(), QStringLiteral("slotWindowPackRight"), QRect(1270, 507, 10, 10)); renderWindow(surface4.data(), QStringLiteral("slotWindowPackDown"), QRect(635, 1014, 10, 10)); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QTEST(c->geometry(), "expectedGeometry"); } void MoveResizeWindowTest::testGrowShrink_data() { QTest::addColumn("methodCall"); QTest::addColumn("expectedGeometry"); QTest::newRow("grow vertical") << QStringLiteral("slotWindowGrowVertical") << QRect(590, 487, 100, 537); QTest::newRow("grow horizontal") << QStringLiteral("slotWindowGrowHorizontal") << QRect(590, 487, 690, 50); QTest::newRow("shrink vertical") << QStringLiteral("slotWindowShrinkVertical") << QRect(590, 487, 100, 23); QTest::newRow("shrink horizontal") << QStringLiteral("slotWindowShrinkHorizontal") << QRect(590, 487, 40, 50); } void MoveResizeWindowTest::testGrowShrink() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); // block geometry helper QScopedPointer surface1(m_compositor->createSurface()); QVERIFY(!surface1.isNull()); QScopedPointer shellSurface1(m_shell->createSurface(surface1.data())); QVERIFY(!shellSurface1.isNull()); QImage img1(QSize(650, 514), QImage::Format_ARGB32); img1.fill(Qt::blue); surface1->attachBuffer(m_shm->createBuffer(img1)); surface1->damage(QRect(0, 0, 650, 514)); surface1->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); clientAddedSpy.clear(); workspace()->slotWindowPackRight(); workspace()->slotWindowPackDown(); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); // let's place it centered Placement::self()->placeCentered(c, QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometry(), QRect(590, 487, 100, 50)); QFETCH(QString, methodCall); QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); QVERIFY(sizeChangeSpy.wait()); QImage img2(shellSurface->size(), QImage::Format_ARGB32); img2.fill(Qt::red); surface->attachBuffer(m_shm->createBuffer(img2)); surface->damage(QRect(QPoint(0, 0), shellSurface->size())); surface->commit(Surface::CommitFlag::None); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QTEST(c->geometry(), "expectedGeometry"); } void MoveResizeWindowTest::testPointerMoveEnd_data() { QTest::addColumn("additionalButton"); QTest::newRow("BTN_RIGHT") << BTN_RIGHT; QTest::newRow("BTN_MIDDLE") << BTN_MIDDLE; QTest::newRow("BTN_SIDE") << BTN_SIDE; QTest::newRow("BTN_EXTRA") << BTN_EXTRA; QTest::newRow("BTN_FORWARD") << BTN_FORWARD; QTest::newRow("BTN_BACK") << BTN_BACK; QTest::newRow("BTN_TASK") << BTN_TASK; for (int i=BTN_TASK + 1; i < BTN_JOYSTICK; i++) { QTest::newRow(QByteArray::number(i, 16).constData()) << i; } } void MoveResizeWindowTest::testPointerMoveEnd() { // this test verifies that moving a window through pointer only ends if all buttons are released using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QVERIFY(!c->isMove()); // let's trigger the left button quint32 timestamp = 1; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(!c->isMove()); workspace()->slotWindowMove(); QVERIFY(c->isMove()); // let's press another button QFETCH(int, additionalButton); kwinApp()->platform()->pointerButtonPressed(additionalButton, timestamp++); QVERIFY(c->isMove()); // release the left button, should still have the window moving kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(c->isMove()); // but releasing the other button should now end moving kwinApp()->platform()->pointerButtonReleased(additionalButton, timestamp++); QVERIFY(!c->isMove()); } void MoveResizeWindowTest::testPlasmaShellSurfaceMovable_data() { QTest::addColumn("role"); QTest::addColumn("movable"); QTest::addColumn("movableAcrossScreens"); QTest::addColumn("resizable"); QTest::newRow("normal") << KWayland::Client::PlasmaShellSurface::Role::Normal << true << true << true; QTest::newRow("desktop") << KWayland::Client::PlasmaShellSurface::Role::Desktop << false << false << false; QTest::newRow("panel") << KWayland::Client::PlasmaShellSurface::Role::Panel << false << false << false; QTest::newRow("osd") << KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay << false << false << false; } void MoveResizeWindowTest::testPlasmaShellSurfaceMovable() { // this test verifies that certain window types from PlasmaShellSurface are not moveable or resizable using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); // and a PlasmaShellSurface QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); QFETCH(KWayland::Client::PlasmaShellSurface::Role, role); plasmaSurface->setRole(role); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = clientAddedSpy.first().first().value(); QVERIFY(c); QTEST(c->isMovable(), "movable"); QTEST(c->isMovableAcrossScreens(), "movableAcrossScreens"); QTEST(c->isResizable(), "resizable"); } } WAYLANDTEST_MAIN(KWin::MoveResizeWindowTest) #include "move_resize_window_test.moc" diff --git a/autotests/wayland/platformcursor.cpp b/autotests/wayland/platformcursor.cpp index fecf208ae..2f92a04dc 100644 --- a/autotests/wayland/platformcursor.cpp +++ b/autotests/wayland/platformcursor.cpp @@ -1,71 +1,71 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "cursor.h" -#include "abstract_backend.h" +#include "platform.h" #include "wayland_server.h" namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_platform_cursor-0"); class PlatformCursorTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testPos(); }; void PlatformCursorTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void PlatformCursorTest::testPos() { // this test verifies that the PlatformCursor of the QPA plugin forwards ::pos and ::setPos correctly // that is QCursor should work just like KWin::Cursor // cursor should be centered on screen QCOMPARE(Cursor::pos(), QPoint(639, 511)); QCOMPARE(QCursor::pos(), QPoint(639, 511)); // let's set the pos through QCursor API QCursor::setPos(QPoint(10, 10)); QCOMPARE(Cursor::pos(), QPoint(10, 10)); QCOMPARE(QCursor::pos(), QPoint(10, 10)); // and let's set the pos through Cursor API QCursor::setPos(QPoint(20, 20)); QCOMPARE(Cursor::pos(), QPoint(20, 20)); QCOMPARE(QCursor::pos(), QPoint(20, 20)); } } WAYLANDTEST_MAIN(KWin::PlatformCursorTest) #include "platformcursor.moc" diff --git a/autotests/wayland/pointer_input.cpp b/autotests/wayland/pointer_input.cpp index b4e7360c8..71842c1a6 100644 --- a/autotests/wayland/pointer_input.cpp +++ b/autotests/wayland/pointer_input.cpp @@ -1,954 +1,954 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "deleted.h" #include "pointer_input.h" #include "options.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0"); class PointerInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testWarpingUpdatesFocus(); void testWarpingGeneratesPointerMotion(); void testUpdateFocusAfterScreenChange(); void testModifierClickUnrestrictedMove_data(); void testModifierClickUnrestrictedMove(); void testModifierScrollOpacity_data(); void testModifierScrollOpacity(); void testScrollAction(); void testFocusFollowsMouse(); void testMouseActionInactiveWindow_data(); void testMouseActionInactiveWindow(); void testMouseActionActiveWindow_data(); void testMouseActionActiveWindow(); void testCursorImage(); void testEffectOverrideCursorImage(); private: void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void PointerInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void PointerInputTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void PointerInputTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void PointerInputTest::render(KWayland::Client::Surface *surface, const QSize &size) { QImage img(size, QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(QPoint(0, 0), size)); surface->commit(KWayland::Client::Surface::CommitFlag::None); m_connection->flush(); } void PointerInputTest::testWarpingUpdatesFocus() { // this test verifies that warping the pointer creates pointer enter and leave events using namespace KWayland::Client; // create pointer and signal spy for enter and leave signals auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // currently there should not be a focused pointer surface QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); QVERIFY(!pointer->enteredSurface()); // enter Cursor::setPos(QPoint(25, 25)); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); // window should have focus QCOMPARE(pointer->enteredSurface(), surface); // also on the server QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); // and out again Cursor::setPos(QPoint(250, 250));; QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // there should not be a focused pointer surface anymore QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); QVERIFY(!pointer->enteredSurface()); } void PointerInputTest::testWarpingGeneratesPointerMotion() { // this test verifies that warping the pointer creates pointer motion events using namespace KWayland::Client; // create pointer and signal spy for enter and motion auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy movedSpy(pointer, &Pointer::motion); QVERIFY(movedSpy.isValid()); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // enter kwinApp()->platform()->pointerMotion(QPointF(25, 25), 1); QVERIFY(enteredSpy.wait()); // we get a move event together with the enter, that's actually wrong but also shouldn't harm QVERIFY(movedSpy.wait()); QCOMPARE(movedSpy.count(), 1); QCOMPARE(movedSpy.first().first().toPointF(), QPointF(25, 25)); // now warp Cursor::setPos(QPoint(26, 26)); QVERIFY(movedSpy.wait()); QCOMPARE(movedSpy.count(), 2); QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26)); } void PointerInputTest::testUpdateFocusAfterScreenChange() { // this test verifies that a pointer enter event is generated when the cursor changes to another // screen due to removal of screen using namespace KWayland::Client; // ensure cursor is on second screen Cursor::setPos(1500, 300); // create pointer and signal spy for enter and motion auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface, QSize(1280, 1024)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); QVERIFY(!window->geometry().contains(Cursor::pos())); QSignalSpy screensChangedSpy(screens(), &Screens::changed); QVERIFY(screensChangedSpy.isValid()); // now let's remove the screen containing the cursor QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 1)); QMetaObject::invokeMethod(kwinApp()->platform(), "sizeChanged", Qt::QueuedConnection); QVERIFY(screensChangedSpy.wait()); QCOMPARE(screens()->count(), 1); // this should have warped the cursor QCOMPARE(Cursor::pos(), QPoint(639, 511)); QVERIFY(window->geometry().contains(Cursor::pos())); // and we should get an enter event QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); } void PointerInputTest::testModifierClickUnrestrictedMove_data() { QTest::addColumn("modifierKey"); QTest::addColumn("mouseButton"); QTest::addColumn("modKey"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt; QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt; QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt; QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt; QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt; QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt; // now everything with meta QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta; QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta; QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta; QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta; QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta; QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta; } void PointerInputTest::testModifierClickUnrestrictedMove() { // this test ensures that Alt+mouse button press triggers unrestricted move using namespace KWayland::Client; // create pointer and signal spy for button events auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAll1", "Move"); group.writeEntry("CommandAll2", "Move"); group.writeEntry("CommandAll3", "Move"); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // move cursor on window Cursor::setPos(window->geometry().center()); // simulate modifier+click quint32 timestamp = 1; QFETCH(int, modifierKey); QFETCH(int, mouseButton); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); QVERIFY(!window->isMove()); kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++); QVERIFY(window->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); QVERIFY(window->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++); QVERIFY(!window->isMove()); // all of that should not have triggered button events on the surface QCOMPARE(buttonSpy.count(), 0); // also waiting shouldn't give us the event QVERIFY(!buttonSpy.wait(100)); } void PointerInputTest::testModifierScrollOpacity_data() { QTest::addColumn("modifierKey"); QTest::addColumn("modKey"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); QTest::newRow("Left Alt") << KEY_LEFTALT << alt; QTest::newRow("Right Alt") << KEY_RIGHTALT << alt; QTest::newRow("Left Meta") << KEY_LEFTMETA << meta; QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta; } void PointerInputTest::testModifierScrollOpacity() { // this test verifies that mod+wheel performs a window operation and does not // pass the wheel to the window using namespace KWayland::Client; // create pointer and signal spy for button events auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy axisSpy(pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); // first modify the config for this run QFETCH(QString, modKey); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", modKey); group.writeEntry("CommandAllWheel", "change opacity"); group.sync(); workspace()->slotReconfigure(); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // set the opacity to 0.5 window->setOpacity(0.5); QCOMPARE(window->opacity(), 0.5); // move cursor on window Cursor::setPos(window->geometry().center()); // simulate modifier+wheel quint32 timestamp = 1; QFETCH(int, modifierKey); kwinApp()->platform()->keyboardKeyPressed(modifierKey, timestamp++); kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); QCOMPARE(window->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(window->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); // axis should have been filtered out QCOMPARE(axisSpy.count(), 0); QVERIFY(!axisSpy.wait(100)); } void PointerInputTest::testScrollAction() { // this test verifies that scroll on inactive window performs a mouse action using namespace KWayland::Client; auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy axisSpy(pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); // first modify the config for this run KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandWindowWheel", "activate and scroll"); group.sync(); workspace()->slotReconfigure(); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = m_compositor->createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = m_compositor->createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); // move cursor to the inactive window Cursor::setPos(window1->geometry().center()); quint32 timestamp = 1; QVERIFY(!window1->isActive()); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QVERIFY(window1->isActive()); // but also the wheel event should be passed to the window QVERIFY(axisSpy.wait()); // we need to wait a little bit, otherwise the test crashes in effectshandler, needs fixing QTest::qWait(100); } void PointerInputTest::testFocusFollowsMouse() { using namespace KWayland::Client; // need to create a pointer, otherwise it doesn't accept focus auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); // move cursor out of the way of first window to be created Cursor::setPos(900, 900); // first modify the config for this run KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("AutoRaise", true); group.writeEntry("AutoRaiseInterval", 20); group.writeEntry("DelayFocusInterval", 200); group.writeEntry("FocusPolicy", "FocusFollowsMouse"); group.sync(); workspace()->slotReconfigure(); // verify the settings QCOMPARE(options->focusPolicy(), Options::FocusFollowsMouse); QVERIFY(options->isAutoRaise()); QCOMPARE(options->autoRaiseInterval(), 20); QCOMPARE(options->delayFocusInterval(), 200); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = m_compositor->createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = m_compositor->createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // signal spies for active window changed and stacking order changed QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::clientActivated); QVERIFY(activeWindowChangedSpy.isValid()); QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); QVERIFY(!window1->isActive()); QVERIFY(window2->isActive()); // move on top of first window QVERIFY(window1->geometry().contains(10, 10)); QVERIFY(!window2->geometry().contains(10, 10)); Cursor::setPos(10, 10); QVERIFY(stackingOrderChangedSpy.wait()); QCOMPARE(stackingOrderChangedSpy.count(), 1); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); QTRY_VERIFY(window1->isActive()); // move on second window, but move away before active window change delay hits Cursor::setPos(810, 810); QVERIFY(stackingOrderChangedSpy.wait()); QCOMPARE(stackingOrderChangedSpy.count(), 2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); Cursor::setPos(10, 10); QVERIFY(!activeWindowChangedSpy.wait(250)); QVERIFY(window1->isActive()); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); // as we moved back on window 1 that should been raised in the mean time QCOMPARE(stackingOrderChangedSpy.count(), 3); // quickly move on window 2 and back on window 1 should not raise window 2 Cursor::setPos(810, 810); Cursor::setPos(10, 10); QVERIFY(!stackingOrderChangedSpy.wait(250)); } void PointerInputTest::testMouseActionInactiveWindow_data() { QTest::addColumn("button"); QTest::newRow("Left") << quint32(BTN_LEFT); QTest::newRow("Middle") << quint32(BTN_MIDDLE); QTest::newRow("Right") << quint32(BTN_RIGHT); } void PointerInputTest::testMouseActionInactiveWindow() { // this test performs the mouse button window action on an inactive window // it should activate the window and raise it using namespace KWayland::Client; // first modify the config for this run - disable FocusFollowsMouse KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("FocusPolicy", "ClickToFocus"); group.sync(); group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandWindow1", "Activate, raise and pass click"); group.writeEntry("CommandWindow2", "Activate, raise and pass click"); group.writeEntry("CommandWindow3", "Activate, raise and pass click"); group.sync(); workspace()->slotReconfigure(); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = m_compositor->createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = m_compositor->createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // signal spies for active window changed and stacking order changed QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::clientActivated); QVERIFY(activeWindowChangedSpy.isValid()); QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); QVERIFY(!window1->isActive()); QVERIFY(window2->isActive()); // move on top of first window QVERIFY(window1->geometry().contains(10, 10)); QVERIFY(!window2->geometry().contains(10, 10)); Cursor::setPos(10, 10); // no focus follows mouse QVERIFY(!stackingOrderChangedSpy.wait(200)); QVERIFY(stackingOrderChangedSpy.isEmpty()); QVERIFY(activeWindowChangedSpy.isEmpty()); QVERIFY(window2->isActive()); // and click quint32 timestamp = 1; QFETCH(quint32, button); kwinApp()->platform()->pointerButtonPressed(button, timestamp++); // should raise window1 and activate it QCOMPARE(stackingOrderChangedSpy.count(), 1); QVERIFY(!activeWindowChangedSpy.isEmpty()); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); QVERIFY(window1->isActive()); QVERIFY(!window2->isActive()); // release again kwinApp()->platform()->pointerButtonReleased(button, timestamp++); } void PointerInputTest::testMouseActionActiveWindow_data() { QTest::addColumn("clickRaise"); QTest::addColumn("button"); for (quint32 i=BTN_LEFT; i < BTN_JOYSTICK; i++) { QByteArray number = QByteArray::number(i, 16); QTest::newRow(QByteArrayLiteral("click raise/").append(number).constData()) << true << i; QTest::newRow(QByteArrayLiteral("no click raise/").append(number).constData()) << false << i; } } void PointerInputTest::testMouseActionActiveWindow() { // this test verifies the mouse action performed on an active window // for all buttons it should trigger a window raise depending on the // click raise option using namespace KWayland::Client; // create a button spy - all clicks should be passed through auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); // adjust config for this run QFETCH(bool, clickRaise); KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("ClickRaise", clickRaise); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->isClickRaise(), clickRaise); // create two windows QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = m_compositor->createSurface(m_compositor); QVERIFY(surface1); ShellSurface *shellSurface1 = m_shell->createSurface(surface1, surface1); QVERIFY(shellSurface1); render(surface1, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = m_compositor->createSurface(m_compositor); QVERIFY(surface2); ShellSurface *shellSurface2 = m_shell->createSurface(surface2, surface2); QVERIFY(shellSurface2); render(surface2, QSize(800, 800)); QVERIFY(clientAddedSpy.wait()); AbstractClient *window2 = workspace()->activeClient(); QVERIFY(window2); QVERIFY(window1 != window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window2); // geometry of the two windows should be overlapping QVERIFY(window1->geometry().intersects(window2->geometry())); // lower the currently active window workspace()->lowerClient(window2); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); // signal spy for stacking order spy QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); QVERIFY(stackingOrderChangedSpy.isValid()); // move on top of second window QVERIFY(!window1->geometry().contains(900, 900)); QVERIFY(window2->geometry().contains(900, 900)); Cursor::setPos(900, 900); // and click quint32 timestamp = 1; QFETCH(quint32, button); kwinApp()->platform()->pointerButtonPressed(button, timestamp++); QVERIFY(buttonSpy.wait()); if (clickRaise) { QCOMPARE(stackingOrderChangedSpy.count(), 1); QTRY_COMPARE_WITH_TIMEOUT(workspace()->topClientOnDesktop(1, -1), window2, 200); } else { QCOMPARE(stackingOrderChangedSpy.count(), 0); QVERIFY(!stackingOrderChangedSpy.wait(100)); QCOMPARE(workspace()->topClientOnDesktop(1, -1), window1); } // release again kwinApp()->platform()->pointerButtonReleased(button, timestamp++); } void PointerInputTest::testCursorImage() { // this test verifies that the pointer image gets updated correctly from the client provided data using namespace KWayland::Client; // we need a pointer to get the enter event auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); // move cursor somewhere the new window won't open Cursor::setPos(800, 800); auto p = input()->pointer(); // at the moment it should be the fallback cursor QVERIFY(!p->cursorImage().isNull()); // create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // move cursor to center of window, this should first set a null pointer Cursor::setPos(window->geometry().center()); QCOMPARE(p->window().data(), window); QVERIFY(p->cursorImage().isNull()); QVERIFY(enteredSpy.wait()); // create a cursor on the pointer Surface *cursorSurface = m_compositor->createSurface(m_compositor); QVERIFY(cursorSurface); QSignalSpy cursorRenderedSpy(cursorSurface, &Surface::frameRendered); QVERIFY(cursorRenderedSpy.isValid()); QImage red = QImage(QSize(10, 10), QImage::Format_ARGB32); red.fill(Qt::red); cursorSurface->attachBuffer(m_shm->createBuffer(red)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(); pointer->setCursor(cursorSurface, QPoint(5, 5)); QVERIFY(cursorRenderedSpy.wait()); QCOMPARE(p->cursorImage(), red); QCOMPARE(p->cursorHotSpot(), QPoint(5, 5)); // change hotspot pointer->setCursor(cursorSurface, QPoint(6, 6)); m_connection->flush(); QTRY_COMPARE(p->cursorHotSpot(), QPoint(6, 6)); QCOMPARE(p->cursorImage(), red); // change the buffer QImage blue = QImage(QSize(10, 10), QImage::Format_ARGB32); blue.fill(Qt::blue); auto b = m_shm->createBuffer(blue); cursorSurface->attachBuffer(b); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(); QVERIFY(cursorRenderedSpy.wait()); QTRY_COMPARE(p->cursorImage(), blue); QCOMPARE(p->cursorHotSpot(), QPoint(6, 6)); // hide the cursor pointer->setCursor(nullptr); m_connection->flush(); QTRY_VERIFY(p->cursorImage().isNull()); // move cursor somewhere else, should reset to fallback cursor Cursor::setPos(window->geometry().bottomLeft() + QPoint(20, 20)); QVERIFY(p->window().isNull()); QVERIFY(!p->cursorImage().isNull()); } class HelperEffect : public Effect { Q_OBJECT public: HelperEffect() {} ~HelperEffect() {} }; void PointerInputTest::testEffectOverrideCursorImage() { // this test verifies the effect cursor override handling using namespace KWayland::Client; // we need a pointer to get the enter event and set a cursor auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); // move cursor somewhere the new window won't open Cursor::setPos(800, 800); auto p = input()->pointer(); // here we should have the fallback cursor const QImage fallback = p->cursorImage(); QVERIFY(!fallback.isNull()); // now let's create a window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // and move cursor to the window QVERIFY(!window->geometry().contains(QPoint(800, 800))); Cursor::setPos(window->geometry().center()); QVERIFY(enteredSpy.wait()); // cursor image should be null QVERIFY(p->cursorImage().isNull()); // now create an effect and set an override cursor QScopedPointer effect(new HelperEffect); effects->startMouseInterception(effect.data(), Qt::SizeAllCursor); const QImage sizeAll = p->cursorImage(); QVERIFY(!sizeAll.isNull()); QVERIFY(sizeAll != fallback); QVERIFY(leftSpy.wait()); // let's change to arrow cursor, this should be our fallback effects->defineCursor(Qt::ArrowCursor); QCOMPARE(p->cursorImage(), fallback); // back to size all effects->defineCursor(Qt::SizeAllCursor); QCOMPARE(p->cursorImage(), sizeAll); // move cursor outside the window area Cursor::setPos(800, 800); // and end the override, which should switch to fallback effects->stopMouseInterception(effect.data()); QCOMPARE(p->cursorImage(), fallback); // start mouse interception again effects->startMouseInterception(effect.data(), Qt::SizeAllCursor); QCOMPARE(p->cursorImage(), sizeAll); // move cursor to area of window Cursor::setPos(window->geometry().center()); // this should not result in an enter event QVERIFY(!enteredSpy.wait(100)); // after ending the interception we should get an enter event effects->stopMouseInterception(effect.data()); QVERIFY(enteredSpy.wait()); QVERIFY(p->cursorImage().isNull()); } } WAYLANDTEST_MAIN(KWin::PointerInputTest) #include "pointer_input.moc" diff --git a/autotests/wayland/quick_tiling_test.cpp b/autotests/wayland/quick_tiling_test.cpp index db4524cc8..1cb2fd200 100644 --- a/autotests/wayland/quick_tiling_test.cpp +++ b/autotests/wayland/quick_tiling_test.cpp @@ -1,525 +1,525 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::AbstractClient::QuickTileMode) Q_DECLARE_METATYPE(KWin::MaximizeMode) namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); class QuickTilingTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testQuickTiling_data(); void testQuickTiling(); void testQuickMaximizing_data(); void testQuickMaximizing(); void testQuickTilingKeyboardMove_data(); void testQuickTilingKeyboardMove(); void testQuickTilingPointerMove_data(); void testQuickTilingPointerMove(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void QuickTilingTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType("MaximizeMode"); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); // set custom config which disables the Outline KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("Outline"); group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); group.sync(); kwinApp()->setConfig(config); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); } void QuickTilingTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); screens()->setCurrent(0); } void QuickTilingTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } void QuickTilingTest::testQuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("secondScreen"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testQuickTiling() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(AbstractClient::QuickTileMode, mode); QFETCH(QRect, expectedGeometry); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 1); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), mode); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), expectedGeometry.size()); // attach a new image img = QImage(expectedGeometry.size(), QImage::Format_ARGB32); img.fill(Qt::red); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(QPoint(0, 0), expectedGeometry.size())); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); QCOMPARE(geometryChangedSpy.count(), 1); QCOMPARE(c->geometry(), expectedGeometry); // send window to other screen QCOMPARE(c->screen(), 0); c->sendToScreen(1); QCOMPARE(c->screen(), 1); // quick tile should not be changed QEXPECT_FAIL("maximize", "Maximize loses the state", Continue); QCOMPARE(c->quickTileMode(), mode); QTEST(c->geometry(), "secondScreen"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) AbstractClient::QuickTileMode(AbstractClient::QuickTile##name) QTest::newRow("maximize") << FLAG(Maximize); QTest::newRow("none") << FLAG(None); #undef FLAG } void QuickTilingTest::testQuickMaximizing() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QSignalSpy maximizeChangedSpy1(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,MaximizeMode))); QVERIFY(maximizeChangedSpy1.isValid()); QSignalSpy maximizeChangedSpy2(c, SIGNAL(clientMaximizedStateChanged(KWin::AbstractClient*,bool,bool))); QVERIFY(maximizeChangedSpy2.isValid()); c->setQuickTileMode(AbstractClient::QuickTileMaximize, true); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(maximizeChangedSpy1.count(), 1); QCOMPARE(maximizeChangedSpy1.first().first().value(), c); QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); QCOMPARE(maximizeChangedSpy2.count(), 1); QCOMPARE(maximizeChangedSpy2.first().first().value(), c); QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); // at this point the geometry did not yet change QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); // but quick tile mode already changed QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileMaximize); QCOMPARE(c->maximizeMode(), MaximizeFull); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // but we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 1); QCOMPARE(sizeChangeSpy.first().first().toSize(), QSize(1280, 1024)); // attach a new image img = QImage(QSize(1280, 1024), QImage::Format_ARGB32); img.fill(Qt::red); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 1280, 1024)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 2); QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // go back to quick tile none QFETCH(AbstractClient::QuickTileMode, mode); c->setQuickTileMode(mode, true); QCOMPARE(quickTileChangedSpy.count(), 2); QCOMPARE(maximizeChangedSpy1.count(), 2); QCOMPARE(maximizeChangedSpy1.last().first().value(), c); QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); QCOMPARE(maximizeChangedSpy2.count(), 2); QCOMPARE(maximizeChangedSpy2.last().first().value(), c); QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); // geometry not yet changed QCOMPARE(c->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(sizeChangeSpy.wait()); QCOMPARE(sizeChangeSpy.count(), 2); QCOMPARE(sizeChangeSpy.last().first().toSize(), QSize(100, 50)); // render again img = QImage(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::yellow); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(geometryChangedSpy.count(), 4); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); } void QuickTilingTest::testQuickTilingKeyboardMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingKeyboardMove() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); while (Cursor::pos().x() > targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFT, timestamp++); } while (Cursor::pos().x() < targetPos.x()) { kwinApp()->platform()->keyboardKeyPressed(KEY_RIGHT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_RIGHT, timestamp++); } while (Cursor::pos().y() < targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_DOWN, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_DOWN, timestamp++); } while (Cursor::pos().y() > targetPos.y()) { kwinApp()->platform()->keyboardKeyPressed(KEY_UP, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_UP, timestamp++); } kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_ENTER, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_ENTER, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } void QuickTilingTest::testQuickTilingPointerMove_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileRight); QTest::newRow("right") << QPoint(2559, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileRight); QTest::newRow("bottomRight") << QPoint(2559, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileRight); QTest::newRow("bottomLeft") << QPoint(0, 1023) << AbstractClient::QuickTileMode(AbstractClient::QuickTileBottom | AbstractClient::QuickTileLeft); QTest::newRow("Left") << QPoint(0, 512) << AbstractClient::QuickTileMode(AbstractClient::QuickTileLeft); QTest::newRow("topLeft") << QPoint(0, 24) << AbstractClient::QuickTileMode(AbstractClient::QuickTileTop | AbstractClient::QuickTileLeft); } void QuickTilingTest::testQuickTilingPointerMove() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy sizeChangeSpy(shellSurface.data(), &ShellSurface::sizeChanged); QVERIFY(sizeChangeSpy.isValid()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 50)); QCOMPARE(c->quickTileMode(), AbstractClient::QuickTileNone); QCOMPARE(c->maximizeMode(), MaximizeRestore); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QFETCH(QPoint, targetPos); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(targetPos, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(Cursor::pos(), targetPos); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); } } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/autotests/wayland/start_test.cpp b/autotests/wayland/start_test.cpp index acfdff58d..8feb17861 100644 --- a/autotests/wayland/start_test.cpp +++ b/autotests/wayland/start_test.cpp @@ -1,168 +1,168 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_start_test-0"); class StartTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testScreens(); void testNoWindowsAtStart(); void testCreateWindow(); }; void StartTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void StartTest::testScreens() { QCOMPARE(screens()->count(), 1); QCOMPARE(screens()->size(), QSize(1280, 1024)); QCOMPARE(screens()->geometry(), QRect(0, 0, 1280, 1024)); } void StartTest::testNoWindowsAtStart() { QVERIFY(workspace()->clientList().isEmpty()); QVERIFY(workspace()->desktopList().isEmpty()); QVERIFY(workspace()->allClientList().isEmpty()); QVERIFY(workspace()->deletedList().isEmpty()); QVERIFY(workspace()->unmanagedList().isEmpty()); QVERIFY(waylandServer()->clients().isEmpty()); } void StartTest::testCreateWindow() { // first we need to connect to the server using namespace KWayland::Client; auto connection = new ConnectionThread; QSignalSpy connectedSpy(connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); connection->setSocketName(s_socketName); QThread *thread = new QThread(this); connection->moveToThread(thread); thread->start(); connection->initConnection(); QVERIFY(connectedSpy.wait()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QSignalSpy shellClientRemovedSpy(waylandServer(), &WaylandServer::shellClientRemoved); QVERIFY(shellClientRemovedSpy.isValid()); { EventQueue queue; queue.setup(connection); Registry registry; registry.setEventQueue(&queue); registry.create(connection); QSignalSpy registryAnnouncedSpy(®istry, &Registry::interfacesAnnounced); QVERIFY(registryAnnouncedSpy.isValid()); registry.setup(); QVERIFY(registryAnnouncedSpy.wait()); const auto compositorData = registry.interface(Registry::Interface::Compositor); const auto shellData = registry.interface(Registry::Interface::Shell); const auto shmData = registry.interface(Registry::Interface::Shm); QScopedPointer compositor(registry.createCompositor(compositorData.name, compositorData.version)); QVERIFY(!compositor.isNull()); QScopedPointer shell(registry.createShell(shellData.name, shellData.version)); QVERIFY(!shell.isNull()); QScopedPointer surface(compositor->createSurface()); QVERIFY(!surface.isNull()); QSignalSpy surfaceRenderedSpy(surface.data(), &Surface::frameRendered); QVERIFY(surfaceRenderedSpy.isValid()); QScopedPointer shellSurface(shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); connection->flush(); QVERIFY(waylandServer()->clients().isEmpty()); // now dispatch should give us the client waylandServer()->dispatch(); QCOMPARE(waylandServer()->clients().count(), 1); // but still not yet in workspace QVERIFY(workspace()->allClientList().isEmpty()); // icon geometry accesses windowManagementInterface which only exists after window became visible // verify that accessing doesnt't crash QVERIFY(waylandServer()->clients().first()->iconGeometry().isNull()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); QScopedPointer shm(registry.createShmPool(shmData.name, shmData.version)); QVERIFY(!shm.isNull()); surface->attachBuffer(shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(); connection->flush(); QVERIFY(shellClientAddedSpy.wait()); QCOMPARE(workspace()->allClientList().count(), 1); QCOMPARE(workspace()->allClientList().first(), waylandServer()->clients().first()); QVERIFY(workspace()->activeClient()); QCOMPARE(workspace()->activeClient()->pos(), QPoint(0, 0)); QCOMPARE(workspace()->activeClient()->size(), QSize(100, 50)); QCOMPARE(workspace()->activeClient()->geometry(), QRect(0, 0, 100, 50)); // and kwin will render it QVERIFY(surfaceRenderedSpy.wait()); } // this should tear down everything again QVERIFY(shellClientRemovedSpy.wait()); QVERIFY(waylandServer()->clients().isEmpty()); // cleanup connection->deleteLater(); thread->quit(); thread->wait(); } } WAYLANDTEST_MAIN(KWin::StartTest) #include "start_test.moc" diff --git a/autotests/wayland/touch_input_test.cpp b/autotests/wayland/touch_input_test.cpp index 241314529..f8d20751a 100644 --- a/autotests/wayland/touch_input_test.cpp +++ b/autotests/wayland/touch_input_test.cpp @@ -1,323 +1,323 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_touch_input-0"); class TouchInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testMultipleTouchPoints(); void testCancel(); void testTouchMouseAction(); private: AbstractClient *showWindow(); KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::Touch *m_touch = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void TouchInputTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void TouchInputTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); QSignalSpy hasTouchSpy(m_seat, &Seat::hasTouchChanged); QVERIFY(hasTouchSpy.isValid()); QVERIFY(hasTouchSpy.wait()); m_touch = m_seat->createTouch(m_seat); QVERIFY(m_touch); QVERIFY(m_touch->isValid()); screens()->setCurrent(0); Cursor::setPos(QPoint(1280, 512)); } void TouchInputTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_touch; m_touch = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } AbstractClient *TouchInputTest::showWindow() { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); VERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); VERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); VERIFY(shellSurface); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); VERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); VERIFY(c); COMPARE(clientAddedSpy.first().first().value(), c); #undef VERIFY #undef COMPARE return c; } void TouchInputTest::testMultipleTouchPoints() { using namespace KWayland::Client; AbstractClient *c = showWindow(); c->move(100, 100); QVERIFY(c); QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy pointAddedSpy(m_touch, &Touch::pointAdded); QVERIFY(pointAddedSpy.isValid()); QSignalSpy pointMovedSpy(m_touch, &Touch::pointMoved); QVERIFY(pointMovedSpy.isValid()); QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); QSignalSpy endedSpy(m_touch, &Touch::sequenceEnded); QVERIFY(endedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(125, 125), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 1); QCOMPARE(m_touch->sequence().first()->isDown(), true); QCOMPARE(m_touch->sequence().first()->position(), QPointF(25, 25)); QCOMPARE(pointAddedSpy.count(), 0); QCOMPARE(pointMovedSpy.count(), 0); // a point outside the window kwinApp()->platform()->touchDown(2, QPointF(0, 0), timestamp++); QVERIFY(pointAddedSpy.wait()); QCOMPARE(pointAddedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().at(1)->isDown(), true); QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(-100, -100)); QCOMPARE(pointMovedSpy.count(), 0); // let's move that one kwinApp()->platform()->touchMotion(2, QPointF(100, 100), timestamp++); QVERIFY(pointMovedSpy.wait()); QCOMPARE(pointMovedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().at(1)->isDown(), true); QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(0, 0)); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 1); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().first()->isDown(), false); QCOMPARE(endedSpy.count(), 0); kwinApp()->platform()->touchUp(2, timestamp++); QVERIFY(pointRemovedSpy.wait()); QCOMPARE(pointRemovedSpy.count(), 2); QCOMPARE(m_touch->sequence().count(), 2); QCOMPARE(m_touch->sequence().first()->isDown(), false); QCOMPARE(m_touch->sequence().at(1)->isDown(), false); QCOMPARE(endedSpy.count(), 1); } void TouchInputTest::testCancel() { using namespace KWayland::Client; AbstractClient *c = showWindow(); c->move(100, 100); QVERIFY(c); QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy cancelSpy(m_touch, &Touch::sequenceCanceled); QVERIFY(cancelSpy.isValid()); QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); QVERIFY(pointRemovedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, QPointF(125, 125), timestamp++); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); // cancel kwinApp()->platform()->touchCancel(); QVERIFY(cancelSpy.wait()); QCOMPARE(cancelSpy.count(), 1); kwinApp()->platform()->touchUp(1, timestamp++); QVERIFY(!pointRemovedSpy.wait(100)); QCOMPARE(pointRemovedSpy.count(), 0); } void TouchInputTest::testTouchMouseAction() { // this test verifies that a touch down on an inactive client will activate it using namespace KWayland::Client; // create two windows AbstractClient *c1 = showWindow(); QVERIFY(c1); AbstractClient *c2 = showWindow(); QVERIFY(c2); QVERIFY(!c1->isActive()); QVERIFY(c2->isActive()); // also create a sequence started spy as the touch event should be passed through QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); QVERIFY(sequenceStartedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(1, c1->geometry().center(), timestamp++); QVERIFY(c1->isActive()); QVERIFY(sequenceStartedSpy.wait()); QCOMPARE(sequenceStartedSpy.count(), 1); // cleanup kwinApp()->platform()->touchCancel(); } } WAYLANDTEST_MAIN(KWin::TouchInputTest) #include "touch_input_test.moc" diff --git a/autotests/wayland/transient_no_input_test.cpp b/autotests/wayland/transient_no_input_test.cpp index f65fcea20..d6636058c 100644 --- a/autotests/wayland/transient_no_input_test.cpp +++ b/autotests/wayland/transient_no_input_test.cpp @@ -1,191 +1,191 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_no_input-0"); class TransientNoInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testTransientNoFocus(); private: KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void TransientNoInputTest::initTestCase() { qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } void TransientNoInputTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); } void TransientNoInputTest::cleanup() { delete m_compositor; m_compositor = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } } void TransientNoInputTest::testTransientNoFocus() { using namespace KWayland::Client; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(m_shell->createSurface(surface.data())); QVERIFY(!shellSurface.isNull()); // let's render QImage img(QSize(100, 50), QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(0, 0, 100, 50)); surface->commit(); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); QVERIFY(c); QCOMPARE(clientAddedSpy.first().first().value(), c); // let's create a transient with no input QScopedPointer transientSurface(m_compositor->createSurface()); QVERIFY(!transientSurface.isNull()); QScopedPointer transientShellSurface(m_shell->createSurface(transientSurface.data())); QVERIFY(!transientShellSurface.isNull()); transientShellSurface->setTransient(surface.data(), QPoint(10, 20), ShellSurface::TransientFlag::NoFocus); m_connection->flush(); // let's render QImage img2(QSize(200, 20), QImage::Format_ARGB32); img2.fill(Qt::red); transientSurface->attachBuffer(m_shm->createBuffer(img2)); transientSurface->damage(QRect(0, 0, 200, 20)); transientSurface->commit(); m_connection->flush(); QVERIFY(clientAddedSpy.wait()); // get the latest ShellClient auto transientClient = clientAddedSpy.last().first().value(); QVERIFY(transientClient != c); QCOMPARE(transientClient->geometry(), QRect(c->x() + 10, c->y() + 20, 200, 20)); QVERIFY(transientClient->isTransient()); QCOMPARE(transientClient->transientPlacementHint(), QPoint(10, 20)); QVERIFY(!transientClient->wantsInput()); // workspace's active window should not have changed QCOMPARE(workspace()->activeClient(), c); } } WAYLANDTEST_MAIN(KWin::TransientNoInputTest) #include "transient_no_input_test.moc" diff --git a/autotests/wayland/transient_placement.cpp b/autotests/wayland/transient_placement.cpp index 0921f6523..0879a0ee1 100644 --- a/autotests/wayland/transient_placement.cpp +++ b/autotests/wayland/transient_placement.cpp @@ -1,314 +1,314 @@ /******************************************************************** 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 "kwin_wayland_test.h" -#include "abstract_backend.h" +#include "platform.h" #include "abstract_client.h" #include "cursor.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_placement-0"); class TransientPlacementTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testSimplePosition_data(); void testSimplePosition(); void testDecorationPosition_data(); void testDecorationPosition(); private: AbstractClient *showWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); KWayland::Client::Surface *surfaceForClient(AbstractClient *c) const; KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::ServerSideDecorationManager *m_deco = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; }; void TransientPlacementTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); waylandServer()->init(s_socketName.toLocal8Bit()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void TransientPlacementTest::init() { using namespace KWayland::Client; // setup connection m_connection = new ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); QVERIFY(shellSpy.isValid()); QVERIFY(compositorSpy.isValid()); QVERIFY(seatSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); QVERIFY(!shellSpy.isEmpty()); QVERIFY(!seatSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); QVERIFY(m_shell->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QVERIFY(m_seat->isValid()); QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerSpy.isValid()); QVERIFY(hasPointerSpy.wait()); m_deco = registry.createServerSideDecorationManager(registry.interface(Registry::Interface::ServerSideDecorationManager).name, registry.interface(Registry::Interface::ServerSideDecorationManager).version, this); QVERIFY(m_deco->isValid()); screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void TransientPlacementTest::cleanup() { delete m_deco; m_deco = nullptr; delete m_compositor; m_compositor = nullptr; delete m_seat; m_seat = nullptr; delete m_shm; m_shm = nullptr; delete m_shell; m_shell = nullptr; delete m_queue; m_queue = nullptr; if (m_thread) { m_connection->deleteLater(); m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; m_connection = nullptr; } } AbstractClient *TransientPlacementTest::showWindow(const QSize &size, bool decorated, KWayland::Client::Surface *parent, const QPoint &offset) { using namespace KWayland::Client; #define VERIFY(statement) \ if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ return nullptr; #define COMPARE(actual, expected) \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ return nullptr; QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); VERIFY(clientAddedSpy.isValid()); Surface *surface = m_compositor->createSurface(m_compositor); VERIFY(surface); ShellSurface *shellSurface = m_shell->createSurface(surface, surface); VERIFY(shellSurface); if (parent) { shellSurface->setTransient(parent, offset); } if (decorated) { auto deco = m_deco->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); VERIFY(decoSpy.isValid()); VERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); VERIFY(decoSpy.wait()); COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); } // let's render QImage img(size, QImage::Format_ARGB32); img.fill(Qt::blue); surface->attachBuffer(m_shm->createBuffer(img)); surface->damage(QRect(QPoint(0, 0), size)); surface->commit(Surface::CommitFlag::None); m_connection->flush(); VERIFY(clientAddedSpy.wait()); AbstractClient *c = workspace()->activeClient(); VERIFY(c); COMPARE(clientAddedSpy.first().first().value(), c); #undef VERIFY #undef COMPARE return c; } KWayland::Client::Surface *TransientPlacementTest::surfaceForClient(AbstractClient *c) const { const auto &surfaces = KWayland::Client::Surface::all(); auto it = std::find_if(surfaces.begin(), surfaces.end(), [c] (KWayland::Client::Surface *s) { return s->id() == c->surface()->id(); }); if (it != surfaces.end()) { return *it; } return nullptr; } void TransientPlacementTest::testSimplePosition_data() { QTest::addColumn("parentSize"); QTest::addColumn("parentPosition"); QTest::addColumn("transientSize"); QTest::addColumn("transientOffset"); QTest::addColumn("expectedGeometry"); QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); QTest::newRow("right border") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(1279, 50) << QRect(1269, 50, 10, 100); QTest::newRow("bottom border") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(512, 1020) << QRect(512, 920, 10, 100); QTest::newRow("bottom right") << QSize(1280, 1024) << QPoint(0, 0) << QSize(10, 100) << QPoint(1279, 1020) << QRect(1269, 920, 10, 100); QTest::newRow("top border") << QSize(1280, 1024) << QPoint(0, -100) << QSize(10, 100) << QPoint(512, 50) << QRect(512, 0, 10, 100); QTest::newRow("left border") << QSize(1280, 1024) << QPoint(-100, 0) << QSize(100, 10) << QPoint(50, 512) << QRect(0, 512, 100, 10); QTest::newRow("top left") << QSize(1280, 1024) << QPoint(-100, -100) << QSize(100, 100) << QPoint(50, 50) << QRect(0, 0, 100, 100); QTest::newRow("bottom left") << QSize(1280, 1024) << QPoint(-100, 0) << QSize(100, 100) << QPoint(50, 1000) << QRect(0, 900, 100, 100); } void TransientPlacementTest::testSimplePosition() { // this test verifies that the position of a transient window is taken from the passed position // there are no further constraints like window too large to fit screen, cascading transients, etc // some test cases also verify that the transient fits on the screen QFETCH(QSize, parentSize); AbstractClient *parent = showWindow(parentSize); QVERIFY(parent->clientPos().isNull()); QVERIFY(!parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); QTEST(transient->geometry(), "expectedGeometry"); } void TransientPlacementTest::testDecorationPosition_data() { QTest::addColumn("parentSize"); QTest::addColumn("parentPosition"); QTest::addColumn("transientSize"); QTest::addColumn("transientOffset"); QTest::addColumn("expectedGeometry"); QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); } void TransientPlacementTest::testDecorationPosition() { // this test verifies that a transient window is correctly placed if the parent window has a // server side decoration QFETCH(QSize, parentSize); AbstractClient *parent = showWindow(parentSize, true); QVERIFY(!parent->clientPos().isNull()); QVERIFY(parent->isDecorated()); QFETCH(QPoint, parentPosition); parent->move(parentPosition); QFETCH(QSize, transientSize); QFETCH(QPoint, transientOffset); AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); QVERIFY(transient); QVERIFY(!transient->isDecorated()); QVERIFY(transient->hasTransientPlacementHint()); QFETCH(QRect, expectedGeometry); expectedGeometry.translate(parent->clientPos()); QCOMPARE(transient->geometry(), expectedGeometry); } } WAYLANDTEST_MAIN(KWin::TransientPlacementTest) #include "transient_placement.moc" diff --git a/backends/drm/drm_backend.h b/backends/drm/drm_backend.h index 1fa9d1a52..41fbcf353 100644 --- a/backends/drm/drm_backend.h +++ b/backends/drm/drm_backend.h @@ -1,137 +1,137 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H -#include "abstract_backend.h" +#include "platform.h" #include "input.h" #include "drm_buffer.h" #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include struct gbm_bo; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; class DrmOutput; class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); virtual ~DrmBackend(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; DrmBuffer *createBuffer(const QSize &size); DrmBuffer *createBuffer(gbm_surface *surface); void present(DrmBuffer *buffer, DrmOutput *output); QSize size() const; int fd() const { return m_fd; } QVector outputs() const { return m_outputs; } QVector buffers() const { return m_buffers; } void bufferDestroyed(DrmBuffer *b); void outputWentOff(); void checkOutputsAreOn(); public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: void outputRemoved(KWin::DrmOutput *output); void outputAdded(KWin::DrmOutput *output); private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void queryResources(); void setCursor(); void updateCursor(); void hideCursor(); void moveCursor(); void initCursor(); quint32 findCrtc(drmModeRes *res, drmModeConnector *connector, bool *ok = nullptr); bool crtcIsUsed(quint32 crtc); void outputDpmsChanged(); void readOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; QVector m_outputs; DrmBuffer *m_cursor[2]; int m_cursorIndex = 0; int m_pageFlipsPending = 0; bool m_active = false; QVector m_buffers; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; }; } #endif diff --git a/backends/fbdev/fb_backend.h b/backends/fbdev/fb_backend.h index afdd88ff3..7aee7eefc 100644 --- a/backends/fbdev/fb_backend.h +++ b/backends/fbdev/fb_backend.h @@ -1,98 +1,98 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_FB_BACKEND_H #define KWIN_FB_BACKEND_H -#include "abstract_backend.h" +#include "platform.h" #include #include namespace KWin { class KWIN_EXPORT FramebufferBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "fbdev.json") public: explicit FramebufferBackend(QObject *parent = nullptr); virtual ~FramebufferBackend(); Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override { return m_resolution; } void init() override; bool isValid() const { return m_fd >= 0; } QSize size() const { return m_resolution; } QSize physicalSize() const { return m_physicalSize; } void map(); void unmap(); void *mappedMemory() const { return m_memory; } int bytesPerLine() const { return m_bytesPerLine; } int bufferSize() const { return m_bufferLength; } quint32 bitsPerPixel() const { return m_bitsPerPixel; } QImage::Format imageFormat() const; private: void openFrameBuffer(); bool queryScreenInfo(); QSize m_resolution; QSize m_physicalSize; QByteArray m_id; struct Color { quint32 offset; quint32 length; }; Color m_red; Color m_green; Color m_blue; Color m_alpha; quint32 m_bitsPerPixel = 0; int m_fd = -1; quint32 m_bufferLength = 0; int m_bytesPerLine = 0; void *m_memory = nullptr; }; } #endif diff --git a/backends/hwcomposer/hwcomposer_backend.h b/backends/hwcomposer/hwcomposer_backend.h index c94f6cfc5..e7125adf7 100644 --- a/backends/hwcomposer/hwcomposer_backend.h +++ b/backends/hwcomposer/hwcomposer_backend.h @@ -1,142 +1,142 @@ /******************************************************************** 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 3 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_HWCOMPOSER_BACKEND_H #define KWIN_HWCOMPOSER_BACKEND_H -#include "abstract_backend.h" +#include "platform.h" #include "input.h" #include #include #include // libhybris #include // needed as hwcomposer_window.h includes EGL which on non-arm includes Xlib #include typedef struct hwc_display_contents_1 hwc_display_contents_1_t; typedef struct hwc_layer_1 hwc_layer_1_t; typedef struct hwc_composer_device_1 hwc_composer_device_1_t; struct light_device_t; class HWComposerNativeWindowBuffer; namespace KWin { class HwcomposerWindow; class BacklightInputEventFilter; class HwcomposerBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "hwcomposer.json") public: explicit HwcomposerBackend(QObject *parent = nullptr); virtual ~HwcomposerBackend(); void init() override; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QSize screenSize() const override { return m_displaySize; } HwcomposerWindow *createSurface(); QSize size() const { return m_displaySize; } hwc_composer_device_1_t *device() const { return m_device; } int refreshRate() const { return m_refreshRate; } void enableVSync(bool enable); void waitVSync(); void wakeVSync(); bool isBacklightOff() const { return m_outputBlank; } Q_SIGNALS: void outputBlankChanged(); private Q_SLOTS: void toggleBlankOutput(); private: void initLights(); void toggleScreenBrightness(); QSize m_displaySize; hwc_composer_device_1_t *m_device = nullptr; light_device_t *m_lights = nullptr; bool m_outputBlank = true; int m_refreshRate = 60000; int m_vsyncInterval = 16; bool m_hasVsync = false; QMutex m_vsyncMutex; QWaitCondition m_vsyncWaitCondition; QScopedPointer m_filter; }; class HwcomposerWindow : public HWComposerNativeWindow { public: virtual ~HwcomposerWindow(); void present(HWComposerNativeWindowBuffer *buffer); private: friend HwcomposerBackend; HwcomposerWindow(HwcomposerBackend *backend); HwcomposerBackend *m_backend; hwc_display_contents_1_t **m_list; }; class BacklightInputEventFilter : public InputEventFilter { public: BacklightInputEventFilter(HwcomposerBackend *backend); virtual ~BacklightInputEventFilter(); bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override; bool wheelEvent(QWheelEvent *event) override; bool keyEvent(QKeyEvent *event) override; bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; bool touchUp(quint32 id, quint32 time) override; private: void toggleBacklight(); HwcomposerBackend *m_backend; QElapsedTimer m_doubleTapTimer; QVector m_touchPoints; bool m_secondTap = false; }; } #endif diff --git a/backends/virtual/virtual_backend.h b/backends/virtual/virtual_backend.h index f0d1e2c7d..ca3d2e1d5 100644 --- a/backends/virtual/virtual_backend.h +++ b/backends/virtual/virtual_backend.h @@ -1,76 +1,76 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_VIRTUAL_BACKEND_H #define KWIN_VIRTUAL_BACKEND_H -#include "abstract_backend.h" +#include "platform.h" #include #include #include class QTemporaryDir; namespace KWin { class KWIN_EXPORT VirtualBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "virtual.json") Q_PROPERTY(QSize size READ size NOTIFY sizeChanged) public: VirtualBackend(QObject *parent = nullptr); virtual ~VirtualBackend(); void init() override; QSize size() const { return m_size; } int outputCount() const { return m_outputCount; } bool saveFrames() const { return !m_screenshotDir.isNull(); } QString screenshotDirPath() const; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend* createQPainterBackend() override; OpenGLBackend *createOpenGLBackend() override; Q_INVOKABLE void setOutputCount(int count) { m_outputCount = count; } Q_SIGNALS: void sizeChanged(); private: QSize m_size; int m_outputCount = 1; QScopedPointer m_screenshotDir; }; } #endif diff --git a/backends/wayland/wayland_backend.h b/backends/wayland/wayland_backend.h index 36ac9f5df..606c03177 100644 --- a/backends/wayland/wayland_backend.h +++ b/backends/wayland/wayland_backend.h @@ -1,175 +1,175 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_WAYLAND_BACKEND_H #define KWIN_WAYLAND_BACKEND_H // KWin -#include "abstract_backend.h" +#include "platform.h" #include #include // Qt #include #include #include #include #include class QTemporaryFile; struct wl_buffer; struct wl_display; struct wl_event_queue; struct wl_seat; namespace KWayland { namespace Client { class Buffer; class ShmPool; class Compositor; class ConnectionThread; class EventQueue; class Keyboard; class Pointer; class Registry; class Seat; class Shell; class ShellSurface; class Surface; class Touch; } } namespace KWin { class WaylandCursorTheme; namespace Wayland { class WaylandBackend; class WaylandSeat; class WaylandSeat : public QObject { Q_OBJECT public: WaylandSeat(wl_seat *seat, WaylandBackend *backend); virtual ~WaylandSeat(); void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot); void installCursorImage(const QImage &image, const QPoint &hotspot); void setInstallCursor(bool install); bool isInstallCursor() const { return m_installCursor; } private: void destroyPointer(); void destroyKeyboard(); void destroyTouch(); KWayland::Client::Seat *m_seat; KWayland::Client::Pointer *m_pointer; KWayland::Client::Keyboard *m_keyboard; KWayland::Client::Touch *m_touch; KWayland::Client::Surface *m_cursor; uint32_t m_enteredSerial; WaylandBackend *m_backend; bool m_installCursor; }; /** * @brief Class encapsulating all Wayland data structures needed by the Egl backend. * * It creates the connection to the Wayland Compositor, sets up the registry and creates * the Wayland surface and its shell mapping. */ class KWIN_EXPORT WaylandBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "wayland.json") public: explicit WaylandBackend(QObject *parent = nullptr); virtual ~WaylandBackend(); void init() override; wl_display *display(); KWayland::Client::Compositor *compositor(); KWayland::Client::ShmPool *shmPool(); KWayland::Client::Surface *surface() const; QSize shellSurfaceSize() const; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend *createQPainterBackend() override; QSize screenSize() const override { return shellSurfaceSize(); } void flush(); Q_SIGNALS: void shellSurfaceSizeChanged(const QSize &size); void systemCompositorDied(); void connectionFailed(); private: void initConnection(); void createSurface(); wl_display *m_display; KWayland::Client::EventQueue *m_eventQueue; KWayland::Client::Registry *m_registry; KWayland::Client::Compositor *m_compositor; KWayland::Client::Shell *m_shell; KWayland::Client::Surface *m_surface; KWayland::Client::ShellSurface *m_shellSurface; QScopedPointer m_seat; KWayland::Client::ShmPool *m_shm; KWayland::Client::ConnectionThread *m_connectionThreadObject; QThread *m_connectionThread; }; inline wl_display *WaylandBackend::display() { return m_display; } inline KWayland::Client::Compositor *WaylandBackend::compositor() { return m_compositor; } inline KWayland::Client::ShmPool* WaylandBackend::shmPool() { return m_shm; } inline KWayland::Client::Surface *WaylandBackend::surface() const { return m_surface; } } // namespace Wayland } // namespace KWin #endif // KWIN_WAYLAND_BACKEND_H diff --git a/backends/x11/x11windowed_backend.h b/backends/x11/x11windowed_backend.h index 559289185..25121f907 100644 --- a/backends/x11/x11windowed_backend.h +++ b/backends/x11/x11windowed_backend.h @@ -1,107 +1,107 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_X11WINDOWED_BACKEND_H #define KWIN_X11WINDOWED_BACKEND_H -#include "abstract_backend.h" +#include "platform.h" #include #include #include #include struct _XDisplay; typedef struct _XDisplay Display; typedef struct _XCBKeySymbols xcb_key_symbols_t; class NETWinInfo; namespace KWin { class KWIN_EXPORT X11WindowedBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") Q_PROPERTY(QSize size READ screenSize NOTIFY sizeChanged) public: X11WindowedBackend(QObject *parent = nullptr); virtual ~X11WindowedBackend(); void init() override; QVector screenGeometries() const override; xcb_connection_t *connection() const { return m_connection; } int screenNumer() const { return m_screenNumber; } xcb_window_t window() const { return windowForScreen(0); } xcb_window_t windowForScreen(int screen) const; Display *display() const { return m_display; } xcb_window_t rootWindow() const; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend* createQPainterBackend() override; void warpPointer(const QPointF &globalPos) override; Q_SIGNALS: void sizeChanged(); private: void createWindow(); void startEventReading(); void grabKeyboard(xcb_timestamp_t time); void updateWindowTitle(); void handleEvent(xcb_generic_event_t *event); void handleClientMessage(xcb_client_message_event_t *event); void handleButtonPress(xcb_button_press_event_t *event); void handleExpose(xcb_expose_event_t *event); void updateSize(xcb_configure_notify_event_t *event); void createCursor(const QImage &img, const QPoint &hotspot); xcb_connection_t *m_connection = nullptr; xcb_screen_t *m_screen = nullptr; xcb_key_symbols_t *m_keySymbols = nullptr; int m_screenNumber = 0; struct Output { xcb_window_t window = XCB_WINDOW_NONE; QSize size; QPoint xPosition; QPoint internalPosition; NETWinInfo *winInfo = nullptr; }; QVector m_windows; xcb_atom_t m_protocols = XCB_ATOM_NONE; xcb_atom_t m_deleteWindowProtocol = XCB_ATOM_NONE; xcb_cursor_t m_cursor = XCB_CURSOR_NONE; Display *m_display = nullptr; bool m_keyboardGrabbed = false; }; } #endif diff --git a/composite.cpp b/composite.cpp index 0b9d33f91..ef8488f11 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1332 +1,1332 @@ /******************************************************************** 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 . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "utils.h" #include #include "workspace.h" #include "client.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "scene.h" #include "scene_xrender.h" #include "scene_opengl.h" #include "scene_qpainter.h" #include "screens.h" #include "shadow.h" #include "useractions.h" #include "compositingprefs.h" #include "xcbutils.h" -#include "abstract_backend.h" +#include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason) namespace KWin { extern int currentRefreshRate(); CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()), owning(false) { connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership())); } void CompositorSelectionOwner::looseOwnership() { owning = false; } KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor) static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , cm_selection(NULL) , vBlankInterval(0) , fpsInterval(0) , m_xrrRefreshRate(0) , forceUnredirectCheck(false) , m_finishing(false) , m_timeSinceLastVBlank(0) , m_scene(NULL) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { qRegisterMetaType("Compositor::SuspendReason"); connect(&unredirectTimer, SIGNAL(timeout()), SLOT(delayedCheckUnredirect())); connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart())); connect(options, &Options::configChanged, this, &Compositor::slotConfigChanged); connect(options, SIGNAL(unredirectFullscreenChanged()), SLOT(delayedCheckUnredirect())); unredirectTimer.setSingleShot(true); compositeResetTimer.setSingleShot(true); nextPaintReference.invalidate(); // Initialize the timer // 2 sec which should be enough to restart the compositor static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, SIGNAL(timeout()), SLOT(releaseCompositorSelection())); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties())); if (kwinApp()->operationMode() != Application::OperationModeX11) { if (kwinApp()->platform()->isReady()) { QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { setup(); } else { finish(); } }, Qt::QueuedConnection ); connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, [this] { delete cm_selection; cm_selection = nullptr; } ); } else { // delay the call to setup by one event cycle // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); finish(); deleteUnusedSupportProperties(); delete cm_selection; s_compositor = NULL; } void Compositor::setup() { if (hasScene()) return; if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!CompositingPrefs::compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } m_starting = true; if (!options->isCompositingInitialized()) { options->reloadCompositingSettings(true); slotCompositingOptionsInitialized(); } else { slotCompositingOptionsInitialized(); } } extern int screen_number; // main.cpp extern bool is_multihead; void Compositor::slotCompositingOptionsInitialized() { claimCompositorSelection(); // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275) if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } switch(options->compositingMode()) { case OpenGLCompositing: { qCDebug(KWIN_CORE) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: KSharedConfigPtr unsafeConfigPtr = kwinApp()->config(); KConfigGroup unsafeConfig(unsafeConfigPtr, "Compositing"); const QString openGLIsUnsafe = QLatin1String("OpenGLIsUnsafe") + (is_multihead ? QString::number(screen_number) : QString()); if (unsafeConfig.readEntry(openGLIsUnsafe, false)) qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use"; else { unsafeConfig.writeEntry(openGLIsUnsafe, true); unsafeConfig.sync(); if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL && !kwinApp()->shouldUseWaylandForCompositing() && !CompositingPrefs::hasGlx()) { unsafeConfig.writeEntry(openGLIsUnsafe, false); unsafeConfig.sync(); qCDebug(KWIN_CORE) << "No glx extensions available"; break; } m_scene = SceneOpenGL::createScene(this); // TODO: Add 30 second delay to protect against screen freezes as well unsafeConfig.writeEntry(openGLIsUnsafe, false); unsafeConfig.sync(); if (m_scene && !m_scene->initFailed()) { connect(static_cast(m_scene), &SceneOpenGL::resetCompositing, this, &Compositor::restart); break; // --> } delete m_scene; m_scene = NULL; } // Do not Fall back to XRender - it causes problems when selfcheck fails during startup, but works later on break; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING case XRenderCompositing: qCDebug(KWIN_CORE) << "Initializing XRender compositing"; m_scene = SceneXrender::createScene(this); break; #endif case QPainterCompositing: qCDebug(KWIN_CORE) << "Initializing QPainter compositing"; m_scene = SceneQPainter::createScene(this); break; default: qCDebug(KWIN_CORE) << "No compositing enabled"; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } if (m_scene == NULL || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; delete m_scene; m_scene = NULL; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &Compositor::startupWithWorkspace); } } void Compositor::claimCompositorSelection() { if (!cm_selection && kwinApp()->x11Connection()) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); cm_selection = new CompositorSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish())); } if (!cm_selection) // no X11 yet return; if (!cm_selection->owning) { cm_selection->claim(true); // force claiming cm_selection->owning = true; } } void Compositor::startupWithWorkspace() { if (!m_starting) { return; } Q_ASSERT(m_scene); claimCompositorSelection(); m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = milliToNano(1000) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults. m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur scheduleRepaint(); xcb_composite_redirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); new EffectsHandlerImpl(this, m_scene); // sets also the 'effects' pointer connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::windowDeleted); connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull())); addRepaintFull(); foreach (Client * c, Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } foreach (Client * c, Workspace::self()->desktopList()) c->setupCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } emit compositingToggled(true); m_starting = false; if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // render at least once performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::finish() { if (!hasScene()) return; m_finishing = true; m_releaseSelectionTimer.start(); if (Workspace::self()) { foreach (Client * c, Workspace::self()->clientList()) m_scene->windowClosed(c, NULL); foreach (Client * c, Workspace::self()->desktopList()) m_scene->windowClosed(c, NULL); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) m_scene->windowClosed(c, NULL); foreach (Deleted * c, Workspace::self()->deletedList()) m_scene->windowDeleted(c); foreach (Client * c, Workspace::self()->clientList()) c->finishCompositing(); foreach (Client * c, Workspace::self()->desktopList()) c->finishCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) c->finishCompositing(); foreach (Deleted * c, Workspace::self()->deletedList()) c->finishCompositing(); xcb_composite_unredirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } delete effects; effects = NULL; delete m_scene; m_scene = NULL; compositeTimer.stop(); repaints_region = QRegion(); if (Workspace::self()) { for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo i(connection(), (*it)->frameId(), rootWindow(), 0, 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } // discard all Deleted windows (#152914) while (!Workspace::self()->deletedList().isEmpty()) Workspace::self()->deletedList().first()->discard(); } m_finishing = false; emit compositingToggled(false); } void Compositor::releaseCompositorSelection() { if (hasScene() && !m_finishing) { // compositor is up and running again, no need to release the selection return; } if (m_starting) { // currently still starting the compositor, it might fail, so restart the timer to test again m_releaseSelectionTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow, so restart the timer to test again m_releaseSelectionTimer.start(); return; } qCDebug(KWIN_CORE) << "Releasing compositor selection"; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_starting) { // currently still starting the compositor m_unusedSupportPropertyTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow m_unusedSupportPropertyTimer.start(); return; } if (kwinApp()->x11Connection()) { foreach (const xcb_atom_t &atom, m_unusedSupportProperties) { // remove property from root window xcb_delete_property(connection(), rootWindow(), atom); } } } // OpenGL self-check failed, fallback to XRender void Compositor::fallbackToXRenderCompositing() { finish(); KConfigGroup config(kwinApp()->config(), "Compositing"); config.writeEntry("Backend", "XRender"); config.sync(); options->setCompositingMode(XRenderCompositing); setup(); } void Compositor::slotConfigChanged() { if (!m_suspended) { setup(); if (effects) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finish(); } void Compositor::slotReinitialize() { // Reparse config. Config options will be reloaded by setup() kwinApp()->config()->reparseConfiguration(); // Restart compositing finish(); // resume compositing if suspended m_suspended = NoReasonSuspend; options->setCompositingInitialized(false); setup(); if (effects) { // setup() may fail effects->reconfigure(); } } // for the shortcut void Compositor::slotToggleCompositing() { if (kwinApp()->requiresCompositing()) { // we are not allowed to turn on/off compositing return; } if (m_suspended) { // direct user call; clear all bits resume(AllReasonSuspend); } else { // but only set the user one (sufficient to suspend) suspend(UserSuspend); } } void Compositor::updateCompositeBlocking() { updateCompositeBlocking(NULL); } void Compositor::updateCompositeBlocking(Client *c) { if (kwinApp()->requiresCompositing()) { return; } if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain! QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume? bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspend(false); from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } } void Compositor::suspend(Compositor::SuspendReason reason) { if (kwinApp()->requiresCompositing()) { return; } Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & KWin::Compositor::ScriptSuspend) { // when disabled show a shortcut how the user can get back compositing const auto shortcuts = KGlobalAccel::self()->shortcut(workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // display notification only if there is the shortcut const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } finish(); } void Compositor::resume(Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; setup(); // signal "toggled" is eventually emitted from within setup } void Compositor::restart() { if (hasScene()) { finish(); QTimer::singleShot(0, this, SLOT(setup())); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (!hasScene()) return; repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (!hasScene()) return; const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { assert(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { assert(m_bufferSwapPending); m_bufferSwapPending = false; if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { if (m_scene->usesOverlayWindow() && !isOverlayWindowVisible()) return; // nothing is visible anyway // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (waylandServer() && !kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply foreach (Toplevel *win, windows) { if (win->resetAndFetchDamage()) damaged << win; } if (damaged.count() > 0) { m_scene->triggerFence(); xcb_flush(connection()); } // Move elevated windows to the top of the stacking order foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies foreach (Toplevel *win, damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" m_timeSinceStart += m_timeSinceLastVBlank; // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // skip windows that are not yet ready for being painted and if screen is locked skip windows that are // neither lockscreen nor inputmethod windows // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach (Toplevel *t, windows) { if (!t->readyForPainting()) { windows.removeAll(t); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!t->isLockScreen() && !t->isInputMethod()) { windows.removeAll(t); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); m_timeSinceLastVBlank = m_scene->paint(repaints, windows); m_timeSinceStart += m_timeSinceLastVBlank; if (kwinApp()->shouldUseWaylandForCompositing()) { for (Toplevel *win : damaged) { if (auto surface = win->surface()) { surface->frameRendered(m_timeSinceStart); } } } compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint() // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } bool Compositor::windowRepaintsPending() const { foreach (Toplevel * c, Workspace::self()->clientList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->desktopList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->unmanagedList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->deletedList()) if (!c->repaints().isEmpty()) return true; if (auto w = waylandServer()) { const auto &clients = w->clients(); for (auto c : clients) { if (c->isShown(true) && !c->repaints().isEmpty()) { return true; } } const auto &internalClients = w->internalClients(); for (auto c : internalClients) { if (c->isShown(true) && !c->repaints().isEmpty()) { return true; } } } return false; } void Compositor::setCompositeResetTimer(int msecs) { compositeResetTimer.start(msecs); } void Compositor::setCompositeTimer() { if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending return; if (m_starting || !Workspace::self()) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (waylandServer() && !kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // we're at low repaints or spent more time in painting than the user wanted to wait for that frame padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank } else { // -> align to the next maxFps tick padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval); // "remaining time of the first vsync" + "time for the other vsyncs of the frame" } if (padding < options->vBlankTime()) { // we'll likely miss this frame waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ... } }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task } } compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum } bool Compositor::isActive() { return !m_finishing && hasScene(); } void Compositor::checkUnredirect() { checkUnredirect(false); } // force is needed when the list of windows changes (e.g. a window goes away) void Compositor::checkUnredirect(bool force) { if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen()) return; if (force) forceUnredirectCheck = true; if (!unredirectTimer.isActive()) unredirectTimer.start(0); } void Compositor::delayedCheckUnredirect() { if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !(options->isUnredirectFullscreen() || sender() == options)) return; ToplevelList list; bool changed = forceUnredirectCheck; foreach (Client * c, Workspace::self()->clientList()) list.append(c); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) list.append(c); foreach (Toplevel * c, list) { if (c->updateUnredirectedState()) { changed = true; break; } } // no desktops, no Deleted ones if (!changed) return; forceUnredirectCheck = false; // Cut out parts from the overlay window where unredirected windows are, // so that they are actually visible. const QSize &s = screens()->size(); QRegion reg(0, 0, s.width(), s.height()); foreach (Toplevel * c, list) { if (c->unredirected()) reg -= c->geometry(); } m_scene->overlayWindow()->setShape(reg); addRepaint(reg); } bool Compositor::checkForOverlayWindow(WId w) const { if (!hasScene()) { // no scene, so it cannot be the overlay window return false; } if (!m_scene->overlayWindow()) { // no overlay window, it cannot be the overlay return false; } // and compare the window ID's return w == m_scene->overlayWindow()->window(); } WId Compositor::overlayWindow() const { if (!hasScene()) { return None; } if (!m_scene->overlayWindow()) { return None; } return m_scene->overlayWindow()->window(); } bool Compositor::isOverlayWindowVisible() const { if (!hasScene()) { return false; } if (!m_scene->overlayWindow()) { return false; } return m_scene->overlayWindow()->isVisible(); } void Compositor::setOverlayWindowVisibility(bool visible) { if (hasScene() && m_scene->overlayWindow()) { m_scene->overlayWindow()->setVisibility(visible); } } /***************************************************** * Workspace ****************************************************/ bool Workspace::compositing() const { return m_compositor && m_compositor->hasScene(); } //**************************************** // Toplevel //**************************************** bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); unredirect = false; Compositor::self()->checkUnredirect(true); Compositor::self()->scene()->windowAdded(this); // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. if (dynamic_cast(this)) addDamageFull(); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; Compositor::self()->checkUnredirect(true); if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (kwinApp()->operationMode() == Application::OperationModeX11 && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (kwinApp()->operationMode() != Application::OperationModeX11) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, 0); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addLayerRepaint(r); } void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { repaints_region = QRegion(); layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; Compositor::self()->addRepaint(r2); } bool Toplevel::updateUnredirectedState() { assert(compositing()); bool should = options->isUnredirectFullscreen() && shouldUnredirect() && !unredirectSuspend && !shape() && !hasAlpha() && opacity() == 1.0 && !static_cast(effects)->activeFullScreenEffect(); if (should == unredirect) return false; static QElapsedTimer lastUnredirect; static const qint64 msecRedirectInterval = 100; if (!lastUnredirect.hasExpired(msecRedirectInterval)) { QTimer::singleShot(msecRedirectInterval, Compositor::self(), SLOT(checkUnredirect())); return false; } lastUnredirect.start(); unredirect = should; if (unredirect) { qCDebug(KWIN_CORE) << "Unredirecting:" << this; xcb_composite_unredirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); } else { qCDebug(KWIN_CORE) << "Redirecting:" << this; xcb_composite_redirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); discardWindowPixmap(); } return true; } void Toplevel::suspendUnredirect(bool suspend) { if (unredirectSuspend == suspend) return; unredirectSuspend = suspend; Compositor::self()->checkUnredirect(); } //**************************************** // Client //**************************************** bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } if (isDecorated()) { decoratedClient()->destroyRenderer(); } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); if (!deleting) { if (isDecorated()) { decoratedClient()->destroyRenderer(); } } // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } bool Client::shouldUnredirect() const { if (isActiveFullScreen()) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Unmanaged //**************************************** bool Unmanaged::shouldUnredirect() const { // the pixmap is needed for the login effect, a nicer solution would be the login effect increasing // refcount for the window pixmap (which would prevent unredirect), avoiding this hack if (resourceClass() == "ksplashx" || resourceClass() == "ksplashsimple" || resourceClass() == "ksplashqml" ) return false; // it must cover whole display or one xinerama screen, and be the topmost there const int desktop = VirtualDesktopManager::self()->current(); if (geometry() == workspace()->clientArea(FullArea, geometry().center(), desktop) || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), desktop)) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Deleted //**************************************** bool Deleted::shouldUnredirect() const { return false; } } // namespace diff --git a/effects.cpp b/effects.cpp index 7b968d721..2bade17c7 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1,2055 +1,2055 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2010, 2011 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 "effects.h" #include "effectsadaptor.h" #include "effectloader.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "deleted.h" #include "client.h" #include "cursor.h" #include "group.h" #include "pointer_input.h" #include "scene_xrender.h" #include "scene_qpainter.h" #include "unmanaged.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "scripting/scriptedeffect.h" #include "screens.h" #include "thumbnailitem.h" #include "virtualdesktops.h" #include "workspace.h" #include "kwinglutils.h" #include #include #include #include #include #include #include #include "composite.h" #include "xcbutils.h" -#include "abstract_backend.h" +#include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decorationbridge.h" #include // dbus generated #include "screenlocker_interface.h" namespace KWin { static const QString SCREEN_LOCKER_SERVICE_NAME = QStringLiteral("org.freedesktop.ScreenSaver"); ScreenLockerWatcher::ScreenLockerWatcher(QObject *parent) : QObject(parent) , m_interface(NULL) , m_serviceWatcher(new QDBusServiceWatcher(this)) , m_locked(false) { connect(m_serviceWatcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), SLOT(serviceOwnerChanged(QString,QString,QString))); m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); m_serviceWatcher->addWatchedService(SCREEN_LOCKER_SERVICE_NAME); // check whether service is registered QFutureWatcher > *watcher = new QFutureWatcher >(this); connect(watcher, SIGNAL(finished()), SLOT(serviceRegisteredQueried())); connect(watcher, SIGNAL(canceled()), watcher, SLOT(deleteLater())); watcher->setFuture(QtConcurrent::run(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::isServiceRegistered, SCREEN_LOCKER_SERVICE_NAME)); } ScreenLockerWatcher::~ScreenLockerWatcher() { } void ScreenLockerWatcher::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner) if (serviceName != SCREEN_LOCKER_SERVICE_NAME) { return; } delete m_interface; m_interface = NULL; m_locked = false; if (!newOwner.isEmpty()) { m_interface = new OrgFreedesktopScreenSaverInterface(newOwner, QString(), QDBusConnection::sessionBus(), this); connect(m_interface, SIGNAL(ActiveChanged(bool)), SLOT(setLocked(bool))); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_interface->GetActive(), this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(activeQueried(QDBusPendingCallWatcher*))); } } void ScreenLockerWatcher::serviceRegisteredQueried() { QFutureWatcher > *watcher = dynamic_cast > *>(sender()); if (!watcher) { return; } const QDBusReply &reply = watcher->result(); if (reply.isValid() && reply.value()) { QFutureWatcher > *ownerWatcher = new QFutureWatcher >(this); connect(ownerWatcher, SIGNAL(finished()), SLOT(serviceOwnerQueried())); connect(ownerWatcher, SIGNAL(canceled()), ownerWatcher, SLOT(deleteLater())); ownerWatcher->setFuture(QtConcurrent::run(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwner, SCREEN_LOCKER_SERVICE_NAME)); } watcher->deleteLater(); } void ScreenLockerWatcher::serviceOwnerQueried() { QFutureWatcher > *watcher = dynamic_cast > *>(sender()); if (!watcher) { return; } const QDBusReply reply = watcher->result(); if (reply.isValid()) { serviceOwnerChanged(SCREEN_LOCKER_SERVICE_NAME, QString(), reply.value()); } watcher->deleteLater(); } void ScreenLockerWatcher::activeQueried(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { setLocked(reply.value()); } watcher->deleteLater(); } void ScreenLockerWatcher::setLocked(bool activated) { if (m_locked == activated) { return; } m_locked = activated; emit locked(m_locked); } //--------------------- // Static static QByteArray readWindowProperty(xcb_window_t win, xcb_atom_t atom, xcb_atom_t type, int format) { uint32_t len = 32768; for (;;) { Xcb::Property prop(false, win, atom, XCB_ATOM_ANY, 0, len); if (prop.isNull()) { // get property failed return QByteArray(); } if (prop->bytes_after > 0) { len *= 2; continue; } return prop.toByteArray(format, type); } } static void deleteWindowProperty(Window win, long int atom) { xcb_delete_property(connection(), win, atom); } //--------------------- EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene) : EffectsHandler(scene->compositingType()) , keyboard_grab_effect(NULL) , fullscreen_effect(0) , next_window_quad_type(EFFECT_QUAD_TYPE_START) , m_compositor(compositor) , m_scene(scene) , m_screenLockerWatcher(new ScreenLockerWatcher(this)) , m_desktopRendering(false) , m_currentRenderedDesktop(0) , m_effectLoader(new EffectLoader(this)) , m_trackingCursorChanges(0) { connect(m_effectLoader, &AbstractEffectLoader::effectLoaded, this, [this](Effect *effect, const QString &name) { effect_order.insert(effect->requestedEffectChainPosition(), EffectPair(name, effect)); loaded_effects << EffectPair(name, effect); effectsChanged(); } ); m_effectLoader->setConfig(kwinApp()->config()); new EffectsAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Effects"), this); // init is important, otherwise causes crashes when quads are build before the first painting pass start m_currentBuildQuadsIterator = m_activeEffects.constEnd(); Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(ws, &Workspace::showingDesktopChanged, this, &EffectsHandlerImpl::showingDesktopChanged); connect(ws, &Workspace::currentDesktopChanged, this, [this](int old, AbstractClient *c) { const int newDesktop = VirtualDesktopManager::self()->current(); if (old != 0 && newDesktop != old) { emit desktopChanged(old, newDesktop, c ? c->effectWindow() : 0); // TODO: remove in 4.10 emit desktopChanged(old, newDesktop); } } ); connect(ws, &Workspace::desktopPresenceChanged, this, [this](AbstractClient *c, int old) { if (!c->effectWindow()) { return; } // the visibility update hasn't happed yet, thus the signal is delayed to prevent glitches, see also BUG 347490 QMetaObject::invokeMethod(this, "desktopPresenceChanged", Qt::QueuedConnection, Q_ARG(KWin::EffectWindow*, c->effectWindow()), Q_ARG(int, old), Q_ARG(int, c->desktop())); } ); connect(ws, &Workspace::clientAdded, this, [this](Client *c) { if (c->readyForPainting()) slotClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); } ); connect(ws, &Workspace::unmanagedAdded, this, [this](Unmanaged *u) { // it's never initially ready but has synthetic 50ms delay connect(u, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotUnmanagedShown); } ); connect(ws, &Workspace::clientActivated, this, [this](KWin::AbstractClient *c) { emit windowActivated(c ? c->effectWindow() : nullptr); } ); connect(ws, &Workspace::deletedRemoved, this, [this](KWin::Deleted *d) { emit windowDeleted(d->effectWindow()); elevated_windows.removeAll(d->effectWindow()); } ); connect(vds, &VirtualDesktopManager::countChanged, this, &EffectsHandler::numberDesktopsChanged); connect(Cursor::self(), &Cursor::mouseChanged, this, &EffectsHandler::mouseChanged); connect(ws, &Workspace::propertyNotify, this, [this](long int atom) { if (!registered_atoms.contains(atom)) return; emit propertyNotify(nullptr, atom); } ); connect(screens(), &Screens::countChanged, this, &EffectsHandler::numberScreensChanged); connect(screens(), &Screens::sizeChanged, this, &EffectsHandler::virtualScreenSizeChanged); connect(screens(), &Screens::geometryChanged, this, &EffectsHandler::virtualScreenGeometryChanged); #ifdef KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, &Activities::added, this, &EffectsHandler::activityAdded); connect(activities, &Activities::removed, this, &EffectsHandler::activityRemoved); connect(activities, &Activities::currentChanged, this, &EffectsHandler::currentActivityChanged); } #endif connect(ws, &Workspace::stackingOrderChanged, this, &EffectsHandler::stackingOrderChanged); #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); connect(tabBox, &TabBox::TabBox::tabBoxAdded, this, &EffectsHandler::tabBoxAdded); connect(tabBox, &TabBox::TabBox::tabBoxUpdated, this, &EffectsHandler::tabBoxUpdated); connect(tabBox, &TabBox::TabBox::tabBoxClosed, this, &EffectsHandler::tabBoxClosed); connect(tabBox, &TabBox::TabBox::tabBoxKeyEvent, this, &EffectsHandler::tabBoxKeyEvent); #endif connect(ScreenEdges::self(), &ScreenEdges::approaching, this, &EffectsHandler::screenEdgeApproaching); connect(m_screenLockerWatcher, &ScreenLockerWatcher::locked, this, &EffectsHandler::screenLockingChanged); // connect all clients for (Client *c : ws->clientList()) { setupClientConnections(c); } for (Unmanaged *u : ws->unmanagedList()) { setupUnmanagedConnections(u); } if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this](ShellClient *c) { if (c->readyForPainting()) slotShellClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotShellClientShown); } ); } reconfigure(); } EffectsHandlerImpl::~EffectsHandlerImpl() { unloadAllEffects(); } void EffectsHandlerImpl::unloadAllEffects() { makeOpenGLContextCurrent(); if (keyboard_grab_effect != NULL) ungrabKeyboard(); setActiveFullScreenEffect(nullptr); for (auto it = loaded_effects.begin(); it != loaded_effects.end(); ++it) { Effect *effect = (*it).second; stopMouseInterception(effect); // remove support properties for the effect const QList properties = m_propertiesForEffects.keys(); for (const QByteArray &property : properties) { removeSupportProperty(property, effect); } delete effect; } loaded_effects.clear(); m_effectLoader->clear(); } void EffectsHandlerImpl::setupAbstractClientConnections(AbstractClient* c) { connect(c, &AbstractClient::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(c, static_cast(&AbstractClient::clientMaximizedStateChanged), this, &EffectsHandlerImpl::slotClientMaximized); connect(c, &AbstractClient::clientStartUserMovedResized, this, [this](AbstractClient *c) { emit windowStartUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::clientStepUserMovedResized, this, [this](AbstractClient *c, const QRect &geometry) { emit windowStepUserMovedResized(c->effectWindow(), geometry); } ); connect(c, &AbstractClient::clientFinishUserMovedResized, this, [this](AbstractClient *c) { emit windowFinishUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(c, &AbstractClient::clientMinimized, this, [this](AbstractClient *c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowMinimized(c->effectWindow()); } } ); connect(c, &AbstractClient::clientUnminimized, this, [this](AbstractClient* c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowUnminimized(c->effectWindow()); } } ); connect(c, &AbstractClient::modalChanged, this, &EffectsHandlerImpl::slotClientModalityChanged); connect(c, &AbstractClient::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); } void EffectsHandlerImpl::setupClientConnections(Client* c) { setupAbstractClientConnections(c); connect(c, &Client::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); connect(c, &Client::propertyNotify, this, &EffectsHandlerImpl::slotPropertyNotify); } void EffectsHandlerImpl::setupUnmanagedConnections(Unmanaged* u) { connect(u, &Unmanaged::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(u, &Unmanaged::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(u, &Unmanaged::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(u, &Unmanaged::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); connect(u, &Unmanaged::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(u, &Unmanaged::propertyNotify, this, &EffectsHandlerImpl::slotPropertyNotify); } void EffectsHandlerImpl::reconfigure() { m_effectLoader->queryAndLoadAll(); } // the idea is that effects call this function again which calls the next one void EffectsHandlerImpl::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->prePaintScreen(data, time); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->paintScreen(mask, region, data); --m_currentPaintScreenIterator; } else m_scene->finalPaintScreen(mask, region, data); } void EffectsHandlerImpl::paintDesktop(int desktop, int mask, QRegion region, ScreenPaintData &data) { if (desktop < 1 || desktop > numberOfDesktops()) { return; } m_currentRenderedDesktop = desktop; m_desktopRendering = true; // save the paint screen iterator EffectsIterator savedIterator = m_currentPaintScreenIterator; m_currentPaintScreenIterator = m_activeEffects.constBegin(); effects->paintScreen(mask, region, data); // restore the saved iterator m_currentPaintScreenIterator = savedIterator; m_desktopRendering = false; } void EffectsHandlerImpl::postPaintScreen() { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->postPaintScreen(); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->prePaintWindow(w, data, time); --m_currentPaintWindowIterator; } // no special final code } void EffectsHandlerImpl::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->paintWindow(w, mask, region, data); --m_currentPaintWindowIterator; } else m_scene->finalPaintWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { if (m_currentPaintEffectFrameIterator != m_activeEffects.constEnd()) { (*m_currentPaintEffectFrameIterator++)->paintEffectFrame(frame, region, opacity, frameOpacity); --m_currentPaintEffectFrameIterator; } else { const EffectFrameImpl* frameImpl = static_cast(frame); frameImpl->finalRender(region, opacity, frameOpacity); } } void EffectsHandlerImpl::postPaintWindow(EffectWindow* w) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->postPaintWindow(w); --m_currentPaintWindowIterator; } // no special final code } Effect *EffectsHandlerImpl::provides(Effect::Feature ef) { for (int i = 0; i < loaded_effects.size(); ++i) if (loaded_effects.at(i).second->provides(ef)) return loaded_effects.at(i).second; return NULL; } void EffectsHandlerImpl::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentDrawWindowIterator != m_activeEffects.constEnd()) { (*m_currentDrawWindowIterator++)->drawWindow(w, mask, region, data); --m_currentDrawWindowIterator; } else m_scene->finalDrawWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::buildQuads(EffectWindow* w, WindowQuadList& quadList) { static bool initIterator = true; if (initIterator) { m_currentBuildQuadsIterator = m_activeEffects.constBegin(); initIterator = false; } if (m_currentBuildQuadsIterator != m_activeEffects.constEnd()) { (*m_currentBuildQuadsIterator++)->buildQuads(w, quadList); --m_currentBuildQuadsIterator; } if (m_currentBuildQuadsIterator == m_activeEffects.constBegin()) initIterator = true; } bool EffectsHandlerImpl::hasDecorationShadows() const { return false; } bool EffectsHandlerImpl::decorationsHaveAlpha() const { return true; } bool EffectsHandlerImpl::decorationSupportsBlurBehind() const { return Decoration::DecorationBridge::self()->needsBlur(); } // start another painting pass void EffectsHandlerImpl::startPaint() { m_activeEffects.clear(); m_activeEffects.reserve(loaded_effects.count()); for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->isActive()) { m_activeEffects << it->second; } } m_currentDrawWindowIterator = m_activeEffects.constBegin(); m_currentPaintWindowIterator = m_activeEffects.constBegin(); m_currentPaintScreenIterator = m_activeEffects.constBegin(); m_currentPaintEffectFrameIterator = m_activeEffects.constBegin(); } void EffectsHandlerImpl::slotClientMaximized(KWin::AbstractClient *c, MaximizeMode maxMode) { bool horizontal = false; bool vertical = false; switch (maxMode) { case MaximizeHorizontal: horizontal = true; break; case MaximizeVertical: vertical = true; break; case MaximizeFull: horizontal = true; vertical = true; break; case MaximizeRestore: // fall through default: // default - nothing to do break; } if (EffectWindowImpl *w = c->effectWindow()) { emit windowMaximizedStateChanged(w, horizontal, vertical); } } void EffectsHandlerImpl::slotOpacityChanged(Toplevel *t, qreal oldOpacity) { if (t->opacity() == oldOpacity || !t->effectWindow()) { return; } emit windowOpacityChanged(t->effectWindow(), oldOpacity, (qreal)t->opacity()); } void EffectsHandlerImpl::slotClientShown(KWin::Toplevel *t) { Q_ASSERT(dynamic_cast(t)); Client *c = static_cast(t); setupClientConnections(c); if (!c->tabGroup()) // the "window" has already been there emit windowAdded(c->effectWindow()); } void EffectsHandlerImpl::slotShellClientShown(Toplevel *t) { ShellClient *c = static_cast(t); setupAbstractClientConnections(c); emit windowAdded(t->effectWindow()); } void EffectsHandlerImpl::slotUnmanagedShown(KWin::Toplevel *t) { // regardless, unmanaged windows are -yet?- not synced anyway Q_ASSERT(dynamic_cast(t)); Unmanaged *u = static_cast(t); setupUnmanagedConnections(u); emit windowAdded(u->effectWindow()); } void EffectsHandlerImpl::slotWindowClosed(KWin::Toplevel *c, KWin::Deleted *d) { c->disconnect(this); if (d) { emit windowClosed(c->effectWindow()); } } void EffectsHandlerImpl::slotClientModalityChanged() { emit windowModalityChanged(static_cast(sender())->effectWindow()); } void EffectsHandlerImpl::slotCurrentTabAboutToChange(EffectWindow *from, EffectWindow *to) { emit currentTabAboutToChange(from, to); } void EffectsHandlerImpl::slotTabAdded(EffectWindow* w, EffectWindow* to) { emit tabAdded(w, to); } void EffectsHandlerImpl::slotTabRemoved(EffectWindow *w, EffectWindow* leaderOfFormerGroup) { emit tabRemoved(w, leaderOfFormerGroup); } void EffectsHandlerImpl::slotWindowDamaged(Toplevel* t, const QRect& r) { if (!t->effectWindow()) { // can happen during tear down of window return; } emit windowDamaged(t->effectWindow(), r); } void EffectsHandlerImpl::slotGeometryShapeChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == NULL || t->effectWindow() == NULL) return; emit windowGeometryShapeChanged(t->effectWindow(), old); } void EffectsHandlerImpl::slotPaddingChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == NULL || t->effectWindow() == NULL) return; emit windowPaddingChanged(t->effectWindow(), old); } void EffectsHandlerImpl::setActiveFullScreenEffect(Effect* e) { fullscreen_effect = e; m_compositor->checkUnredirect(); } Effect* EffectsHandlerImpl::activeFullScreenEffect() const { return fullscreen_effect; } bool EffectsHandlerImpl::grabKeyboard(Effect* effect) { if (keyboard_grab_effect != NULL) return false; if (kwinApp()->operationMode() == Application::OperationModeX11) { bool ret = grabXKeyboard(); if (!ret) return false; } keyboard_grab_effect = effect; return true; } void EffectsHandlerImpl::ungrabKeyboard() { assert(keyboard_grab_effect != NULL); if (kwinApp()->operationMode() == Application::OperationModeX11) { ungrabXKeyboard(); } keyboard_grab_effect = NULL; } void EffectsHandlerImpl::grabbedKeyboardEvent(QKeyEvent* e) { if (keyboard_grab_effect != NULL) keyboard_grab_effect->grabbedKeyboardEvent(e); } void EffectsHandlerImpl::startMouseInterception(Effect *effect, Qt::CursorShape shape) { if (m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.append(effect); if (m_grabbedMouseEffects.size() != 1) { return; } if (kwinApp()->operationMode() != Application::OperationModeX11) { input()->pointer()->setEffectsOverrideCursor(shape); return; } // NOTE: it is intended to not perform an XPointerGrab on X11. See documentation in kwineffects.h // The mouse grab is implemented by using a full screen input only window if (!m_mouseInterceptionWindow.isValid()) { const QSize &s = screens()->size(); const QRect geo(0, 0, s.width(), s.height()); const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = { true, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_mouseInterceptionWindow.reset(Xcb::createInputWindow(geo, mask, values)); defineCursor(shape); } else { defineCursor(shape); } m_mouseInterceptionWindow.map(); m_mouseInterceptionWindow.raise(); // Raise electric border windows above the input windows // so they can still be triggered. ScreenEdges::self()->ensureOnTop(); } void EffectsHandlerImpl::stopMouseInterception(Effect *effect) { if (!m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.removeAll(effect); if (kwinApp()->operationMode() != Application::OperationModeX11) { input()->pointer()->removeEffectsOverrideCursor(); return; } if (m_grabbedMouseEffects.isEmpty()) { m_mouseInterceptionWindow.unmap(); Workspace::self()->stackScreenEdgesUnderOverrideRedirect(); } } bool EffectsHandlerImpl::isMouseInterception() const { return m_grabbedMouseEffects.count() > 0; } void EffectsHandlerImpl::registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) { input()->registerShortcut(shortcut, action); } void EffectsHandlerImpl::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { input()->registerPointerShortcut(modifiers, pointerButtons, action); } void EffectsHandlerImpl::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { input()->registerAxisShortcut(modifiers, axis, action); } void* EffectsHandlerImpl::getProxy(QString name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) return (*it).second->proxy(); return NULL; } void EffectsHandlerImpl::startMousePolling() { if (Cursor::self()) Cursor::self()->startMousePolling(); } void EffectsHandlerImpl::stopMousePolling() { if (Cursor::self()) Cursor::self()->stopMousePolling(); } bool EffectsHandlerImpl::hasKeyboardGrab() const { return keyboard_grab_effect != NULL; } void EffectsHandlerImpl::desktopResized(const QSize &size) { m_scene->screenGeometryChanged(size); if (m_mouseInterceptionWindow.isValid()) { m_mouseInterceptionWindow.setGeometry(QRect(0, 0, size.width(), size.height())); } emit screenGeometryChanged(size); } void EffectsHandlerImpl::slotPropertyNotify(Toplevel* t, long int atom) { if (!registered_atoms.contains(atom)) return; emit propertyNotify(t->effectWindow(), atom); } void EffectsHandlerImpl::registerPropertyType(long atom, bool reg) { if (reg) ++registered_atoms[ atom ]; // initialized to 0 if not present yet else { if (--registered_atoms[ atom ] == 0) registered_atoms.remove(atom); } } xcb_atom_t EffectsHandlerImpl::announceSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it != m_propertiesForEffects.end()) { // property has already been registered for an effect // just append Effect and return the atom stored in m_managedProperties if (!it.value().contains(effect)) { it.value().append(effect); } return m_managedProperties.value(propertyName); } // get the atom for the propertyName ScopedCPointer atomReply(xcb_intern_atom_reply(connection(), xcb_intern_atom_unchecked(connection(), false, propertyName.size(), propertyName.constData()), NULL)); if (atomReply.isNull()) { return XCB_ATOM_NONE; } m_compositor->keepSupportProperty(atomReply->atom); // announce property on root window unsigned char dummy = 0; xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, rootWindow(), atomReply->atom, atomReply->atom, 8, 1, &dummy); // TODO: add to _NET_SUPPORTED m_managedProperties.insert(propertyName, atomReply->atom); m_propertiesForEffects.insert(propertyName, QList() << effect); registerPropertyType(atomReply->atom, true); return atomReply->atom; } void EffectsHandlerImpl::removeSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it == m_propertiesForEffects.end()) { // property is not registered - nothing to do return; } if (!it.value().contains(effect)) { // property is not registered for given effect - nothing to do return; } it.value().removeAll(effect); if (!it.value().isEmpty()) { // property still registered for another effect - nothing further to do return; } const xcb_atom_t atom = m_managedProperties.take(propertyName); registerPropertyType(atom, false); m_propertiesForEffects.remove(propertyName); m_compositor->removeSupportProperty(atom); // delayed removal } QByteArray EffectsHandlerImpl::readRootProperty(long atom, long type, int format) const { return readWindowProperty(rootWindow(), atom, type, format); } void EffectsHandlerImpl::deleteRootProperty(long atom) const { deleteWindowProperty(rootWindow(), atom); } void EffectsHandlerImpl::activateWindow(EffectWindow* c) { if (AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(c)->window())) Workspace::self()->activateClient(cl, true); } EffectWindow* EffectsHandlerImpl::activeWindow() const { return Workspace::self()->activeClient() ? Workspace::self()->activeClient()->effectWindow() : NULL; } void EffectsHandlerImpl::moveWindow(EffectWindow* w, const QPoint& pos, bool snap, double snapAdjust) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (!cl || !cl->isMovable()) return; if (snap) cl->move(Workspace::self()->adjustClientPosition(cl, pos, true, snapAdjust)); else cl->move(pos); } void EffectsHandlerImpl::windowToDesktop(EffectWindow* w, int desktop) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) Workspace::self()->sendClientToDesktop(cl, desktop, true); } void EffectsHandlerImpl::windowToScreen(EffectWindow* w, int screen) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) Workspace::self()->sendClientToScreen(cl, screen); } void EffectsHandlerImpl::setShowingDesktop(bool showing) { Workspace::self()->setShowingDesktop(showing); } QString EffectsHandlerImpl::currentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return QString(); } return Activities::self()->current(); #else return QString(); #endif } int EffectsHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } int EffectsHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } void EffectsHandlerImpl::setCurrentDesktop(int desktop) { VirtualDesktopManager::self()->setCurrent(desktop); } void EffectsHandlerImpl::setNumberOfDesktops(int desktops) { VirtualDesktopManager::self()->setCount(desktops); } QSize EffectsHandlerImpl::desktopGridSize() const { return VirtualDesktopManager::self()->grid().size(); } int EffectsHandlerImpl::desktopGridWidth() const { return desktopGridSize().width(); } int EffectsHandlerImpl::desktopGridHeight() const { return desktopGridSize().height(); } int EffectsHandlerImpl::workspaceWidth() const { return desktopGridWidth() * displayWidth(); } int EffectsHandlerImpl::workspaceHeight() const { return desktopGridHeight() * displayHeight(); } int EffectsHandlerImpl::desktopAtCoords(QPoint coords) const { return VirtualDesktopManager::self()->grid().at(coords); } QPoint EffectsHandlerImpl::desktopGridCoords(int id) const { return VirtualDesktopManager::self()->grid().gridCoords(id); } QPoint EffectsHandlerImpl::desktopCoords(int id) const { QPoint coords = VirtualDesktopManager::self()->grid().gridCoords(id); if (coords.x() == -1) return QPoint(-1, -1); return QPoint(coords.x() * displayWidth(), coords.y() * displayHeight()); } int EffectsHandlerImpl::desktopAbove(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToRight(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopBelow(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToLeft(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } QString EffectsHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } bool EffectsHandlerImpl::optionRollOverDesktops() const { return options->isRollOverDesktops(); } double EffectsHandlerImpl::animationTimeFactor() const { return options->animationTimeFactor(); } WindowQuadType EffectsHandlerImpl::newWindowQuadType() { return WindowQuadType(next_window_quad_type++); } EffectWindow* EffectsHandlerImpl::findWindow(WId id) const { if (Client* w = Workspace::self()->findClient(Predicate::WindowMatch, id)) return w->effectWindow(); if (Unmanaged* w = Workspace::self()->findUnmanaged(id)) return w->effectWindow(); if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(id)) { return w->effectWindow(); } } return NULL; } EffectWindow* EffectsHandlerImpl::findWindow(KWayland::Server::SurfaceInterface *surf) const { if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(surf)) { return w->effectWindow(); } } return nullptr; } EffectWindowList EffectsHandlerImpl::stackingOrder() const { ToplevelList list = Workspace::self()->xStackingOrder(); EffectWindowList ret; for (Toplevel *t : list) { if (EffectWindow *w = effectWindow(t)) ret.append(w); } return ret; } void EffectsHandlerImpl::setElevatedWindow(KWin::EffectWindow* w, bool set) { elevated_windows.removeAll(w); if (set) elevated_windows.append(w); } void EffectsHandlerImpl::setTabBoxWindow(EffectWindow* w) { #ifdef KWIN_BUILD_TABBOX if (AbstractClient* c = dynamic_cast< AbstractClient* >(static_cast< EffectWindowImpl* >(w)->window())) { TabBox::TabBox::self()->setCurrentClient(c); } #else Q_UNUSED(w) #endif } void EffectsHandlerImpl::setTabBoxDesktop(int desktop) { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->setCurrentDesktop(desktop); #else Q_UNUSED(desktop) #endif } EffectWindowList EffectsHandlerImpl::currentTabBoxWindowList() const { #ifdef KWIN_BUILD_TABBOX EffectWindowList ret; const auto clients = TabBox::TabBox::self()->currentClientList(); for (auto c : clients) ret.append(c->effectWindow()); return ret; #else return EffectWindowList(); #endif } void EffectsHandlerImpl::refTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->reference(); #endif } void EffectsHandlerImpl::unrefTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->unreference(); #endif } void EffectsHandlerImpl::closeTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->close(); #endif } QList< int > EffectsHandlerImpl::currentTabBoxDesktopList() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktopList(); #endif return QList< int >(); } int EffectsHandlerImpl::currentTabBoxDesktop() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktop(); #endif return -1; } EffectWindow* EffectsHandlerImpl::currentTabBoxWindow() const { #ifdef KWIN_BUILD_TABBOX if (auto c = TabBox::TabBox::self()->currentClient()) return c->effectWindow(); #endif return NULL; } void EffectsHandlerImpl::addRepaintFull() { m_compositor->addRepaintFull(); } void EffectsHandlerImpl::addRepaint(const QRect& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(const QRegion& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(int x, int y, int w, int h) { m_compositor->addRepaint(x, y, w, h); } int EffectsHandlerImpl::activeScreen() const { return screens()->current(); } int EffectsHandlerImpl::numScreens() const { return screens()->count(); } int EffectsHandlerImpl::screenNumber(const QPoint& pos) const { return screens()->number(pos); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, int screen, int desktop) const { return Workspace::self()->clientArea(opt, screen, desktop); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const EffectWindow* c) const { const Toplevel* t = static_cast< const EffectWindowImpl* >(c)->window(); if (const AbstractClient* cl = dynamic_cast< const AbstractClient* >(t)) return Workspace::self()->clientArea(opt, cl); else return Workspace::self()->clientArea(opt, t->geometry().center(), VirtualDesktopManager::self()->current()); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return Workspace::self()->clientArea(opt, p, desktop); } QRect EffectsHandlerImpl::virtualScreenGeometry() const { return screens()->geometry(); } QSize EffectsHandlerImpl::virtualScreenSize() const { return screens()->size(); } void EffectsHandlerImpl::defineCursor(Qt::CursorShape shape) { if (!m_mouseInterceptionWindow.isValid()) { input()->pointer()->setEffectsOverrideCursor(shape); return; } const xcb_cursor_t c = Cursor::x11Cursor(shape); if (c != XCB_CURSOR_NONE) { m_mouseInterceptionWindow.defineCursor(c); } } bool EffectsHandlerImpl::checkInputWindowEvent(xcb_button_press_event_t *e) { if (m_grabbedMouseEffects.isEmpty() || m_mouseInterceptionWindow != e->event) { return false; } for (Effect *effect : m_grabbedMouseEffects) { Qt::MouseButton button = x11ToQtMouseButton(e->detail); Qt::MouseButtons buttons = x11ToQtMouseButtons(e->state); const QEvent::Type type = ((e->response_type & ~0x80) == XCB_BUTTON_PRESS) ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; if (type == QEvent::MouseButtonPress) { buttons |= button; } else { buttons &= ~button; } QMouseEvent ev(type, QPoint(e->event_x, e->event_y), QPoint(e->root_x, e->root_y), button, buttons, x11ToQtKeyboardModifiers(e->state)); effect->windowInputMouseEvent(&ev); } return true; // eat event } bool EffectsHandlerImpl::checkInputWindowEvent(xcb_motion_notify_event_t *e) { if (m_grabbedMouseEffects.isEmpty() || m_mouseInterceptionWindow != e->event) { return false; } for (Effect *effect : m_grabbedMouseEffects) { QMouseEvent ev(QEvent::MouseMove, QPoint(e->event_x, e->event_y), QPoint(e->root_x, e->root_y), Qt::NoButton, x11ToQtMouseButtons(e->state), x11ToQtKeyboardModifiers(e->state)); effect->windowInputMouseEvent(&ev); } return true; // eat event } bool EffectsHandlerImpl::checkInputWindowEvent(QMouseEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } bool EffectsHandlerImpl::checkInputWindowEvent(QWheelEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } void EffectsHandlerImpl::connectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { if (!m_trackingCursorChanges) { connect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); Cursor::self()->startCursorTracking(); } ++m_trackingCursorChanges; } EffectsHandler::connectNotify(signal); } void EffectsHandlerImpl::disconnectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { Q_ASSERT(m_trackingCursorChanges > 0); if (!--m_trackingCursorChanges) { Cursor::self()->stopCursorTracking(); disconnect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); } } EffectsHandler::disconnectNotify(signal); } void EffectsHandlerImpl::checkInputWindowStacking() { if (m_grabbedMouseEffects.isEmpty()) { return; } if (kwinApp()->operationMode() != Application::OperationModeX11) { return; } m_mouseInterceptionWindow.raise(); // Raise electric border windows above the input windows // so they can still be triggered. TODO: Do both at once. ScreenEdges::self()->ensureOnTop(); } QPoint EffectsHandlerImpl::cursorPos() const { return Cursor::pos(); } void EffectsHandlerImpl::reserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->reserve(border, effect, "borderActivated"); } void EffectsHandlerImpl::unreserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->unreserve(border, effect); } unsigned long EffectsHandlerImpl::xrenderBufferPicture() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (SceneXrender* s = dynamic_cast< SceneXrender* >(m_scene)) return s->bufferPicture(); #endif return None; } QPainter *EffectsHandlerImpl::scenePainter() { if (SceneQPainter *s = dynamic_cast(m_scene)) { return s->painter(); } else { return NULL; } } void EffectsHandlerImpl::toggleEffect(const QString& name) { if (isEffectLoaded(name)) unloadEffect(name); else loadEffect(name); } QStringList EffectsHandlerImpl::loadedEffects() const { QStringList listModules; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { listModules << (*it).first; } return listModules; } QStringList EffectsHandlerImpl::listOfEffects() const { return m_effectLoader->listOfKnownEffects(); } bool EffectsHandlerImpl::loadEffect(const QString& name) { makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->loadEffect(name); } void EffectsHandlerImpl::unloadEffect(const QString& name) { makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); for (QMap< int, EffectPair >::iterator it = effect_order.begin(); it != effect_order.end(); ++it) { if (it.value().first == name) { qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Unloading Effect : " << name; if (activeFullScreenEffect() == it.value().second) { setActiveFullScreenEffect(0); } stopMouseInterception(it.value().second); // remove support properties for the effect const QList properties = m_propertiesForEffects.keys(); for (const QByteArray &property : properties) { removeSupportProperty(property, it.value().second); } delete it.value().second; effect_order.erase(it); effectsChanged(); return; } } qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Effect not loaded : " << name; } void EffectsHandlerImpl::reconfigureEffect(const QString& name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) { kwinApp()->config()->reparseConfiguration(); makeOpenGLContextCurrent(); (*it).second->reconfigure(Effect::ReconfigureAll); return; } } bool EffectsHandlerImpl::isEffectLoaded(const QString& name) const { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) return true; return false; } bool EffectsHandlerImpl::isEffectSupported(const QString &name) { // if the effect is loaded, it is obviously supported auto it = std::find_if(loaded_effects.constBegin(), loaded_effects.constEnd(), [name](const EffectPair &pair) { return pair.first == name; }); if (it != loaded_effects.constEnd()) { return true; } // next checks might require a context makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->isEffectSupported(name); } QList< bool > EffectsHandlerImpl::areEffectsSupported(const QStringList &names) { QList< bool > retList; for (const QString &name : names) { retList << isEffectSupported(name); } return retList; } void EffectsHandlerImpl::reloadEffect(Effect *effect) { QString effectName; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).second == effect) { effectName = (*it).first; break; } } if (!effectName.isNull()) { unloadEffect(effectName); m_effectLoader->loadEffect(effectName); } } void EffectsHandlerImpl::effectsChanged() { loaded_effects.clear(); m_activeEffects.clear(); // it's possible to have a reconfigure and a quad rebuild between two paint cycles - bug #308201 // qDebug() << "Recreating effects' list:"; for (const EffectPair & effect : effect_order) { // qDebug() << effect.first; loaded_effects.append(effect); } m_activeEffects.reserve(loaded_effects.count()); } QStringList EffectsHandlerImpl::activeEffects() const { QStringList ret; for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(), end = loaded_effects.constEnd(); it != end; ++it) { if (it->second->isActive()) { ret << it->first; } } return ret; } KWayland::Server::Display *EffectsHandlerImpl::waylandDisplay() const { if (waylandServer()) { return waylandServer()->display(); } return nullptr; } EffectFrame* EffectsHandlerImpl::effectFrame(EffectFrameStyle style, bool staticSize, const QPoint& position, Qt::Alignment alignment) const { return new EffectFrameImpl(style, staticSize, position, alignment); } QVariant EffectsHandlerImpl::kwinOption(KWinOption kwopt) { switch (kwopt) { case CloseButtonCorner: // TODO: this could become per window and be derived from the actual position in the deco return Decoration::DecorationBridge::self()->settings()->decorationButtonsLeft().contains(KDecoration2::DecorationButtonType::Close) ? Qt::TopLeftCorner : Qt::TopRightCorner; case SwitchDesktopOnScreenEdge: return ScreenEdges::self()->isDesktopSwitching(); case SwitchDesktopOnScreenEdgeMovingWindows: return ScreenEdges::self()->isDesktopSwitchingMovingClients(); default: return QVariant(); // an invalid one } } QString EffectsHandlerImpl::supportInformation(const QString &name) const { if (!isEffectLoaded(name)) { return QString(); } for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).first == name) { QString support((*it).first + QLatin1String(":\n")); const QMetaObject *metaOptions = (*it).second->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (qstrcmp(property.name(), "objectName") == 0) { continue; } support += QString::fromUtf8(property.name()) + QLatin1String(": ") + (*it).second->property(property.name()).toString() + QLatin1Char('\n'); } return support; } } return QString(); } bool EffectsHandlerImpl::isScreenLocked() const { return m_screenLockerWatcher->isLocked(); } QString EffectsHandlerImpl::debug(const QString& name, const QString& parameter) const { QString internalName = name.toLower();; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).first == internalName) { return it->second->debug(parameter); } } return QString(); } bool EffectsHandlerImpl::makeOpenGLContextCurrent() { return m_scene->makeOpenGLContextCurrent(); } void EffectsHandlerImpl::doneOpenGLContextCurrent() { m_scene->doneOpenGLContextCurrent(); } //**************************************** // EffectWindowImpl //**************************************** EffectWindowImpl::EffectWindowImpl(Toplevel *toplevel) : EffectWindow(toplevel) , toplevel(toplevel) , sw(NULL) { } EffectWindowImpl::~EffectWindowImpl() { QVariant cachedTextureVariant = data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; } } bool EffectWindowImpl::isPaintingEnabled() { return sceneWindow()->isPaintingEnabled(); } void EffectWindowImpl::enablePainting(int reason) { sceneWindow()->enablePainting(reason); } void EffectWindowImpl::disablePainting(int reason) { sceneWindow()->disablePainting(reason); } const EffectWindowGroup* EffectWindowImpl::group() const { if (Client* c = dynamic_cast< Client* >(toplevel)) return c->group()->effectGroup(); return NULL; // TODO } void EffectWindowImpl::refWindow() { if (Deleted* d = dynamic_cast< Deleted* >(toplevel)) return d->refWindow(); abort(); // TODO } void EffectWindowImpl::unrefWindow() { if (Deleted* d = dynamic_cast< Deleted* >(toplevel)) return d->unrefWindow(); // delays deletion in case abort(); // TODO } void EffectWindowImpl::setWindow(Toplevel* w) { toplevel = w; setParent(w); } void EffectWindowImpl::setSceneWindow(Scene::Window* w) { sw = w; } QRegion EffectWindowImpl::shape() const { return sw ? sw->shape() : geometry(); } QRect EffectWindowImpl::decorationInnerRect() const { Client *client = dynamic_cast(toplevel); return client ? client->transparentRect() : contentsRect(); } QByteArray EffectWindowImpl::readProperty(long atom, long type, int format) const { return readWindowProperty(window()->window(), atom, type, format); } void EffectWindowImpl::deleteProperty(long int atom) const { deleteWindowProperty(window()->window(), atom); } EffectWindow* EffectWindowImpl::findModal() { if (AbstractClient* c = dynamic_cast< AbstractClient* >(toplevel)) { if (AbstractClient* c2 = c->findModal()) return c2->effectWindow(); } return NULL; } template EffectWindowList getMainWindows(Toplevel *toplevel) { T *c = static_cast(toplevel); EffectWindowList ret; const auto mainclients = c->mainClients(); for (auto tmp : mainclients) ret.append(tmp->effectWindow()); return ret; } EffectWindowList EffectWindowImpl::mainWindows() const { if (dynamic_cast(toplevel)) { return getMainWindows(toplevel); } else if (toplevel->isDeleted()) { return getMainWindows(toplevel); } return EffectWindowList(); } WindowQuadList EffectWindowImpl::buildQuads(bool force) const { return sceneWindow()->buildQuads(force); } void EffectWindowImpl::setData(int role, const QVariant &data) { if (!data.isNull()) dataMap[ role ] = data; else dataMap.remove(role); } QVariant EffectWindowImpl::data(int role) const { if (!dataMap.contains(role)) return QVariant(); return dataMap[ role ]; } EffectWindow* effectWindow(Toplevel* w) { EffectWindowImpl* ret = w->effectWindow(); return ret; } EffectWindow* effectWindow(Scene::Window* w) { EffectWindowImpl* ret = w->window()->effectWindow(); ret->setSceneWindow(w); return ret; } void EffectWindowImpl::elevate(bool elevate) { effects->setElevatedWindow(this, elevate); } void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item) { if (WindowThumbnailItem *thumb = qobject_cast(item)) { insertThumbnail(thumb); connect(thumb, SIGNAL(destroyed(QObject*)), SLOT(thumbnailDestroyed(QObject*))); connect(thumb, SIGNAL(wIdChanged(qulonglong)), SLOT(thumbnailTargetChanged())); } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { m_desktopThumbnails.append(desktopThumb); connect(desktopThumb, SIGNAL(destroyed(QObject*)), SLOT(desktopThumbnailDestroyed(QObject*))); } } void EffectWindowImpl::thumbnailDestroyed(QObject *object) { // we know it is a ThumbnailItem m_thumbnails.remove(static_cast(object)); } void EffectWindowImpl::thumbnailTargetChanged() { if (WindowThumbnailItem *item = qobject_cast(sender())) { insertThumbnail(item); } } void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item) { EffectWindow *w = effects->findWindow(item->wId()); if (w) { m_thumbnails.insert(item, QWeakPointer(static_cast(w))); } else { m_thumbnails.insert(item, QWeakPointer()); } } void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) { // we know it is a DesktopThumbnailItem m_desktopThumbnails.removeAll(static_cast(object)); } void EffectWindowImpl::referencePreviousWindowPixmap() { if (sw) { sw->referencePreviousPixmap(); } } void EffectWindowImpl::unreferencePreviousWindowPixmap() { if (sw) { sw->unreferencePreviousPixmap(); } } //**************************************** // EffectWindowGroupImpl //**************************************** EffectWindowList EffectWindowGroupImpl::members() const { EffectWindowList ret; for (Toplevel * c : group->members()) ret.append(c->effectWindow()); return ret; } //**************************************** // EffectFrameImpl //**************************************** EffectFrameImpl::EffectFrameImpl(EffectFrameStyle style, bool staticSize, QPoint position, Qt::Alignment alignment) : QObject(0) , EffectFrame() , m_style(style) , m_static(staticSize) , m_point(position) , m_alignment(alignment) , m_shader(NULL) , m_theme(new Plasma::Theme(this)) { if (m_style == EffectFrameStyled) { m_frame.setImagePath(QStringLiteral("widgets/background")); m_frame.setCacheAllRenderedFrames(true); connect(m_theme, SIGNAL(themeChanged()), this, SLOT(plasmaThemeChanged())); } m_selection.setImagePath(QStringLiteral("widgets/viewitem")); m_selection.setElementPrefix(QStringLiteral("hover")); m_selection.setCacheAllRenderedFrames(true); m_selection.setEnabledBorders(Plasma::FrameSvg::AllBorders); m_sceneFrame = Compositor::self()->scene()->createEffectFrame(this); } EffectFrameImpl::~EffectFrameImpl() { delete m_sceneFrame; } const QFont& EffectFrameImpl::font() const { return m_font; } void EffectFrameImpl::setFont(const QFont& font) { if (m_font == font) { return; } m_font = font; QRect oldGeom = m_geometry; if (!m_text.isEmpty()) { autoResize(); } if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::free() { m_sceneFrame->free(); } const QRect& EffectFrameImpl::geometry() const { return m_geometry; } void EffectFrameImpl::setGeometry(const QRect& geometry, bool force) { QRect oldGeom = m_geometry; m_geometry = geometry; if (m_geometry == oldGeom && !force) { return; } effects->addRepaint(oldGeom); effects->addRepaint(m_geometry); if (m_geometry.size() == oldGeom.size() && !force) { return; } if (m_style == EffectFrameStyled) { qreal left, top, right, bottom; m_frame.getMargins(left, top, right, bottom); // m_geometry is the inner geometry m_frame.resizeFrame(m_geometry.adjusted(-left, -top, right, bottom).size()); } free(); } const QIcon& EffectFrameImpl::icon() const { return m_icon; } void EffectFrameImpl::setIcon(const QIcon& icon) { m_icon = icon; if (isCrossFade()) { m_sceneFrame->crossFadeIcon(); } if (m_iconSize.isEmpty() && !m_icon.availableSizes().isEmpty()) { // Set a size if we don't already have one setIconSize(m_icon.availableSizes().first()); } m_sceneFrame->freeIconFrame(); } const QSize& EffectFrameImpl::iconSize() const { return m_iconSize; } void EffectFrameImpl::setIconSize(const QSize& size) { if (m_iconSize == size) { return; } m_iconSize = size; autoResize(); m_sceneFrame->freeIconFrame(); } void EffectFrameImpl::plasmaThemeChanged() { free(); } void EffectFrameImpl::render(QRegion region, double opacity, double frameOpacity) { if (m_geometry.isEmpty()) { return; // Nothing to display } m_shader = NULL; setScreenProjectionMatrix(static_cast(effects)->scene()->screenProjectionMatrix()); effects->paintEffectFrame(this, region, opacity, frameOpacity); } void EffectFrameImpl::finalRender(QRegion region, double opacity, double frameOpacity) const { region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL m_sceneFrame->render(region, opacity, frameOpacity); } Qt::Alignment EffectFrameImpl::alignment() const { return m_alignment; } void EffectFrameImpl::align(QRect &geometry) { if (m_alignment & Qt::AlignLeft) geometry.moveLeft(m_point.x()); else if (m_alignment & Qt::AlignRight) geometry.moveLeft(m_point.x() - geometry.width()); else geometry.moveLeft(m_point.x() - geometry.width() / 2); if (m_alignment & Qt::AlignTop) geometry.moveTop(m_point.y()); else if (m_alignment & Qt::AlignBottom) geometry.moveTop(m_point.y() - geometry.height()); else geometry.moveTop(m_point.y() - geometry.height() / 2); } void EffectFrameImpl::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; align(m_geometry); setGeometry(m_geometry); } void EffectFrameImpl::setPosition(const QPoint& point) { m_point = point; QRect geometry = m_geometry; // this is important, setGeometry need call repaint for old & new geometry align(geometry); setGeometry(geometry); } const QString& EffectFrameImpl::text() const { return m_text; } void EffectFrameImpl::setText(const QString& text) { if (m_text == text) { return; } if (isCrossFade()) { m_sceneFrame->crossFadeText(); } m_text = text; QRect oldGeom = m_geometry; autoResize(); if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::setSelection(const QRect& selection) { if (selection == m_selectionGeometry) { return; } m_selectionGeometry = selection; if (m_selectionGeometry.size() != m_selection.frameSize().toSize()) { m_selection.resizeFrame(m_selectionGeometry.size()); } // TODO; optimize to only recreate when resizing m_sceneFrame->freeSelection(); } void EffectFrameImpl::autoResize() { if (m_static) return; // Not automatically resizing QRect geometry; // Set size if (!m_text.isEmpty()) { QFontMetrics metrics(m_font); geometry.setSize(metrics.size(0, m_text)); } if (!m_icon.isNull() && !m_iconSize.isEmpty()) { geometry.setLeft(-m_iconSize.width()); if (m_iconSize.height() > geometry.height()) geometry.setHeight(m_iconSize.height()); } align(geometry); setGeometry(geometry); } QColor EffectFrameImpl::styledTextColor() { return m_theme->color(Plasma::Theme::TextColor); } } // namespace diff --git a/input.cpp b/input.cpp index 4c7657b09..4eccbf826 100644 --- a/input.cpp +++ b/input.cpp @@ -1,1196 +1,1196 @@ /******************************************************************** 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 "input.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "client.h" #include "effects.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "virtual_terminal.h" #endif -#include "abstract_backend.h" +#include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { VirtualTerminal::self()->activate(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->getMovingClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } }; class GlobalShortcutFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton); if (event->type() == QEvent::MouseButtonPress) { if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) { return true; } } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->modifiers() == Qt::NoModifier) { return false; } PointerAxisDirection direction = PointerAxisUp; if (event->angleDelta().x() < 0) { direction = PointerAxisRight; } else if (event->angleDelta().x() > 0) { direction = PointerAxisLeft; } else if (event->angleDelta().y() < 0) { direction = PointerAxisDown; } else if (event->angleDelta().y() > 0) { direction = PointerAxisUp; } return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { return input()->shortcuts()->processKey(event->modifiers(), event->nativeVirtualKey()); } return false; } }; class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } event->setAccepted(false); return QCoreApplication::sendEvent(found, event); } }; class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if (orientation == Qt::Vertical && decoration->decoration()->titleBar().contains(localPos.toPoint())) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } if (event->type() == QEvent::KeyPress) TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; switch (event->button()) { case Qt::LeftButton: command = options->commandAll1(); break; case Qt::MiddleButton: command = options->commandAll2(); break; case Qt::RightButton: command = options->commandAll3(); break; default: // nothing break; } } else { command = c->getMouseCommand(event->button(), &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } else { command = c->getWheelCommand(Qt::Vertical, &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } seat->setPointerPos(event->globalPos()); break; case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); if (event->buttons() == Qt::NoButton) { input()->pointer()->update(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(quint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (Toplevel *t = input()->findToplevel(event->globalPos())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->raiseClient(c); } seat->setPointerPos(event->globalPos()); seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } seat->setPointerPos(event->globalPos()); break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } }; KWIN_SINGLETON_FACTORY(InputRedirection) InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #if HAVE_INPUT if (Application::usesLibinput()) { if (VirtualTerminal::self()) { setupLibInput(); } else { connect(kwinApp(), &Application::virtualTerminalCreated, this, &InputRedirection::setupLibInput); } } #endif connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { m_filters << filter; } void InputRedirection::prepandInputEventFilter(InputEventFilter *filter) { m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeAll(filter); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { #if HAVE_INPUT if (VirtualTerminal::self()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); } installInputEventFilter(new ScreenEdgeInputFilter); installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { #if HAVE_INPUT if (Application::usesLibinput()) { const auto config = KSharedConfig::openConfig(QStringLiteral("kcminputrc"))->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { conn->setup(); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (QPointF delta, uint32_t time) { m_pointer->processMotion(m_pointer->pos() + delta, time); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time) { Q_UNUSED(orig) m_pointer->processMotion(screen, time); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { s->setHasKeyboard(conn->hasKeyboard()); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasKeyboardChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasKeyboard(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(VirtualTerminal::self(), &VirtualTerminal::activeChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } #endif } void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->geometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { m_shortcuts->registerShortcut(action, shortcut); registerShortcutForGlobalAccelTimestamp(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } } // namespace diff --git a/main.cpp b/main.cpp index 409015ac2..716717bdb 100644 --- a/main.cpp +++ b/main.cpp @@ -1,556 +1,556 @@ /******************************************************************** 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 . *********************************************************************/ #include "main.h" #include // kwin -#include "abstract_backend.h" +#include "platform.h" #include "atoms.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "logind.h" #include "options.h" #include "screens.h" #include "sm.h" #include "workspace.h" #include "xcbutils.h" // KDE #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #ifdef HAVE_MALLOC_H #include #endif // HAVE_MALLOC_H // xcb #include #ifndef XCB_GE_GENERIC #define XCB_GE_GENERIC 35 #endif namespace KWin { Options* options; Atoms* atoms; int screen_number = -1; bool is_multihead = false; 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; }; int Application::crashes = 0; bool Application::isX11MultiHead() { return is_multihead; } void Application::setX11MultiHead(bool multiHead) { is_multihead = multiHead; } void Application::setX11ScreenNumber(int screenNumber) { screen_number = screenNumber; } int Application::x11ScreenNumber() { return screen_number; } Application::Application(Application::OperationMode mode, int &argc, char **argv) : QApplication(argc, argv) , m_eventFilter(new XcbEventFilter()) , m_configLock(false) , m_config() , m_operationMode(mode) { qRegisterMetaType("Options::WindowOperation"); } void Application::setConfigLock(bool lock) { m_configLock = lock; } Application::OperationMode Application::operationMode() const { return m_operationMode; } void Application::setOperationMode(OperationMode mode) { m_operationMode = mode; } bool Application::shouldUseWaylandForCompositing() const { return m_operationMode == OperationModeWaylandAndX11 || m_operationMode == OperationModeXwayland; } bool Application::requiresCompositing() const { return shouldUseWaylandForCompositing(); } void Application::start() { setQuitOnLastWindowClosed(false); if (!m_config) { m_config = KSharedConfig::openConfig(); } if (!m_config->isImmutable() && m_configLock) { // TODO: This shouldn't be necessary //config->setReadOnly( true ); m_config->reparseConfiguration(); } crashChecking(); performStartup(); } Application::~Application() { delete options; destroyAtoms(); } void Application::destroyAtoms() { delete atoms; atoms = nullptr; } void Application::setupCrashHandler() { KCrash::setEmergencySaveFunction(Application::crashHandler); } void Application::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 Application::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); } void Application::resetCrashesCount() { crashes = 0; } void Application::setCrashCount(int count) { crashes = count; } bool Application::wasCrash() { return crashes > 0; } static const char description[] = I18N_NOOP("KDE window manager"); void Application::createAboutData() { KAboutData aboutData(QStringLiteral(KWIN_NAME), // The program name used internally i18n("KWin"), // A displayable program name string QStringLiteral(KWIN_VERSION_STRING), // The program version string i18n(description), // Short description of what the app does KAboutLicense::GPL, // The license this code is released under i18n("(c) 1999-2013, The KDE Developers")); // Copyright Statement aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org")); aboutData.addAuthor(i18n("Cristian Tibirna"), QString(), QStringLiteral("tibirna@kde.org")); aboutData.addAuthor(i18n("Daniel M. Duley"), QString(), QStringLiteral("mosfet@kde.org")); aboutData.addAuthor(i18n("Luboš Luňák"), QString(), QStringLiteral("l.lunak@kde.org")); aboutData.addAuthor(i18n("Martin Gräßlin"), i18n("Maintainer"), QStringLiteral("mgraesslin@kde.org")); KAboutData::setApplicationData(aboutData); } static const QString s_lockOption = QStringLiteral("lock"); static const QString s_crashesOption = QStringLiteral("crashes"); void Application::setupCommandLine(QCommandLineParser *parser) { QCommandLineOption lockOption(s_lockOption, i18n("Disable configuration options")); QCommandLineOption crashesOption(s_crashesOption, i18n("Indicate that KWin has recently crashed n times"), QStringLiteral("n")); parser->setApplicationDescription(i18n("KDE window manager")); parser->addVersionOption(); parser->addHelpOption(); parser->addOption(lockOption); parser->addOption(crashesOption); KAboutData::applicationData().setupCommandLine(parser); } void Application::processCommandLine(QCommandLineParser *parser) { setConfigLock(parser->isSet(s_lockOption)); Application::setCrashCount(parser->value(s_crashesOption).toInt()); } void Application::setupTranslator() { QTranslator *qtTranslator = new QTranslator(qApp); qtTranslator->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); installTranslator(qtTranslator); } void Application::setupMalloc() { #ifdef M_TRIM_THRESHOLD // Prevent fragmentation of the heap by malloc (glibc). // // The default threshold is 128*1024, which can result in a large memory usage // due to fragmentation especially if we use the raster graphicssystem. On the // otherside if the threshold is too low, free() starts to permanently ask the kernel // about shrinking the heap. #ifdef HAVE_UNISTD_H const int pagesize = sysconf(_SC_PAGESIZE); #else const int pagesize = 4*1024; #endif // HAVE_UNISTD_H mallopt(M_TRIM_THRESHOLD, 5*pagesize); #endif // M_TRIM_THRESHOLD } void Application::setupLocalizedString() { KLocalizedString::setApplicationDomain("kwin"); } void Application::notifyKSplash() { // Tell KSplash that KWin has started QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("wm")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } void Application::createWorkspace() { // ensure the helper atoms are retrieved before we create the Workspace atoms->retrieveHelpers(); // we want all QQuickWindows with an alpha buffer, do here as Workspace might create QQuickWindows QQuickWindow::setDefaultAlphaBuffer(true); // This tries to detect compositing options and can use GLX. GLX problems // (X errors) shouldn't cause kwin to abort, so this is out of the // critical startup section where x errors cause kwin to abort. // create workspace. (void) new Workspace(m_originalSessionKey); emit workspaceCreated(); } void Application::createInput() { LogindIntegration::create(this); auto input = InputRedirection::create(this); input->init(); Cursor::create(this); } void Application::createScreens() { if (Screens::self()) { return; } Screens::create(this); emit screensCreated(); } void Application::createAtoms() { atoms = new Atoms; } void Application::createOptions() { options = new Options; } void Application::createCompositor() { Compositor::create(this); } void Application::setupEventFilters() { installNativeEventFilter(m_eventFilter.data()); } void Application::destroyWorkspace() { delete Workspace::self(); } void Application::destroyCompositor() { delete Compositor::self(); } void Application::updateX11Time(xcb_generic_event_t *event) { xcb_timestamp_t time = XCB_TIME_CURRENT_TIME; const uint8_t eventType = event->response_type & ~0x80; switch(eventType) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: time = reinterpret_cast(event)->time; break; case XCB_MOTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_ENTER_NOTIFY: case XCB_LEAVE_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_FOCUS_IN: case XCB_FOCUS_OUT: case XCB_KEYMAP_NOTIFY: case XCB_EXPOSE: case XCB_GRAPHICS_EXPOSURE: case XCB_NO_EXPOSURE: case XCB_VISIBILITY_NOTIFY: case XCB_CREATE_NOTIFY: case XCB_DESTROY_NOTIFY: case XCB_UNMAP_NOTIFY: case XCB_MAP_NOTIFY: case XCB_MAP_REQUEST: case XCB_REPARENT_NOTIFY: case XCB_CONFIGURE_NOTIFY: case XCB_CONFIGURE_REQUEST: case XCB_GRAVITY_NOTIFY: case XCB_RESIZE_REQUEST: case XCB_CIRCULATE_NOTIFY: case XCB_CIRCULATE_REQUEST: // no timestamp return; case XCB_PROPERTY_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_CLEAR: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_REQUEST: time = reinterpret_cast(event)->time; break; case XCB_SELECTION_NOTIFY: time = reinterpret_cast(event)->time; break; case XCB_COLORMAP_NOTIFY: case XCB_CLIENT_MESSAGE: case XCB_MAPPING_NOTIFY: case XCB_GE_GENERIC: // no timestamp return; default: // extension handling if (Xcb::Extensions::self()) { if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) { time = reinterpret_cast(event)->server_time; } if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) { time = reinterpret_cast(event)->timestamp; } } break; } setX11Time(time); } bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result) if (eventType != "xcb_generic_event_t") { return false; } auto event = static_cast(message); kwinApp()->updateX11Time(event); if (!Workspace::self()) { // Workspace not yet created return false; } return Workspace::self()->workspaceEvent(event); } static bool s_useLibinput = false; void Application::setUseLibinput(bool use) { s_useLibinput = use; } bool Application::usesLibinput() { return s_useLibinput; } QProcessEnvironment Application::processStartupEnvironment() const { return QProcessEnvironment::systemEnvironment(); } void Application::initPlatform(const KPluginMetaData &plugin) { Q_ASSERT(!m_platform); m_platform = qobject_cast(plugin.instantiate()); if (m_platform) { m_platform->setParent(this); #if HAVE_INPUT // check whether it needs libinput const QJsonObject &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("input")); if (it != metaData.end()) { if ((*it).isBool()) { if (!(*it).toBool()) { qCDebug(KWIN_CORE) << "Platform does not support input, enforcing libinput support"; setUseLibinput(true); } } } #endif } } } // namespace diff --git a/main_wayland.cpp b/main_wayland.cpp index 2f3d85130..f15d30ee3 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,717 +1,717 @@ /******************************************************************** 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 . *********************************************************************/ #include "main_wayland.h" #include "workspace.h" #include // kwin -#include "abstract_backend.h" +#include "platform.h" #include "effects.h" #include "wayland_server.h" #include "xcbutils.h" // KWayland #include #include // KDE #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include // system #ifdef HAVE_UNISTD_H #include #endif // HAVE_UNISTD_H #if HAVE_SYS_PRCTL_H #include #endif #include #include namespace KWin { static void sighandler(int) { QApplication::exit(); } static void readDisplay(int pipe); //************************************ // ApplicationWayland //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) : Application(OperationModeWaylandAndX11, argc, argv) { } ApplicationWayland::~ApplicationWayland() { if (!waylandServer()) { return; } kwinApp()->platform()->setOutputsEnabled(false); destroyWorkspace(); waylandServer()->dispatch(); // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } disconnect(m_xwaylandFailConnection); if (x11Connection()) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); destroyAtoms(); emit x11ConnectionAboutToBeDestroyed(); xcb_disconnect(x11Connection()); setX11Connection(nullptr); } if (m_xwaylandProcess) { m_xwaylandProcess->terminate(); while (m_xwaylandProcess->state() != QProcess::NotRunning) { processEvents(QEventLoop::WaitForMoreEvents); } waylandServer()->destroyXWaylandConnection(); } waylandServer()->terminateClientConnections(); destroyCompositor(); } void ApplicationWayland::performStartup() { setOperationMode(m_startXWayland ? OperationModeXwayland : OperationModeWaylandAndX11); // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); createBackend(); } void ApplicationWayland::setupCrashHandler() { // this disables auto-restart of kwin_wayland // do nothing hence allowing OS to create dump and so on } void ApplicationWayland::createBackend() { connect(platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; ::exit(1); } ); platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); waylandServer()->initOutputs(); if (!m_startXWayland) { continueStartupWithX(); return; } createCompositor(); startXwaylandServer(); } void ApplicationWayland::continueStartupWithX() { createX11Connection(); xcb_connection_t *c = x11Connection(); if (!c) { // about to quit return; } QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); auto processXcbEvents = [this, c] { while (auto event = xcb_poll_for_event(c)) { updateX11Time(event); long result = 0; if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { free(event); continue; } if (Workspace::self()) { Workspace::self()->workspaceEvent(event); } free(event); } xcb_flush(c); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); // create selection owner for WM_S0 - magic X display number expected by XWayland KSelectionOwner owner("WM_S0", c, x11RootWindow()); owner.claim(true); createAtoms(); setupEventFilters(); // 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_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr); ::exit(1); } if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { QProcessEnvironment environment = m_environment; environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.remove("DISPLAY"); environment.remove("WAYLAND_DISPLAY"); QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, [this, p] { if (waylandServer()) { waylandServer()->destroyInputMethodConnection(); } p->deleteLater(); } ); p->setProcessEnvironment(environment); p->start(m_inputMethodServerToStart); p->waitForStarted(); } } m_environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY"))); // start session if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(m_environment); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, &ApplicationWayland::quit); p->start(m_sessionArgument); } // start the applications passed to us as command line arguments if (!m_applicationsToStart.isEmpty()) { for (const QString &application: m_applicationsToStart) { // note: this will kill the started process when we exit // this is going to happen anyway as we are the wayland and X server the app connects to QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(m_environment); p->start(application); } } createWorkspace(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); } void ApplicationWayland::createX11Connection() { int screenNumber = 0; xcb_connection_t *c = nullptr; if (m_xcbConnectionFd == -1) { c = xcb_connect(nullptr, &screenNumber); } else { c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr); } if (int error = xcb_connection_has_error(c)) { std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl; exit(1); return; } setX11Connection(c); // we don't support X11 multi-head in Wayland setX11ScreenNumber(screenNumber); setX11RootWindow(defaultScreen()->root); } void ApplicationWayland::startXwaylandServer() { int pipeFds[2]; if (pipe(pipeFds) != 0) { std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl; exit(1); return; } int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(1); return; } int fd = dup(sx[1]); if (fd < 0) { std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl; exit(20); return; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(1); return; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl; exit(20); return; } m_xcbConnectionFd = sx[0]; m_xwaylandProcess = new Process(kwinApp()); m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = m_environment; env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"), QString::number(pipeFds[1]), QStringLiteral("-rootless"), QStringLiteral("-wm"), QString::number(fd)}); m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast(&QProcess::error), this, [] (QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl; } else { std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl; } exit(1); } ); const int xDisplayPipe = pipeFds[0]; connect(m_xwaylandProcess, &QProcess::started, this, [this, xDisplayPipe] { QFutureWatcher *watcher = new QFutureWatcher(this); QObject::connect(watcher, &QFutureWatcher::finished, this, &ApplicationWayland::continueStartupWithX, Qt::QueuedConnection); QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater, Qt::QueuedConnection); watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe)); } ); m_xwaylandProcess->start(); close(pipeFds[1]); } static void readDisplay(int pipe) { QFile readPipe; if (!readPipe.open(pipe, QIODevice::ReadOnly)) { std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl; exit(1); } QByteArray displayNumber = readPipe.readLine(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); std::cout << "X-Server started on display " << displayNumber.constData() << std::endl; setenv("DISPLAY", displayNumber.constData(), true); // close our pipe close(pipe); } static const QString s_waylandPlugin = QStringLiteral("KWinWaylandWaylandBackend"); static const QString s_x11Plugin = QStringLiteral("KWinWaylandX11Backend"); static const QString s_fbdevPlugin = QStringLiteral("KWinWaylandFbdevBackend"); #if HAVE_DRM static const QString s_drmPlugin = QStringLiteral("KWinWaylandDrmBackend"); #endif #if HAVE_LIBHYBRIS static const QString s_hwcomposerPlugin = QStringLiteral("KWinWaylandHwcomposerBackend"); #endif static const QString s_virtualPlugin = QStringLiteral("KWinWaylandVirtualBackend"); static QString automaticBackendSelection() { if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { return s_waylandPlugin; } if (qEnvironmentVariableIsSet("DISPLAY")) { return s_x11Plugin; } #if HAVE_LIBHYBRIS if (qEnvironmentVariableIsSet("ANDROID_ROOT")) { return s_hwcomposerPlugin; } #endif #if HAVE_DRM return s_drmPlugin; #endif return s_fbdevPlugin; } static void disablePtrace() { #if HAVE_PR_SET_DUMPABLE // check whether we are running under a debugger const QFileInfo parent(QStringLiteral("/proc/%1/exe").arg(getppid())); if (parent.isSymLink() && parent.symLinkTarget().endsWith(QLatin1String("/gdb"))) { // debugger, don't adjust return; } // disable ptrace in kwin_wayland prctl(PR_SET_DUMPABLE, 0); #endif } } // namespace int main(int argc, char * argv[]) { KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); 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); // ensure that no thread takes SIGUSR sigset_t userSignals; sigemptyset(&userSignals); sigaddset(&userSignals, SIGUSR1); sigaddset(&userSignals, SIGUSR2); pthread_sigmask(SIG_BLOCK, &userSignals, nullptr); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); // enforce our internal qpa plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); qunsetenv("QT_IM_MODULE"); qputenv("QSG_RENDER_LOOP", "basic"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); #endif KWin::ApplicationWayland a(argc, argv); a.setupTranslator(); // reset QT_QPA_PLATFORM to a sane value for any processes started from KWin setenv("QT_QPA_PLATFORM", "wayland", true); KWin::Application::createAboutData(); const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.waylandbackends")); auto hasPlugin = [&availablePlugins] (const QString &name) { return std::any_of(availablePlugins.begin(), availablePlugins.end(), [name] (const KPluginMetaData &plugin) { return plugin.pluginId() == name; } ); }; const bool hasWindowedOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_waylandPlugin); const bool hasSizeOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_virtualPlugin); const bool hasOutputCountOption = hasPlugin(KWin::s_x11Plugin); const bool hasX11Option = hasPlugin(KWin::s_x11Plugin); const bool hasVirtualOption = hasPlugin(KWin::s_virtualPlugin); const bool hasWaylandOption = hasPlugin(KWin::s_waylandPlugin); const bool hasFramebufferOption = hasPlugin(KWin::s_fbdevPlugin); #if HAVE_DRM const bool hasDrmOption = hasPlugin(KWin::s_drmPlugin); #endif #if HAVE_LIBHYBRIS const bool hasHwcomposerOption = hasPlugin(KWin::s_hwcomposerPlugin); #endif QCommandLineOption xwaylandOption(QStringLiteral("xwayland"), i18n("Start a rootless Xwayland server.")); QCommandLineOption waylandSocketOption(QStringList{QStringLiteral("s"), QStringLiteral("socket")}, i18n("Name of the Wayland socket to listen on. If not set \"wayland-0\" is used."), QStringLiteral("socket")); QCommandLineOption windowedOption(QStringLiteral("windowed"), i18n("Use a nested compositor in windowed mode.")); QCommandLineOption framebufferOption(QStringLiteral("framebuffer"), i18n("Render to framebuffer.")); QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), i18n("The framebuffer device to render to."), QStringLiteral("fbdev")); framebufferDeviceOption.setDefaultValue(QStringLiteral("/dev/fb0")); QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"), i18n("The X11 Display to use in windowed mode on platform X11."), QStringLiteral("display")); QCommandLineOption waylandDisplayOption(QStringLiteral("wayland-display"), i18n("The Wayland Display to use in windowed mode on platform Wayland."), QStringLiteral("display")); QCommandLineOption virtualFbOption(QStringLiteral("virtual"), i18n("Render to a virtual framebuffer.")); QCommandLineOption widthOption(QStringLiteral("width"), i18n("The width for windowed mode. Default width is 1024."), QStringLiteral("width")); widthOption.setDefaultValue(QString::number(1024)); QCommandLineOption heightOption(QStringLiteral("height"), i18n("The height for windowed mode. Default height is 768."), QStringLiteral("height")); heightOption.setDefaultValue(QString::number(768)); QCommandLineOption outputCountOption(QStringLiteral("output-count"), i18n("The number of windows to open as outputs in windowed mode. Default value is 1"), QStringLiteral("height")); outputCountOption.setDefaultValue(QString::number(1)); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(xwaylandOption); parser.addOption(waylandSocketOption); if (hasWindowedOption) { parser.addOption(windowedOption); } if (hasX11Option) { parser.addOption(x11DisplayOption); } if (hasWaylandOption) { parser.addOption(waylandDisplayOption); } if (hasFramebufferOption) { parser.addOption(framebufferOption); parser.addOption(framebufferDeviceOption); } if (hasVirtualOption) { parser.addOption(virtualFbOption); } if (hasSizeOption) { parser.addOption(widthOption); parser.addOption(heightOption); } if (hasOutputCountOption) { parser.addOption(outputCountOption); } #if HAVE_LIBHYBRIS QCommandLineOption hwcomposerOption(QStringLiteral("hwcomposer"), i18n("Use libhybris hwcomposer")); if (hasHwcomposerOption) { parser.addOption(hwcomposerOption); } #endif #if HAVE_INPUT QCommandLineOption libinputOption(QStringLiteral("libinput"), i18n("Enable libinput support for input events processing. Note: never use in a nested session.")); parser.addOption(libinputOption); #endif #if HAVE_DRM QCommandLineOption drmOption(QStringLiteral("drm"), i18n("Render through drm node.")); if (hasDrmOption) { parser.addOption(drmOption); } #endif QCommandLineOption inputMethodOption(QStringLiteral("inputmethod"), i18n("Input method that KWin starts."), QStringLiteral("path/to/imserver")); parser.addOption(inputMethodOption); QCommandLineOption listBackendsOption(QStringLiteral("list-backends"), i18n("List all available backends and quit.")); parser.addOption(listBackendsOption); QCommandLineOption screenLockerOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode.")); parser.addOption(screenLockerOption); QCommandLineOption exitWithSessionOption(QStringLiteral("exit-with-session"), i18n("Exit after the session application, which is started by KWin, closed."), QStringLiteral("/path/to/session")); parser.addOption(exitWithSessionOption); #ifdef KWIN_BUILD_ACTIVITIES QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), i18n("Disable KActivities integration.")); parser.addOption(noActivitiesOption); #endif parser.addPositionalArgument(QStringLiteral("applications"), i18n("Applications to start once Wayland and Xwayland server are started"), QStringLiteral("[/path/to/application...]")); parser.process(a); a.processCommandLine(&parser); #ifdef KWIN_BUILD_ACTIVITIES if (parser.isSet(noActivitiesOption)) { a.setUseKActivities(false); } #endif if (parser.isSet(listBackendsOption)) { for (const auto &plugin: availablePlugins) { std::cout << std::setw(40) << std::left << qPrintable(plugin.name()) << qPrintable(plugin.description()) << std::endl; } return 0; } if (parser.isSet(exitWithSessionOption)) { a.setSessionArgument(parser.value(exitWithSessionOption)); } #if HAVE_INPUT KWin::Application::setUseLibinput(parser.isSet(libinputOption)); #endif QString pluginName; QSize initialWindowSize; QByteArray deviceIdentifier; int outputCount = 1; #if HAVE_DRM if (hasDrmOption && parser.isSet(drmOption)) { pluginName = KWin::s_drmPlugin; } #endif if (hasSizeOption) { bool ok = false; const int width = parser.value(widthOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for width" << std::endl; return 1; } const int height = parser.value(heightOption).toInt(&ok); if (!ok) { std::cerr << "FATAL ERROR incorrect value for height" << std::endl; return 1; } initialWindowSize = QSize(width, height); } if (hasOutputCountOption) { bool ok = false; const int count = parser.value(outputCountOption).toInt(&ok); if (ok) { outputCount = qMax(1, count); } } if (hasWindowedOption && parser.isSet(windowedOption)) { if (hasX11Option && parser.isSet(x11DisplayOption)) { deviceIdentifier = parser.value(x11DisplayOption).toUtf8(); } else if (!(hasWaylandOption && parser.isSet(waylandDisplayOption))) { deviceIdentifier = qgetenv("DISPLAY"); } if (!deviceIdentifier.isEmpty()) { pluginName = KWin::s_x11Plugin; } else if (hasWaylandOption) { if (parser.isSet(waylandDisplayOption)) { deviceIdentifier = parser.value(waylandDisplayOption).toUtf8(); } else { deviceIdentifier = qgetenv("WAYLAND_DISPLAY"); } if (!deviceIdentifier.isEmpty()) { pluginName = KWin::s_waylandPlugin; } } } if (hasFramebufferOption && parser.isSet(framebufferOption)) { pluginName = KWin::s_fbdevPlugin; deviceIdentifier = parser.value(framebufferDeviceOption).toUtf8(); } #if HAVE_LIBHYBRIS if (hasHwcomposerOption && parser.isSet(hwcomposerOption)) { pluginName = KWin::s_hwcomposerPlugin; } #endif if (hasVirtualOption && parser.isSet(virtualFbOption)) { pluginName = KWin::s_virtualPlugin; } if (pluginName.isEmpty()) { std::cerr << "No backend specified through command line argument, trying auto resolution" << std::endl; pluginName = KWin::automaticBackendSelection(); } auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [&pluginName] (const KPluginMetaData &plugin) { return plugin.pluginId() == pluginName; } ); if (pluginIt == availablePlugins.end()) { std::cerr << "FATAL ERROR: could not find a backend" << std::endl; return 1; } // TODO: create backend without having the server running KWin::WaylandServer *server = KWin::WaylandServer::create(&a); KWin::WaylandServer::InitalizationFlags flags; if (parser.isSet(screenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::LockScreen; } server->init(parser.value(waylandSocketOption).toUtf8(), flags); a.initPlatform(*pluginIt); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate a backend" << std::endl; return 1; } if (!deviceIdentifier.isEmpty()) { a.platform()->setDeviceIdentifier(deviceIdentifier); } if (initialWindowSize.isValid()) { a.platform()->setInitialWindowSize(initialWindowSize); } a.platform()->setInitialOutputCount(outputCount); QObject::connect(&a, &KWin::Application::workspaceCreated, server, &KWin::WaylandServer::initWorkspace); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), server->display()->socketName()); a.setProcessStartupEnvironment(environment); a.setStartXwayland(parser.isSet(xwaylandOption)); a.setApplicationsToStart(parser.positionalArguments()); a.setInputMethodServerToStart(parser.value(inputMethodOption)); a.start(); return a.exec(); } diff --git a/abstract_backend.cpp b/platform.cpp similarity index 99% rename from abstract_backend.cpp rename to platform.cpp index b9f37db88..0cd51301f 100644 --- a/abstract_backend.cpp +++ b/platform.cpp @@ -1,278 +1,278 @@ /******************************************************************** 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 "abstract_backend.h" +#include "platform.h" #include #include "abstract_egl_backend.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "pointer_input.h" #include "scene_opengl.h" #include "wayland_server.h" namespace KWin { Platform::Platform(QObject *parent) : QObject(parent) { } Platform::~Platform() { } QImage Platform::softwareCursor() const { return input()->pointer()->cursorImage(); } QPoint Platform::softwareCursorHotspot() const { return input()->pointer()->cursorHotSpot(); } Screens *Platform::createScreens(QObject *parent) { Q_UNUSED(parent) return nullptr; } OpenGLBackend *Platform::createOpenGLBackend() { return nullptr; } QPainterBackend *Platform::createQPainterBackend() { return nullptr; } void Platform::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { Q_UNUSED(config) qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; } void Platform::setSoftWareCursor(bool set) { if (m_softWareCursor == set) { return; } m_softWareCursor = set; if (m_softWareCursor) { connect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); } else { disconnect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); } } void Platform::triggerCursorRepaint() { if (!Compositor::self()) { return; } const QPoint &hotSpot = softwareCursorHotspot(); const QSize &size = softwareCursor().size(); Compositor::self()->addRepaint(m_cursor.lastRenderedPosition.x() - hotSpot.x(), m_cursor.lastRenderedPosition.y() - hotSpot.y(), size.width(), size.height()); } void Platform::markCursorAsRendered() { if (m_softWareCursor) { m_cursor.lastRenderedPosition = Cursor::pos(); } if (input()->pointer()) { input()->pointer()->markCursorAsRendered(); } } void Platform::keyboardKeyPressed(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyPressed, time); } void Platform::keyboardKeyReleased(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyReleased, time); } void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!input()) { return; } input()->processKeyboardModifiers(modsDepressed, modsLatched, modsLocked, group); } void Platform::keymapChange(int fd, uint32_t size) { if (!input()) { return; } input()->processKeymapChange(fd, size); } void Platform::pointerAxisHorizontal(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, time); } void Platform::pointerAxisVertical(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, time); } void Platform::pointerButtonPressed(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonPressed, time); } void Platform::pointerButtonReleased(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonReleased, time); } void Platform::pointerMotion(const QPointF &position, quint32 time) { if (!input()) { return; } input()->processPointerMotion(position, time); } void Platform::touchCancel() { if (!input()) { return; } input()->cancelTouch(); } void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchDown(id, pos, time); } void Platform::touchFrame() { if (!input()) { return; } input()->touchFrame(); } void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchMotion(id, pos, time); } void Platform::touchUp(qint32 id, quint32 time) { if (!input()) { return; } input()->processTouchUp(id, time); } void Platform::repaint(const QRect &rect) { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(rect); } void Platform::setReady(bool ready) { if (m_ready == ready) { return; } m_ready = ready; emit readyChanged(m_ready); } void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) } bool Platform::supportsQpaContext() const { return hasGLExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } EGLDisplay Platform::sceneEglDisplay() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->eglDisplay(); } } return EGL_NO_DISPLAY; } EGLContext Platform::sceneEglContext() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->context(); } } return EGL_NO_CONTEXT; } QSize Platform::screenSize() const { return QSize(); } QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } } diff --git a/abstract_backend.h b/platform.h similarity index 98% rename from abstract_backend.h rename to platform.h index 7a9bfaa59..080101d8c 100644 --- a/abstract_backend.h +++ b/platform.h @@ -1,188 +1,188 @@ /******************************************************************** 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 . *********************************************************************/ -#ifndef KWIN_ABSTRACT_BACKEND_H -#define KWIN_ABSTRACT_BACKEND_H +#ifndef KWIN_PLATFORM_H +#define KWIN_PLATFORM_H #include #include #include #include #include namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { class OpenGLBackend; class QPainterBackend; class Screens; class WaylandCursorTheme; class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: virtual ~Platform(); virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. **/ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. **/ virtual EGLDisplay sceneEglDisplay() const; /** * The EGLContext used by the compositing scene. **/ virtual EGLContext sceneEglContext() const; /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. **/ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. **/ virtual QVector screenGeometries() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. */ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); bool handlesOutputs() const { return m_handlesOutputs; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); void pointerAxisHorizontal(qreal delta, quint32 time); void pointerAxisVertical(qreal delta, quint32 time); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. **/ void screenSizeChanged(); protected: explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void handleOutputs() { m_handlesOutputs = true; } void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QPoint lastRenderedPosition; } m_cursor; bool m_handlesOutputs = false; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/qpa/integration.cpp b/plugins/qpa/integration.cpp index 6aef09e0f..569b9c77b 100644 --- a/plugins/qpa/integration.cpp +++ b/plugins/qpa/integration.cpp @@ -1,265 +1,265 @@ /******************************************************************** 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 . *********************************************************************/ #define WL_EGL_PLATFORM 1 #include "integration.h" -#include "abstract_backend.h" +#include "platform.h" #include "backingstore.h" #include "nativeinterface.h" #include "platformcontextwayland.h" #include "screen.h" #include "sharingplatformcontext.h" #include "window.h" #include "../../main.h" #include "../../wayland_server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { namespace QPA { Integration::Integration() : QObject() , QPlatformIntegration() , m_fontDb(new QGenericUnixFontDatabase()) , m_nativeInterface(new NativeInterface(this)) { } Integration::~Integration() = default; bool Integration::hasCapability(Capability cap) const { switch (cap) { case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: return false; case BufferQueueingOpenGL: return false; case MultipleWindows: case NonFullScreenWindows: return true; case RasterGLSurface: return false; default: return QPlatformIntegration::hasCapability(cap); } } void Integration::initialize() { // TODO: start initialize Wayland once the internal Wayland connection is created connect(kwinApp(), &Application::screensCreated, this, &Integration::initializeWayland, Qt::QueuedConnection); QPlatformIntegration::initialize(); m_dummyScreen = new Screen(nullptr); screenAdded(m_dummyScreen); } QAbstractEventDispatcher *Integration::createEventDispatcher() const { return new QUnixEventDispatcherQPA; } QPlatformBackingStore *Integration::createPlatformBackingStore(QWindow *window) const { auto registry = waylandServer()->internalClientRegistry(); const auto shm = registry->interface(KWayland::Client::Registry::Interface::Shm); if (shm.name == 0u) { return nullptr; } return new BackingStore(window, registry->createShmPool(shm.name, shm.version, window)); } QPlatformWindow *Integration::createPlatformWindow(QWindow *window) const { auto c = compositor(); auto s = shell(); if (!s || !c) { return new QPlatformWindow(window); } else { // don't set window as parent, cause infinite recursion in PlasmaQuick::Dialog auto surface = c->createSurface(c); return new Window(window, surface, s->createSurface(surface, surface), this); } } QPlatformFontDatabase *Integration::fontDatabase() const { return m_fontDb; } QPlatformTheme *Integration::createPlatformTheme(const QString &name) const { return QGenericUnixTheme::createUnixTheme(name); } QStringList Integration::themeNames() const { if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { return QStringList({QStringLiteral("kde")}); } return QStringList({QLatin1String(QGenericUnixTheme::name)}); } QPlatformNativeInterface *Integration::nativeInterface() const { return m_nativeInterface; } QPlatformOpenGLContext *Integration::createPlatformOpenGLContext(QOpenGLContext *context) const { if (kwinApp()->platform()->supportsQpaContext()) { return new SharingPlatformContext(context, const_cast(this)); } if (m_eglDisplay == EGL_NO_DISPLAY) { const_cast(this)->initEgl(); } if (m_eglDisplay == EGL_NO_DISPLAY) { return nullptr; } return new PlatformContextWayland(context, const_cast(this)); } void Integration::initializeWayland() { if (m_registry) { return; } using namespace KWayland::Client; auto setupRegistry = [this] { m_registry = waylandServer()->internalClientRegistry(); connect(m_registry, &Registry::outputAnnounced, this, &Integration::createWaylandOutput); const auto outputs = m_registry->interfaces(Registry::Interface::Output); for (const auto &o : outputs) { createWaylandOutput(o.name, o.version); } }; if (waylandServer()->internalClientRegistry()) { setupRegistry(); } else { connect(waylandServer()->internalClientConection(), &ConnectionThread::connected, this, setupRegistry, Qt::QueuedConnection); } } void Integration::createWaylandOutput(quint32 name, quint32 version) { if (m_dummyScreen) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) destroyScreen(m_dummyScreen); #endif m_dummyScreen = nullptr; } using namespace KWayland::Client; auto o = m_registry->createOutput(name, version, this); connect(o, &Output::changed, this, [this, o] { disconnect(o, &Output::changed, nullptr, nullptr); // TODO: handle screen removal screenAdded(new Screen(o)); } ); waylandServer()->internalClientConection()->flush(); } KWayland::Client::Compositor *Integration::compositor() const { if (!m_compositor) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto c = registry->interface(Registry::Interface::Compositor); if (c.name != 0u) { const_cast(this)->m_compositor = registry->createCompositor(c.name, c.version, registry); } } return m_compositor; } KWayland::Client::Shell *Integration::shell() const { if (!m_shell) { using namespace KWayland::Client; auto registry = waylandServer()->internalClientRegistry(); const auto s = registry->interface(Registry::Interface::Shell); if (s.name != 0u) { const_cast(this)->m_shell = registry->createShell(s.name, s.version, registry); } } return m_shell; } EGLDisplay Integration::eglDisplay() const { return m_eglDisplay; } void Integration::initEgl() { Q_ASSERT(m_eglDisplay == EGL_NO_DISPLAY); // This variant uses Wayland as the EGL platform qputenv("EGL_PLATFORM", "wayland"); m_eglDisplay = eglGetDisplay(waylandServer()->internalClientConection()->display()); if (m_eglDisplay == EGL_NO_DISPLAY) { return; } // call eglInitialize in a thread to not block QFuture future = QtConcurrent::run([this] () -> bool { EGLint major, minor; if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE) { return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { return false; } return true; }); // TODO: make this better while (!future.isFinished()) { waylandServer()->internalClientConection()->flush(); QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } if (!future.result()) { eglTerminate(m_eglDisplay); m_eglDisplay = EGL_NO_DISPLAY; } } } } diff --git a/plugins/qpa/sharingplatformcontext.cpp b/plugins/qpa/sharingplatformcontext.cpp index 113852ea0..4c4567b40 100644 --- a/plugins/qpa/sharingplatformcontext.cpp +++ b/plugins/qpa/sharingplatformcontext.cpp @@ -1,92 +1,92 @@ /******************************************************************** 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 "sharingplatformcontext.h" #include "integration.h" #include "window.h" -#include "../../abstract_backend.h" +#include "../../platform.h" #include "../../wayland_server.h" #include "../../shell_client.h" #include namespace KWin { namespace QPA { SharingPlatformContext::SharingPlatformContext(QOpenGLContext *context, Integration *integration) : AbstractPlatformContext(context, integration, kwinApp()->platform()->sceneEglDisplay()) { create(); } bool SharingPlatformContext::makeCurrent(QPlatformSurface *surface) { Window *window = static_cast(surface); if (eglMakeCurrent(eglDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, context())) { window->bindContentFBO(); return true; } return false; } bool SharingPlatformContext::isSharing() const { return false; } void SharingPlatformContext::swapBuffers(QPlatformSurface *surface) { Window *window = static_cast(surface); auto c = window->shellClient(); if (!c) { return; } makeCurrent(surface); glFlush(); c->setInternalFramebufferObject(window->swapFBO()); window->bindContentFBO(); } GLuint SharingPlatformContext::defaultFramebufferObject(QPlatformSurface *surface) const { if (Window *window = dynamic_cast(surface)) { const auto &fbo = window->contentFBO(); if (!fbo.isNull()) { return fbo->handle(); } } return 0; } void SharingPlatformContext::create() { if (config() == 0) { return; } if (!bindApi()) { return; } createContext(kwinApp()->platform()->sceneEglContext()); } } } diff --git a/pointer_input.cpp b/pointer_input.cpp index 8fe569f42..f4aaf8eb6 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,902 +1,902 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 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 "pointer_input.h" -#include "abstract_backend.h" +#include "platform.h" #include "effects.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include // screenlocker #include #include #include // Wayland #include #include namespace KWin { static Qt::MouseButton buttonToQtMouseButton(uint32_t button) { switch (button) { case BTN_LEFT: return Qt::LeftButton; case BTN_MIDDLE: return Qt::MiddleButton; case BTN_RIGHT: return Qt::RightButton; case BTN_SIDE: // in QtWayland mapped like that return Qt::ExtraButton1; case BTN_EXTRA: // in QtWayland mapped like that return Qt::ExtraButton2; case BTN_BACK: return Qt::BackButton; case BTN_FORWARD: return Qt::ForwardButton; case BTN_TASK: return Qt::TaskButton; // mapped like that in QtWayland case 0x118: return Qt::ExtraButton6; case 0x119: return Qt::ExtraButton7; case 0x11a: return Qt::ExtraButton8; case 0x11b: return Qt::ExtraButton9; case 0x11c: return Qt::ExtraButton10; case 0x11d: return Qt::ExtraButton11; case 0x11e: return Qt::ExtraButton12; case 0x11f: return Qt::ExtraButton13; } // all other values get mapped to ExtraButton24 // this is actually incorrect but doesn't matter in our usage // KWin internally doesn't use these high extra buttons anyway // it's only needed for recognizing whether buttons are pressed // if multiple buttons are mapped to the value the evaluation whether // buttons are pressed is correct and that's all we care about. return Qt::ExtraButton24; } static bool screenContainsPos(const QPointF &pos) { for (int i = 0; i < screens()->count(); ++i) { if (screens()->geometry(i).contains(pos.toPoint())) { return true; } } return false; } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : QObject(parent) , m_input(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!m_inited); m_cursor = new CursorImage(this); m_inited = true; connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); m_window.clear(); update(); } ); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time) { if (!m_inited) { return; } updatePosition(pos); QMouseEvent event(QEvent::MouseMove, m_pos.toPoint(), m_pos.toPoint(), Qt::NoButton, m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, 0)) { return; } } } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { if (!m_inited) { return; } updateButton(button, state); QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; break; default: Q_UNREACHABLE(); return; } QMouseEvent event(type, m_pos.toPoint(), m_pos.toPoint(), buttonToQtMouseButton(button), m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, button)) { return; } } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { if (!m_inited) { return; } if (delta == 0) { return; } emit m_input->pointerAxisChanged(axis, delta); QWheelEvent wheelEvent(m_pos, m_pos, QPoint(), (axis == InputRedirection::PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta), delta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, m_qtButtons, m_input->keyboardModifiers()); wheelEvent.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->wheelEvent(&wheelEvent)) { return; } } } void PointerInputRedirection::update() { if (!m_inited) { return; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return; } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; updateInternalWindow(); if (!m_internalWindow) { updateDecoration(t); } else { // TODO: send hover leave to decoration if (m_decoration) { m_decoration->client()->leaveEvent(); } m_decoration.clear(); } if (m_decoration || m_internalWindow) { t = nullptr; } if (m_decoration != oldDeco) { emit decorationChanged(); } auto oldWindow = m_window; if (!oldWindow.isNull() && t == m_window.data()) { return; } auto seat = waylandServer()->seat(); // disconnect old surface if (oldWindow) { if (AbstractClient *c = qobject_cast(oldWindow.data())) { c->leaveEvent(); } disconnect(m_windowGeometryConnection); m_windowGeometryConnection = QMetaObject::Connection(); } if (AbstractClient *c = qobject_cast(t)) { // only send enter if it wasn't on deco for the same client before if (m_decoration.isNull() || m_decoration->client() != c) { c->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } } if (t && t->surface()) { m_window = QPointer(t); seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, [this] { if (m_window.isNull()) { return; } // TODO: can we check on the client instead? if (workspace()->getMovingClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (m_window.data()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation()); } ); } else { m_window.clear(); seat->setFocusedPointerSurface(nullptr); t = nullptr; } } void PointerInputRedirection::updateInternalWindow() { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (w->geometry().contains(m_pos.toPoint())) { m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } }); QEnterEvent event(m_pos - m_internalWindow->position(), m_pos - m_internalWindow->position(), m_pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); return; } } } void PointerInputRedirection::updateDecoration(Toplevel *t) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(m_pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } const QPointF p = m_pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), m_pos.toPoint()); } } void PointerInputRedirection::updatePosition(const QPointF &pos) { // verify that at least one screen contains the pointer position QPointF p = pos; if (!screenContainsPos(p)) { // allow either x or y to pass p = QPointF(m_pos.x(), pos.y()); if (!screenContainsPos(p)) { p = QPointF(pos.x(), m_pos.y()); if (!screenContainsPos(p)) { return; } } } m_pos = p; emit m_input->globalPointerChanged(m_pos); } void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) { m_buttons[button] = state; // update Qt buttons m_qtButtons = Qt::NoButton; for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { if (it.value() == InputRedirection::PointerButtonReleased) { continue; } m_qtButtons |= buttonToQtMouseButton(it.key()); } emit m_input->pointerButtonStateChanged(button, state); } void PointerInputRedirection::warp(const QPointF &pos) { if (supportsWarping()) { kwinApp()->platform()->warpPointer(pos); processMotion(pos, waylandServer()->seat()->timestamp()); } } bool PointerInputRedirection::supportsWarping() const { if (!m_inited) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!m_inited) { return; } if (screenContainsPos(m_pos)) { // pointer still on a screen return; } // pointer no longer on a screen, reposition to closes screen const QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center(); // TODO: better way to get timestamps processMotion(pos, waylandServer()->seat()->timestamp()); } QImage PointerInputRedirection::cursorImage() const { if (!m_inited) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!m_inited) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!m_inited) { return; } m_cursor->markAsRendered(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!m_inited) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!m_inited) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { disconnect(m_drag.connection); reevaluteSource(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); if (m_cursorTheme) { connect(m_cursorTheme, &WaylandCursorTheme::themeChanged, this, [this] { m_cursors.clear(); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); updateDecorationCursor(); updateMoveResize(); // TODO: update effects } ); } m_surfaceRenderedTimer.start(); } CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { if (m_currentSource == CursorSource::DragAndDrop) { // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto s = ddi->icon()) { s->frameRendered(m_surfaceRenderedTimer.elapsed()); } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); return; } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } auto p = waylandServer()->seat()->focusedPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); } void CursorImage::update() { using namespace KWayland::Server; disconnect(m_serverCursor.connection); auto p = waylandServer()->seat()->focusedPointer(); if (p) { m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor); } else { m_serverCursor.connection = QMetaObject::Connection(); } updateServerCursor(); } void CursorImage::updateDecoration() { disconnect(m_decorationConnection); auto deco = m_pointer->decoration(); AbstractClient *c = deco.isNull() ? nullptr : deco->client(); if (c) { m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); } else { m_decorationConnection = QMetaObject::Connection(); } updateDecorationCursor(); } void CursorImage::updateDecorationCursor() { m_decorationCursor.image = QImage(); m_decorationCursor.hotSpot = QPoint(); auto deco = m_pointer->decoration(); if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) { loadThemeCursor(c->cursor(), &m_decorationCursor); if (m_currentSource == CursorSource::Decoration) { emit changed(); } } reevaluteSource(); } void CursorImage::updateMoveResize() { m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->getMovingClient()) { loadThemeCursor(c->isMove() ? Qt::SizeAllCursor : Qt::SizeBDiagCursor, &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); } } reevaluteSource(); } void CursorImage::updateServerCursor() { m_serverCursor.image = QImage(); m_serverCursor.hotSpot = QPoint(); reevaluteSource(); const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface; auto p = waylandServer()->seat()->focusedPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_serverCursor.hotSpot = c->hotspot(); m_serverCursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } } void CursorImage::loadTheme() { if (m_cursorTheme) { return; } // check whether we can create it if (waylandServer()->internalShmPool()) { m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, [this] { delete m_cursorTheme; m_cursorTheme = nullptr; } ); } } void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); if (m_currentSource == CursorSource::EffectsOverride) { emit changed(); } reevaluteSource(); } void CursorImage::removeEffectsOverrideCursor() { reevaluteSource(); } void CursorImage::updateDrag() { using namespace KWayland::Server; disconnect(m_drag.connection); m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); reevaluteSource(); if (auto p = waylandServer()->seat()->dragPointer()) { m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); } else { m_drag.connection = QMetaObject::Connection(); } updateDragCursor(); } void CursorImage::updateDragCursor() { m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; QImage additionalIcon; if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto dragIcon = ddi->icon()) { if (auto buffer = dragIcon->buffer()) { additionalIcon = buffer->data().copy(); } } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_drag.cursor.hotSpot = c->hotspot(); m_drag.cursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } // TODO: add the cursor image } void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = m_cursors.constFind(shape); if (it == m_cursors.constEnd()) { image->image = QImage(); image->hotSpot = QPoint(); wl_cursor_image *cursor = m_cursorTheme->get(shape); if (!cursor) { return; } wl_buffer *b = wl_cursor_image_get_buffer(cursor); if (!b) { return; } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); if (!buffer) { return; } it = decltype(it)(m_cursors.insert(shape, {buffer->data().copy(), QPoint(cursor->hotspot_x, cursor->hotspot_y)})); } image->hotSpot = it.value().hotSpot; image->image = it.value().image; } void CursorImage::reevaluteSource() { if (waylandServer()->seat()->isDragPointer()) { // TODO: touch drag? setSource(CursorSource::DragAndDrop); return; } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->getMovingClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->window().isNull()) { setSource(CursorSource::PointerSurface); return; } setSource(CursorSource::Fallback); } void CursorImage::setSource(CursorSource source) { if (m_currentSource == source) { return; } m_currentSource = source; emit changed(); } QImage CursorImage::image() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.image; case CursorSource::MoveResize: return m_moveResizeCursor.image; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; case CursorSource::DragAndDrop: return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; default: Q_UNREACHABLE(); } } QPoint CursorImage::hotSpot() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.hotSpot; case CursorSource::MoveResize: return m_moveResizeCursor.hotSpot; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; case CursorSource::DragAndDrop: return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/scene_opengl.cpp b/scene_opengl.cpp index 6472d1231..886b9664b 100644 --- a/scene_opengl.cpp +++ b/scene_opengl.cpp @@ -1,2583 +1,2583 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009, 2010, 2011 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. Explicit command stream synchronization based on the sample implementation by James Jones , Copyright © 2011 NVIDIA Corporation 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 "scene_opengl.h" #include "eglonxbackend.h" #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif -#include "abstract_backend.h" +#include "platform.h" #include "wayland_server.h" #include #include #include "utils.h" #include "client.h" #include "composite.h" #include "deleted.h" #include "effects.h" #include "lanczosfilter.h" #include "main.h" #include "overlaywindow.h" #include "screens.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // HACK: workaround for libepoxy < 1.3 #ifndef GL_GUILTY_CONTEXT_RESET #define GL_GUILTY_CONTEXT_RESET 0x8253 #endif #ifndef GL_INNOCENT_CONTEXT_RESET #define GL_INNOCENT_CONTEXT_RESET 0x8254 #endif #ifndef GL_UNKNOWN_CONTEXT_RESET #define GL_UNKNOWN_CONTEXT_RESET 0x8255 #endif namespace KWin { extern int currentRefreshRate(); /** * SyncObject represents a fence used to synchronize operations in * the kwin command stream with operations in the X command stream. */ class SyncObject { public: enum State { Ready, TriggerSent, Waiting, Done, Resetting }; SyncObject(); ~SyncObject(); State state() const { return m_state; } void trigger(); void wait(); bool finish(); void reset(); void finishResetting(); private: State m_state; GLsync m_sync; xcb_sync_fence_t m_fence; xcb_get_input_focus_cookie_t m_reset_cookie; }; SyncObject::SyncObject() { m_state = Ready; xcb_connection_t * const c = connection(); m_fence = xcb_generate_id(c); xcb_sync_create_fence(c, rootWindow(), m_fence, false); xcb_flush(c); m_sync = glImportSyncEXT(GL_SYNC_X11_FENCE_EXT, m_fence, 0); } SyncObject::~SyncObject() { // If glDeleteSync is called before the xcb fence is signalled // the nvidia driver (the only one to implement GL_SYNC_X11_FENCE_EXT) // deadlocks waiting for the fence to be signalled. // To avoid this, make sure the fence is signalled before // deleting the sync. if (m_state == Resetting || m_state == Ready){ trigger(); // The flush is necessary! // The trigger command needs to be sent to the X server. xcb_flush(connection()); } xcb_sync_destroy_fence(connection(), m_fence); glDeleteSync(m_sync); if (m_state == Resetting) xcb_discard_reply(connection(), m_reset_cookie.sequence); } void SyncObject::trigger() { assert(m_state == Ready || m_state == Resetting); // Finish resetting the fence if necessary if (m_state == Resetting) finishResetting(); xcb_sync_trigger_fence(connection(), m_fence); m_state = TriggerSent; } void SyncObject::wait() { if (m_state != TriggerSent) return; glWaitSync(m_sync, 0, GL_TIMEOUT_IGNORED); m_state = Waiting; } bool SyncObject::finish() { if (m_state == Done) return true; // Note: It is possible that we never inserted a wait for the fence. // This can happen if we ended up not rendering the damaged // window because it is fully occluded. assert(m_state == TriggerSent || m_state == Waiting); // Check if the fence is signaled GLint value; glGetSynciv(m_sync, GL_SYNC_STATUS, 1, nullptr, &value); if (value != GL_SIGNALED) { qCDebug(KWIN_CORE) << "Waiting for X fence to finish"; // Wait for the fence to become signaled with a one second timeout const GLenum result = glClientWaitSync(m_sync, 0, 1000000000); switch (result) { case GL_TIMEOUT_EXPIRED: qCWarning(KWIN_CORE) << "Timeout while waiting for X fence"; return false; case GL_WAIT_FAILED: qCWarning(KWIN_CORE) << "glClientWaitSync() failed"; return false; } } m_state = Done; return true; } void SyncObject::reset() { assert(m_state == Done); xcb_connection_t * const c = connection(); // Send the reset request along with a sync request. // We use the cookie to ensure that the server has processed the reset // request before we trigger the fence and call glWaitSync(). // Otherwise there is a race condition between the reset finishing and // the glWaitSync() call. xcb_sync_reset_fence(c, m_fence); m_reset_cookie = xcb_get_input_focus(c); xcb_flush(c); m_state = Resetting; } void SyncObject::finishResetting() { assert(m_state == Resetting); free(xcb_get_input_focus_reply(connection(), m_reset_cookie, nullptr)); m_state = Ready; } // ----------------------------------------------------------------------- /** * SyncManager manages a set of fences used for explicit synchronization * with the X command stream. */ class SyncManager { public: enum { MaxFences = 4 }; SyncManager(); ~SyncManager(); SyncObject *nextFence(); bool updateFences(); private: std::array m_fences; int m_next; }; SyncManager::SyncManager() : m_next(0) { } SyncManager::~SyncManager() { } SyncObject *SyncManager::nextFence() { SyncObject *fence = &m_fences[m_next]; m_next = (m_next + 1) % MaxFences; return fence; } bool SyncManager::updateFences() { for (int i = 0; i < qMin(2, MaxFences - 1); i++) { const int index = (m_next + i) % MaxFences; SyncObject &fence = m_fences[index]; switch (fence.state()) { case SyncObject::Ready: break; case SyncObject::TriggerSent: case SyncObject::Waiting: if (!fence.finish()) return false; fence.reset(); break; // Should not happen in practice since we always reset the fence // after finishing it case SyncObject::Done: fence.reset(); break; case SyncObject::Resetting: fence.finishResetting(); break; } } return true; } // ----------------------------------------------------------------------- //**************************************** // SceneOpenGL //**************************************** OpenGLBackend::OpenGLBackend() : m_syncsToVBlank(false) , m_blocksForRetrace(false) , m_directRendering(false) , m_haveBufferAge(false) , m_failed(false) { } OpenGLBackend::~OpenGLBackend() { } void OpenGLBackend::setFailed(const QString &reason) { qCWarning(KWIN_CORE) << "Creating the OpenGL rendering failed: " << reason; m_failed = true; } void OpenGLBackend::idle() { if (hasPendingFlush()) { effects->makeOpenGLContextCurrent(); present(); } } void OpenGLBackend::addToDamageHistory(const QRegion ®ion) { if (m_damageHistory.count() > 10) m_damageHistory.removeLast(); m_damageHistory.prepend(region); } QRegion OpenGLBackend::accumulatedDamageHistory(int bufferAge) const { QRegion region; // Note: An age of zero means the buffer contents are undefined if (bufferAge > 0 && bufferAge <= m_damageHistory.count()) { for (int i = 0; i < bufferAge - 1; i++) region |= m_damageHistory[i]; } else { const QSize &s = screens()->size(); region = QRegion(0, 0, s.width(), s.height()); } return region; } OverlayWindow* OpenGLBackend::overlayWindow() { return NULL; } QRegion OpenGLBackend::prepareRenderingForScreen(int screenId) { // fallback to repaint complete screen return screens()->geometry(screenId); } void OpenGLBackend::endRenderingFrameForScreen(int screenId, const QRegion &damage, const QRegion &damagedRegion) { Q_UNUSED(screenId) Q_UNUSED(damage) Q_UNUSED(damagedRegion) } bool OpenGLBackend::perScreenRendering() const { return false; } /************************************************ * SceneOpenGL ***********************************************/ SceneOpenGL::SceneOpenGL(OpenGLBackend *backend, QObject *parent) : Scene(parent) , init_ok(true) , m_backend(backend) , m_syncManager(nullptr) , m_currentFence(nullptr) { if (m_backend->isFailed()) { init_ok = false; return; } if (!viewportLimitsMatched(screens()->size())) return; // perform Scene specific checks GLPlatform *glPlatform = GLPlatform::instance(); if (!glPlatform->isGLES() && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_non_power_of_two")) && !hasGLExtension(QByteArrayLiteral("GL_ARB_texture_rectangle"))) { qCCritical(KWIN_CORE) << "GL_ARB_texture_non_power_of_two and GL_ARB_texture_rectangle missing"; init_ok = false; return; // error } if (glPlatform->isMesaDriver() && glPlatform->mesaVersion() < kVersionNumber(8, 0)) { qCCritical(KWIN_CORE) << "KWin requires at least Mesa 8.0 for OpenGL compositing."; init_ok = false; return; } if (!glPlatform->isGLES() && !m_backend->isSurfaceLessContext()) { glDrawBuffer(GL_BACK); } m_debug = qstrcmp(qgetenv("KWIN_GL_DEBUG"), "1") == 0; initDebugOutput(); // set strict binding if (options->isGlStrictBindingFollowsDriver()) { options->setGlStrictBinding(!glPlatform->supports(LooseBinding)); } bool haveSyncObjects = glPlatform->isGLES() ? hasGLVersion(3, 0) : hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync"); if (hasGLExtension("GL_EXT_x11_sync_object") && haveSyncObjects) { const QByteArray useExplicitSync = qgetenv("KWIN_EXPLICIT_SYNC"); if (useExplicitSync != "0") { qCDebug(KWIN_CORE) << "Initializing fences for synchronization with the X command stream"; m_syncManager = new SyncManager; } else { qCDebug(KWIN_CORE) << "Explicit synchronization with the X command stream disabled by environment variable"; } } } static SceneOpenGL *gs_debuggedScene = nullptr; SceneOpenGL::~SceneOpenGL() { // do cleanup after initBuffer() gs_debuggedScene = nullptr; SceneOpenGL::EffectFrame::cleanup(); if (init_ok) { delete m_syncManager; // backend might be still needed for a different scene delete m_backend; } } static void scheduleVboReInit() { if (!gs_debuggedScene) return; static QPointer timer; if (!timer) { delete timer; timer = new QTimer(gs_debuggedScene); timer->setSingleShot(true); QObject::connect(timer.data(), &QTimer::timeout, gs_debuggedScene, []() { GLVertexBuffer::cleanup(); GLVertexBuffer::initStatic(); }); } timer->start(250); } void SceneOpenGL::initDebugOutput() { const bool have_KHR_debug = hasGLExtension(QByteArrayLiteral("GL_KHR_debug")); const bool have_ARB_debug = hasGLExtension(QByteArrayLiteral("GL_ARB_debug_output")); if (!have_KHR_debug && !have_ARB_debug) return; if (!have_ARB_debug) { // if we don't have ARB debug, but only KHR debug we need to verify whether the context is a debug context // it should work without as well, but empirical tests show: no it doesn't if (GLPlatform::instance()->isGLES()) { if (!hasGLVersion(3, 2)) { // empirical data shows extension doesn't work return; } } else if (!hasGLVersion(3, 0)) { return; } // can only be queried with either OpenGL >= 3.0 or OpenGL ES of at least 3.1 GLint value = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &value); if (!(value & GL_CONTEXT_FLAG_DEBUG_BIT)) { return; } } gs_debuggedScene = this; // Set the callback function auto callback = [](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const GLvoid *userParam) { Q_UNUSED(source) Q_UNUSED(severity) Q_UNUSED(userParam) while (message[length] == '\n' || message[length] == '\r') --length; switch (type) { case GL_DEBUG_TYPE_ERROR: case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: qCWarning(KWIN_CORE, "%#x: %.*s", id, length, message); break; case GL_DEBUG_TYPE_OTHER: // at least the nvidia driver seems prone to end up with invalid VBOs after // transferring them between system heap and VRAM // so we re-init them whenever this happens (typically when switching VT, resuming // from STR and XRandR events - #344326 if (strstr(message, "Buffer detailed info:") && strstr(message, "has been updated")) scheduleVboReInit(); // fall through! for general message printing case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: case GL_DEBUG_TYPE_PORTABILITY: case GL_DEBUG_TYPE_PERFORMANCE: default: qCDebug(KWIN_CORE, "%#x: %.*s", id, length, message); break; } }; glDebugMessageCallback(callback, nullptr); // This state exists only in GL_KHR_debug if (have_KHR_debug) glEnable(GL_DEBUG_OUTPUT); #ifndef NDEBUG // Enable all debug messages glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); #else // Enable error messages glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); #endif // Insert a test message const QByteArray message = QByteArrayLiteral("OpenGL debug output initialized"); glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 0, GL_DEBUG_SEVERITY_LOW, message.length(), message.constData()); } SceneOpenGL *SceneOpenGL::createScene(QObject *parent) { OpenGLBackend *backend = NULL; OpenGLPlatformInterface platformInterface = options->glPlatformInterface(); switch (platformInterface) { case GlxPlatformInterface: #if HAVE_EPOXY_GLX backend = new GlxBackend(); #endif break; case EglPlatformInterface: if (kwinApp()->shouldUseWaylandForCompositing()) { backend = kwinApp()->platform()->createOpenGLBackend(); } else { backend = new EglOnXBackend(); } break; default: // no backend available return NULL; } if (!backend) { return nullptr; } if (!backend->isFailed()) { backend->init(); } if (backend->isFailed()) { delete backend; return NULL; } SceneOpenGL *scene = NULL; // first let's try an OpenGL 2 scene if (SceneOpenGL2::supported(backend)) { scene = new SceneOpenGL2(backend, parent); if (scene->initFailed()) { delete scene; scene = NULL; } else { return scene; } } if (!scene) { if (GLPlatform::instance()->recommendedCompositor() == XRenderCompositing) { qCCritical(KWIN_CORE) << "OpenGL driver recommends XRender based compositing. Falling back to XRender."; qCCritical(KWIN_CORE) << "To overwrite the detection use the environment variable KWIN_COMPOSE"; qCCritical(KWIN_CORE) << "For more information see http://community.kde.org/KWin/Environment_Variables#KWIN_COMPOSE"; QTimer::singleShot(0, Compositor::self(), SLOT(fallbackToXRenderCompositing())); } delete backend; } return scene; } OverlayWindow *SceneOpenGL::overlayWindow() { return m_backend->overlayWindow(); } bool SceneOpenGL::syncsToVBlank() const { return m_backend->syncsToVBlank(); } bool SceneOpenGL::blocksForRetrace() const { return m_backend->blocksForRetrace(); } void SceneOpenGL::idle() { m_backend->idle(); Scene::idle(); } bool SceneOpenGL::initFailed() const { return !init_ok; } void SceneOpenGL::copyPixels(const QRegion ®ion) { const int height = screens()->size().height(); foreach (const QRect &r, region.rects()) { const int x0 = r.x(); const int y0 = height - r.y() - r.height(); const int x1 = r.x() + r.width(); const int y1 = height - r.y(); glBlitFramebuffer(x0, y0, x1, y1, x0, y0, x1, y1, GL_COLOR_BUFFER_BIT, GL_NEAREST); } } void SceneOpenGL::handleGraphicsReset(GLenum status) { switch (status) { case GL_GUILTY_CONTEXT_RESET: qCDebug(KWIN_CORE) << "A graphics reset attributable to the current GL context occurred."; break; case GL_INNOCENT_CONTEXT_RESET: qCDebug(KWIN_CORE) << "A graphics reset not attributable to the current GL context occurred."; break; case GL_UNKNOWN_CONTEXT_RESET: qCDebug(KWIN_CORE) << "A graphics reset of an unknown cause occurred."; break; default: break; } QElapsedTimer timer; timer.start(); // Wait until the reset is completed or max 10 seconds while (timer.elapsed() < 10000 && glGetGraphicsResetStatus() != GL_NO_ERROR) usleep(50); qCDebug(KWIN_CORE) << "Attempting to reset compositing."; QMetaObject::invokeMethod(this, "resetCompositing", Qt::QueuedConnection); KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); } void SceneOpenGL::triggerFence() { if (m_syncManager) { m_currentFence = m_syncManager->nextFence(); m_currentFence->trigger(); } } void SceneOpenGL::insertWait() { if (m_currentFence && m_currentFence->state() != SyncObject::Waiting) { m_currentFence->wait(); } } qint64 SceneOpenGL::paint(QRegion damage, ToplevelList toplevels) { // actually paint the frame, flushed with the NEXT frame createStackingOrder(toplevels); // After this call, updateRegion will contain the damaged region in the // back buffer. This is the region that needs to be posted to repair // the front buffer. It doesn't include the additional damage returned // by prepareRenderingFrame(). validRegion is the region that has been // repainted, and may be larger than updateRegion. QRegion updateRegion, validRegion; if (m_backend->perScreenRendering()) { // trigger start render timer m_backend->prepareRenderingFrame(); for (int i = 0; i < screens()->count(); ++i) { const QRect &geo = screens()->geometry(i); QRegion update; QRegion valid; // prepare rendering makes context current on the output QRegion repaint = m_backend->prepareRenderingForScreen(i); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage.intersected(geo), repaint, &update, &valid, projectionMatrix()); // call generic implementation GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrameForScreen(i, valid, update); GLVertexBuffer::streamingBuffer()->framePosted(); } } else { m_backend->makeCurrent(); QRegion repaint = m_backend->prepareRenderingFrame(); const GLenum status = glGetGraphicsResetStatus(); if (status != GL_NO_ERROR) { handleGraphicsReset(status); return 0; } int mask = 0; updateProjectionMatrix(); paintScreen(&mask, damage, repaint, &updateRegion, &validRegion, projectionMatrix()); // call generic implementation if (!GLPlatform::instance()->isGLES()) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); // copy dirty parts from front to backbuffer if (!m_backend->supportsBufferAge() && options->glPreferBufferSwap() == Options::CopyFrontBuffer && validRegion != displayRegion) { glReadBuffer(GL_FRONT); copyPixels(displayRegion - validRegion); glReadBuffer(GL_BACK); validRegion = displayRegion; } } GLVertexBuffer::streamingBuffer()->endOfFrame(); m_backend->endRenderingFrame(validRegion, updateRegion); GLVertexBuffer::streamingBuffer()->framePosted(); } if (m_currentFence) { if (!m_syncManager->updateFences()) { qCDebug(KWIN_CORE) << "Aborting explicit synchronization with the X command stream."; qCDebug(KWIN_CORE) << "Future frames will be rendered unsynchronized."; delete m_syncManager; m_syncManager = nullptr; } m_currentFence = nullptr; } // do cleanup clearStackingOrder(); return m_backend->renderTime(); } QMatrix4x4 SceneOpenGL::transformation(int mask, const ScreenPaintData &data) const { QMatrix4x4 matrix; if (!(mask & PAINT_SCREEN_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation->applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } void SceneOpenGL::paintBackground(QRegion region) { PaintClipper pc(region); if (!PaintClipper::clip()) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); return; } if (pc.clip() && pc.paintArea().isEmpty()) return; // no background to paint QVector verts; for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) { QRect r = iterator.boundingRect(); verts << r.x() + r.width() << r.y(); verts << r.x() << r.y(); verts << r.x() << r.y() + r.height(); verts << r.x() << r.y() + r.height(); verts << r.x() + r.width() << r.y() + r.height(); verts << r.x() + r.width() << r.y(); } doPaintBackground(verts); } void SceneOpenGL::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { if (m_backend->supportsBufferAge()) return; const QSize &screenSize = screens()->size(); if (options->glPreferBufferSwap() == Options::ExtendDamage) { // only Extend "large" repaints const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); uint damagedPixels = 0; const uint fullRepaintLimit = (opaqueFullscreen?0.49f:0.748f)*screenSize.width()*screenSize.height(); // 16:9 is 75% of 4:3 and 2.55:1 is 49.01% of 5:4 // (5:4 is the most square format and 2.55:1 is Cinemascope55 - the widest ever shot // movie aspect - two times ;-) It's a Fox format, though, so maybe we want to restrict // to 2.20:1 - Panavision - which has actually been used for interesting movies ...) // would be 57% of 5/4 foreach (const QRect &r, region.rects()) { // damagedPixels += r.width() * r.height(); // combined window damage test damagedPixels = r.width() * r.height(); // experimental single window damage testing if (damagedPixels > fullRepaintLimit) { region = displayRegion; return; } } } else if (options->glPreferBufferSwap() == Options::PaintFullScreen) { // forced full rePaint region = QRegion(0, 0, screenSize.width(), screenSize.height()); } } SceneOpenGL::Texture *SceneOpenGL::createTexture() { return new Texture(m_backend); } bool SceneOpenGL::viewportLimitsMatched(const QSize &size) const { GLint limit[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, limit); if (limit[0] < size.width() || limit[1] < size.height()) { QMetaObject::invokeMethod(Compositor::self(), "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, Compositor::AllReasonSuspend)); const QString message = i18n("

OpenGL desktop effects not possible

" "Your system cannot perform OpenGL Desktop Effects at the " "current resolution

" "You can try to select the XRender backend, but it " "might be very slow for this resolution as well.
" "Alternatively, lower the combined resolution of all screens " "to %1x%2 ", limit[0], limit[1]); const QString details = i18n("The demanded resolution exceeds the GL_MAX_VIEWPORT_DIMS " "limitation of your GPU and is therefore not compatible " "with the OpenGL compositor.
" "XRender does not know such limitation, but the performance " "will usually be impacted by the hardware limitations that " "restrict the OpenGL viewport size."); const int oldTimeout = QDBusConnection::sessionBus().interface()->timeout(); QDBusConnection::sessionBus().interface()->setTimeout(500); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kwinCompositingDialog")).value()) { QDBusInterface dialog( QStringLiteral("org.kde.kwinCompositingDialog"), QStringLiteral("/CompositorSettings"), QStringLiteral("org.kde.kwinCompositingDialog") ); dialog.asyncCall(QStringLiteral("warn"), message, details, QString()); } else { const QString args = QLatin1String("warn ") + QString::fromUtf8(message.toLocal8Bit().toBase64()) + QLatin1String(" details ") + QString::fromUtf8(details.toLocal8Bit().toBase64()); KProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("kwincompositing") << QStringLiteral("--args") << args); } QDBusConnection::sessionBus().interface()->setTimeout(oldTimeout); return false; } glGetIntegerv(GL_MAX_TEXTURE_SIZE, limit); if (limit[0] < size.width() || limit[0] < size.height()) { KConfig cfg(QStringLiteral("kwin_dialogsrc")); if (!KConfigGroup(&cfg, "Notification Messages").readEntry("max_tex_warning", true)) return true; const QString message = i18n("

OpenGL desktop effects might be unusable

" "OpenGL Desktop Effects at the current resolution are supported " "but might be exceptionally slow.
" "Also large windows will turn entirely black.

" "Consider to suspend compositing, switch to the XRender backend " "or lower the resolution to %1x%1." , limit[0]); const QString details = i18n("The demanded resolution exceeds the GL_MAX_TEXTURE_SIZE " "limitation of your GPU, thus windows of that size cannot be " "assigned to textures and will be entirely black.
" "Also this limit will often be a performance level barrier despite " "below GL_MAX_VIEWPORT_DIMS, because the driver might fall back to " "software rendering in this case."); const int oldTimeout = QDBusConnection::sessionBus().interface()->timeout(); QDBusConnection::sessionBus().interface()->setTimeout(500); if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kwinCompositingDialog")).value()) { QDBusInterface dialog( QStringLiteral("org.kde.kwinCompositingDialog"), QStringLiteral("/CompositorSettings"), QStringLiteral("org.kde.kwinCompositingDialog") ); dialog.asyncCall(QStringLiteral("warn"), message, details, QStringLiteral("kwin_dialogsrc:max_tex_warning")); } else { const QString args = QLatin1String("warn ") + QString::fromUtf8(message.toLocal8Bit().toBase64()) + QLatin1String(" details ") + QString::fromUtf8(details.toLocal8Bit().toBase64()) + QLatin1String(" dontagain kwin_dialogsrc:max_tex_warning"); KProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("kwincompositing") << QStringLiteral("--args") << args); } QDBusConnection::sessionBus().interface()->setTimeout(oldTimeout); } return true; } void SceneOpenGL::screenGeometryChanged(const QSize &size) { if (!viewportLimitsMatched(size)) return; Scene::screenGeometryChanged(size); glViewport(0,0, size.width(), size.height()); m_backend->screenGeometryChanged(size); GLRenderTarget::setVirtualScreenSize(size); GLVertexBuffer::setVirtualScreenSize(size); } void SceneOpenGL::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { const QRect r = region.boundingRect(); glEnable(GL_SCISSOR_TEST); glScissor(r.x(), screens()->size().height() - r.y() - r.height(), r.width(), r.height()); KWin::Scene::paintDesktop(desktop, mask, region, data); glDisable(GL_SCISSOR_TEST); } bool SceneOpenGL::makeOpenGLContextCurrent() { return m_backend->makeCurrent(); } void SceneOpenGL::doneOpenGLContextCurrent() { m_backend->doneCurrent(); } Scene::EffectFrame *SceneOpenGL::createEffectFrame(EffectFrameImpl *frame) { return new SceneOpenGL::EffectFrame(frame, this); } Shadow *SceneOpenGL::createShadow(Toplevel *toplevel) { return new SceneOpenGLShadow(toplevel); } Decoration::Renderer *SceneOpenGL::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneOpenGLDecorationRenderer(impl); } //**************************************** // SceneOpenGL2 //**************************************** bool SceneOpenGL2::supported(OpenGLBackend *backend) { const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); if (!forceEnv.isEmpty()) { if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { qCDebug(KWIN_CORE) << "OpenGL 2 compositing enforced by environment variable"; return true; } else { // OpenGL 2 disabled by environment variable return false; } } if (!backend->isDirectRendering()) { return false; } if (GLPlatform::instance()->recommendedCompositor() < OpenGL2Compositing) { qCDebug(KWIN_CORE) << "Driver does not recommend OpenGL 2 compositing"; return false; } return true; } SceneOpenGL2::SceneOpenGL2(OpenGLBackend *backend, QObject *parent) : SceneOpenGL(backend, parent) , m_lanczosFilter(NULL) , m_colorCorrection() { if (!init_ok) { // base ctor already failed return; } // We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects if (!hasGLVersion(2, 0)) { qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; init_ok = false; return; } // Initialize color correction before the shaders slotColorCorrectedChanged(false); connect(options, SIGNAL(colorCorrectedChanged()), this, SLOT(slotColorCorrectedChanged()), Qt::QueuedConnection); const QSize &s = screens()->size(); GLRenderTarget::setVirtualScreenSize(s); GLVertexBuffer::setVirtualScreenSize(s); // push one shader on the stack so that one is always bound ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); if (checkGLError("Init")) { qCCritical(KWIN_CORE) << "OpenGL 2 compositing setup failed"; init_ok = false; return; // error } // It is not legal to not have a vertex array object bound in a core context if (!GLPlatform::instance()->isGLES() && hasGLExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) { glGenVertexArrays(1, &vao); glBindVertexArray(vao); } if (!ShaderManager::instance()->selfTest()) { qCCritical(KWIN_CORE) << "ShaderManager self test failed"; init_ok = false; return; } qCDebug(KWIN_CORE) << "OpenGL 2 compositing successfully initialized"; init_ok = true; } SceneOpenGL2::~SceneOpenGL2() { } QMatrix4x4 SceneOpenGL2::createProjectionMatrix() const { // Create a perspective projection with a 60° field-of-view, // and an aspect ratio of 1.0. const float fovY = 60.0f; const float aspect = 1.0f; const float zNear = 0.1f; const float zFar = 100.0f; const float yMax = zNear * std::tan(fovY * M_PI / 360.0f); const float yMin = -yMax; const float xMin = yMin * aspect; const float xMax = yMax * aspect; QMatrix4x4 projection; projection.frustum(xMin, xMax, yMin, yMax, zNear, zFar); // Create a second matrix that transforms screen coordinates // to world coordinates. const float scaleFactor = 1.1 * std::tan(fovY * M_PI / 360.0f) / yMax; const QSize size = screens()->size(); QMatrix4x4 matrix; matrix.translate(xMin * scaleFactor, yMax * scaleFactor, -1.1); matrix.scale( (xMax - xMin) * scaleFactor / size.width(), -(yMax - yMin) * scaleFactor / size.height(), 0.001); // Combine the matrices return projection * matrix; } void SceneOpenGL2::updateProjectionMatrix() { m_projectionMatrix = createProjectionMatrix(); } void SceneOpenGL2::paintSimpleScreen(int mask, QRegion region) { m_screenProjectionMatrix = m_projectionMatrix; Scene::paintSimpleScreen(mask, region); } void SceneOpenGL2::paintGenericScreen(int mask, ScreenPaintData data) { const QMatrix4x4 screenMatrix = transformation(mask, data); m_screenProjectionMatrix = m_projectionMatrix * screenMatrix; Scene::paintGenericScreen(mask, data); } void SceneOpenGL2::doPaintBackground(const QVector< float >& vertices) { GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setUseColor(true); vbo->setData(vertices.count() / 2, 2, vertices.data(), NULL); ShaderBinder binder(ShaderTrait::UniformColor); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, m_projectionMatrix); vbo->render(GL_TRIANGLES); } Scene::Window *SceneOpenGL2::createWindow(Toplevel *t) { SceneOpenGL2Window *w = new SceneOpenGL2Window(t); w->setScene(this); return w; } void SceneOpenGL2::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } if (!m_colorCorrection.isNull() && m_colorCorrection->isEnabled()) { // Split the painting for separate screens const int numScreens = screens()->count(); for (int screen = 0; screen < numScreens; ++ screen) { QRegion regionForScreen(region); if (numScreens > 1) regionForScreen = region.intersected(screens()->geometry(screen)); data.setScreen(screen); performPaintWindow(w, mask, regionForScreen, data); } } else { performPaintWindow(w, mask, region, data); } } void SceneOpenGL2::performPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (mask & PAINT_WINDOW_LANCZOS) { if (!m_lanczosFilter) { m_lanczosFilter = new LanczosFilter(this); // recreate the lanczos filter when the screen gets resized connect(screens(), SIGNAL(changed()), SLOT(resetLanczosFilter())); } m_lanczosFilter->performPaint(w, mask, region, data); } else w->sceneWindow()->performPaint(mask, region, data); } void SceneOpenGL2::resetLanczosFilter() { // TODO: Qt5 - replace by a lambda slot delete m_lanczosFilter; m_lanczosFilter = NULL; } ColorCorrection *SceneOpenGL2::colorCorrection() { return m_colorCorrection.data(); } void SceneOpenGL2::slotColorCorrectedChanged(bool recreateShaders) { qCDebug(KWIN_CORE) << "Color correction:" << options->isColorCorrected(); if (options->isColorCorrected() && m_colorCorrection.isNull()) { m_colorCorrection.reset(new ColorCorrection(this)); if (!m_colorCorrection->setEnabled(true)) { m_colorCorrection.reset(); return; } connect(m_colorCorrection.data(), SIGNAL(changed()), Compositor::self(), SLOT(addRepaintFull())); connect(m_colorCorrection.data(), SIGNAL(errorOccured()), options, SLOT(setColorCorrected()), Qt::QueuedConnection); if (recreateShaders) { // Reload all shaders ShaderManager::cleanup(); ShaderManager::instance(); } } else { m_colorCorrection.reset(); } Compositor::self()->addRepaintFull(); } //**************************************** // SceneOpenGL::Texture //**************************************** SceneOpenGL::Texture::Texture(OpenGLBackend *backend) : GLTexture(*backend->createBackendTexture(this)) { } SceneOpenGL::Texture::~Texture() { } SceneOpenGL::Texture& SceneOpenGL::Texture::operator = (const SceneOpenGL::Texture& tex) { d_ptr = tex.d_ptr; return *this; } void SceneOpenGL::Texture::discard() { d_ptr = d_func()->backend()->createBackendTexture(this); } bool SceneOpenGL::Texture::load(WindowPixmap *pixmap) { if (!pixmap->isValid()) { return false; } // decrease the reference counter for the old texture d_ptr = d_func()->backend()->createBackendTexture(this); //new TexturePrivate(); Q_D(Texture); return d->loadTexture(pixmap); } void SceneOpenGL::Texture::updateFromPixmap(WindowPixmap *pixmap) { Q_D(Texture); d->updateTexture(pixmap); } //**************************************** // SceneOpenGL::Texture //**************************************** SceneOpenGL::TexturePrivate::TexturePrivate() { } SceneOpenGL::TexturePrivate::~TexturePrivate() { } void SceneOpenGL::TexturePrivate::updateTexture(WindowPixmap *pixmap) { Q_UNUSED(pixmap) } //**************************************** // SceneOpenGL::Window //**************************************** SceneOpenGL::Window::Window(Toplevel* c) : Scene::Window(c) , m_scene(NULL) { } SceneOpenGL::Window::~Window() { } static SceneOpenGL::Texture *s_frameTexture = NULL; // Bind the window pixmap to an OpenGL texture. bool SceneOpenGL::Window::bindTexture() { s_frameTexture = NULL; OpenGLWindowPixmap *pixmap = windowPixmap(); if (!pixmap) { return false; } s_frameTexture = pixmap->texture(); if (pixmap->isDiscarded()) { return !pixmap->texture()->isNull(); } if (!window()->damage().isEmpty()) m_scene->insertWait(); return pixmap->bind(); } QMatrix4x4 SceneOpenGL::Window::transformation(int mask, const WindowPaintData &data) const { QMatrix4x4 matrix; matrix.translate(x(), y()); if (!(mask & PAINT_WINDOW_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // cannot use data.rotation.applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } bool SceneOpenGL::Window::beginRenderWindow(int mask, const QRegion ®ion, WindowPaintData &data) { if (region.isEmpty()) return false; m_hardwareClipping = region != infiniteRegion() && (mask & PAINT_WINDOW_TRANSFORMED) && !(mask & PAINT_SCREEN_TRANSFORMED); if (region != infiniteRegion() && !m_hardwareClipping) { WindowQuadList quads; quads.reserve(data.quads.count()); const QRegion filterRegion = region.translated(-x(), -y()); // split all quads in bounding rect with the actual rects in the region foreach (const WindowQuad &quad, data.quads) { foreach (const QRect &r, filterRegion.rects()) { const QRectF rf(r); const QRectF quadRect(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())); const QRectF &intersected = rf.intersected(quadRect); if (intersected.isValid()) { if (quadRect == intersected) { // case 1: completely contains, include and do not check other rects quads << quad; break; } // case 2: intersection quads << quad.makeSubQuad(intersected.left(), intersected.top(), intersected.right(), intersected.bottom()); } } } data.quads = quads; } if (data.quads.isEmpty()) return false; if (!bindTexture() || !s_frameTexture) { return false; } if (m_hardwareClipping) { glEnable(GL_SCISSOR_TEST); } // Update the texture filter if (options->glSmoothScale() != 0 && (mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) filter = ImageFilterGood; else filter = ImageFilterFast; s_frameTexture->setFilter(filter == ImageFilterGood ? GL_LINEAR : GL_NEAREST); const GLVertexAttrib attribs[] = { { VA_Position, 2, GL_FLOAT, offsetof(GLVertex2D, position) }, { VA_TexCoord, 2, GL_FLOAT, offsetof(GLVertex2D, texcoord) }, }; GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setAttribLayout(attribs, 2, sizeof(GLVertex2D)); return true; } void SceneOpenGL::Window::endRenderWindow() { if (m_hardwareClipping) { glDisable(GL_SCISSOR_TEST); } } GLTexture *SceneOpenGL::Window::getDecorationTexture() const { if (AbstractClient *client = dynamic_cast(toplevel)) { if (client->noBorder()) { return nullptr; } if (!client->isDecorated()) { return nullptr; } if (SceneOpenGLDecorationRenderer *renderer = static_cast(client->decoratedClient()->renderer())) { renderer->render(); return renderer->texture(); } } else if (toplevel->isDeleted()) { Deleted *deleted = static_cast(toplevel); if (!deleted->wasClient() || deleted->noBorder()) { return nullptr; } if (const SceneOpenGLDecorationRenderer *renderer = static_cast(deleted->decorationRenderer())) { return renderer->texture(); } } return nullptr; } WindowPixmap* SceneOpenGL::Window::createWindowPixmap() { return new OpenGLWindowPixmap(this, m_scene); } //*************************************** // SceneOpenGL2Window //*************************************** SceneOpenGL2Window::SceneOpenGL2Window(Toplevel *c) : SceneOpenGL::Window(c) , m_blendingEnabled(false) { } SceneOpenGL2Window::~SceneOpenGL2Window() { } QVector4D SceneOpenGL2Window::modulate(float opacity, float brightness) const { const float a = opacity; const float rgb = opacity * brightness; return QVector4D(rgb, rgb, rgb, a); } void SceneOpenGL2Window::setBlendEnabled(bool enabled) { if (enabled && !m_blendingEnabled) glEnable(GL_BLEND); else if (!enabled && m_blendingEnabled) glDisable(GL_BLEND); m_blendingEnabled = enabled; } void SceneOpenGL2Window::setupLeafNodes(LeafNode *nodes, const WindowQuadList *quads, const WindowPaintData &data) { if (!quads[ShadowLeaf].isEmpty()) { nodes[ShadowLeaf].texture = static_cast(m_shadow)->shadowTexture(); nodes[ShadowLeaf].opacity = data.opacity(); nodes[ShadowLeaf].hasAlpha = true; nodes[ShadowLeaf].coordinateType = NormalizedCoordinates; } if (!quads[DecorationLeaf].isEmpty()) { nodes[DecorationLeaf].texture = getDecorationTexture(); nodes[DecorationLeaf].opacity = data.opacity(); nodes[DecorationLeaf].hasAlpha = true; nodes[DecorationLeaf].coordinateType = UnnormalizedCoordinates; } nodes[ContentLeaf].texture = s_frameTexture; nodes[ContentLeaf].hasAlpha = !isOpaque(); // TODO: ARGB crsoofading is atm. a hack, playing on opacities for two dumb SrcOver operations // Should be a shader if (data.crossFadeProgress() != 1.0 && (data.opacity() < 0.95 || toplevel->hasAlpha())) { const float opacity = 1.0 - data.crossFadeProgress(); nodes[ContentLeaf].opacity = data.opacity() * (1 - pow(opacity, 1.0f + 2.0f * data.opacity())); } else { nodes[ContentLeaf].opacity = data.opacity(); } nodes[ContentLeaf].coordinateType = UnnormalizedCoordinates; if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); nodes[PreviousContentLeaf].texture = previous ? previous->texture() : NULL; nodes[PreviousContentLeaf].hasAlpha = !isOpaque(); nodes[PreviousContentLeaf].opacity = data.opacity() * (1.0 - data.crossFadeProgress()); nodes[PreviousContentLeaf].coordinateType = NormalizedCoordinates; } } QMatrix4x4 SceneOpenGL2Window::modelViewProjectionMatrix(int mask, const WindowPaintData &data) const { SceneOpenGL2 *scene = static_cast(m_scene); const QMatrix4x4 pMatrix = data.projectionMatrix(); const QMatrix4x4 mvMatrix = data.modelViewMatrix(); // An effect may want to override the default projection matrix in some cases, // such as when it is rendering a window on a render target that doesn't have // the same dimensions as the default framebuffer. // // Note that the screen transformation is not applied here. if (!pMatrix.isIdentity()) return pMatrix * mvMatrix; // If an effect has specified a model-view matrix, we multiply that matrix // with the default projection matrix. If the effect hasn't specified a // model-view matrix, mvMatrix will be the identity matrix. if (mask & Scene::PAINT_SCREEN_TRANSFORMED) return scene->screenProjectionMatrix() * mvMatrix; return scene->projectionMatrix() * mvMatrix; } static void renderSubSurface(GLShader *shader, const QMatrix4x4 &mvp, const QMatrix4x4 &windowMatrix, OpenGLWindowPixmap *pixmap) { QMatrix4x4 newWindowMatrix = windowMatrix; newWindowMatrix.translate(pixmap->subSurface()->position().x(), pixmap->subSurface()->position().y()); if (!pixmap->texture()->isNull()) { // render this texture shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp * newWindowMatrix); auto texture = pixmap->texture(); texture->bind(); texture->render(QRegion(), QRect(0, 0, texture->width(), texture->height())); texture->unbind(); } const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, mvp, newWindowMatrix, static_cast(pixmap)); } } void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!beginRenderWindow(mask, region, data)) return; SceneOpenGL2 *scene = static_cast(m_scene); QMatrix4x4 windowMatrix = transformation(mask, data); const QMatrix4x4 modelViewProjection = modelViewProjectionMatrix(mask, data); const QMatrix4x4 mvpMatrix = modelViewProjection * windowMatrix; GLShader *shader = data.shader; if (!shader) { ShaderTraits traits = ShaderTrait::MapTexture; if (data.opacity() != 1.0 || data.brightness() != 1.0 || data.crossFadeProgress() != 1.0) traits |= ShaderTrait::Modulate; if (data.saturation() != 1.0) traits |= ShaderTrait::AdjustSaturation; shader = ShaderManager::instance()->pushShader(traits); } shader->setUniform(GLShader::ModelViewProjectionMatrix, mvpMatrix); if (ColorCorrection *cc = scene->colorCorrection()) { cc->setupForOutput(data.screen()); } shader->setUniform(GLShader::Saturation, data.saturation()); const GLenum filter = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) && options->glSmoothScale() != 0 ? GL_LINEAR : GL_NEAREST; WindowQuadList quads[LeafCount]; // Split the quads into separate lists for each type foreach (const WindowQuad &quad, data.quads) { switch (quad.type()) { case WindowQuadDecoration: quads[DecorationLeaf].append(quad); continue; case WindowQuadContents: quads[ContentLeaf].append(quad); continue; case WindowQuadShadow: quads[ShadowLeaf].append(quad); continue; default: continue; } } if (data.crossFadeProgress() != 1.0) { OpenGLWindowPixmap *previous = previousWindowPixmap(); if (previous) { const QRect &oldGeometry = previous->contentsRect(); for (const WindowQuad &quad : quads[ContentLeaf]) { // we need to create new window quads with normalize texture coordinates // normal quads divide the x/y position by width/height. This would not work as the texture // is larger than the visible content in case of a decorated Client resulting in garbage being shown. // So we calculate the normalized texture coordinate in the Client's new content space and map it to // the previous Client's content space. WindowQuad newQuad(WindowQuadContents); for (int i = 0; i < 4; ++i) { const qreal xFactor = qreal(quad[i].textureX() - toplevel->clientPos().x())/qreal(toplevel->clientSize().width()); const qreal yFactor = qreal(quad[i].textureY() - toplevel->clientPos().y())/qreal(toplevel->clientSize().height()); WindowVertex vertex(quad[i].x(), quad[i].y(), (xFactor * oldGeometry.width() + oldGeometry.x())/qreal(previous->size().width()), (yFactor * oldGeometry.height() + oldGeometry.y())/qreal(previous->size().height())); newQuad[i] = vertex; } quads[PreviousContentLeaf].append(newQuad); } } } const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; const int verticesPerQuad = indexedQuads ? 4 : 6; const size_t size = verticesPerQuad * (quads[0].count() + quads[1].count() + quads[2].count() + quads[3].count()) * sizeof(GLVertex2D); GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); GLVertex2D *map = (GLVertex2D *) vbo->map(size); LeafNode nodes[LeafCount]; setupLeafNodes(nodes, quads, data); for (int i = 0, v = 0; i < LeafCount; i++) { if (quads[i].isEmpty() || !nodes[i].texture) continue; nodes[i].firstVertex = v; nodes[i].vertexCount = quads[i].count() * verticesPerQuad; const QMatrix4x4 matrix = nodes[i].texture->matrix(nodes[i].coordinateType); quads[i].makeInterleavedArrays(primitiveType, &map[v], matrix); v += quads[i].count() * verticesPerQuad; } vbo->unmap(); vbo->bindArrays(); // Make sure the blend function is set up correctly in case we will be doing blending glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); float opacity = -1.0; for (int i = 0; i < LeafCount; i++) { if (nodes[i].vertexCount == 0) continue; setBlendEnabled(nodes[i].hasAlpha || nodes[i].opacity < 1.0); if (opacity != nodes[i].opacity) { shader->setUniform(GLShader::ModulationConstant, modulate(nodes[i].opacity, data.brightness())); opacity = nodes[i].opacity; } nodes[i].texture->setFilter(filter); nodes[i].texture->setWrapMode(GL_CLAMP_TO_EDGE); nodes[i].texture->bind(); vbo->draw(region, primitiveType, nodes[i].firstVertex, nodes[i].vertexCount, m_hardwareClipping); } vbo->unbindArrays(); setBlendEnabled(false); // render sub-surfaces const auto &children = windowPixmap()->children(); windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } renderSubSurface(shader, modelViewProjection, windowMatrix, static_cast(pixmap)); } if (!data.shader) ShaderManager::instance()->popShader(); endRenderWindow(); } //**************************************** // OpenGLWindowPixmap //**************************************** OpenGLWindowPixmap::OpenGLWindowPixmap(Scene::Window *window, SceneOpenGL* scene) : WindowPixmap(window) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::OpenGLWindowPixmap(const QPointer &subSurface, WindowPixmap *parent, SceneOpenGL *scene) : WindowPixmap(subSurface, parent) , m_texture(scene->createTexture()) , m_scene(scene) { } OpenGLWindowPixmap::~OpenGLWindowPixmap() { } bool OpenGLWindowPixmap::bind() { if (!m_texture->isNull()) { // always call updateBuffer to get the sub-surface tree updated if (subSurface().isNull() && !toplevel()->damage().isEmpty()) { updateBuffer(); } auto s = surface(); if (s && !s->trackedDamage().isEmpty()) { m_texture->updateFromPixmap(this); // mipmaps need to be updated m_texture->setDirty(); } if (subSurface().isNull()) { toplevel()->resetDamage(); } // also bind all children for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } return true; } // also bind all children, needs to be done before checking isValid // as there might be valid children to render, see https://bugreports.qt.io/browse/QTBUG-52192 if (subSurface().isNull()) { updateBuffer(); } for (auto it = children().constBegin(); it != children().constEnd(); ++it) { static_cast(*it)->bind(); } if (!isValid()) { return false; } bool success = m_texture->load(this); if (success) { if (subSurface().isNull()) { toplevel()->resetDamage(); } } else qCDebug(KWIN_CORE) << "Failed to bind window"; return success; } WindowPixmap *OpenGLWindowPixmap::createChild(const QPointer &subSurface) { return new OpenGLWindowPixmap(subSurface, this, m_scene); } //**************************************** // SceneOpenGL::EffectFrame //**************************************** GLTexture* SceneOpenGL::EffectFrame::m_unstyledTexture = NULL; QPixmap* SceneOpenGL::EffectFrame::m_unstyledPixmap = NULL; SceneOpenGL::EffectFrame::EffectFrame(EffectFrameImpl* frame, SceneOpenGL *scene) : Scene::EffectFrame(frame) , m_texture(NULL) , m_textTexture(NULL) , m_oldTextTexture(NULL) , m_textPixmap(NULL) , m_iconTexture(NULL) , m_oldIconTexture(NULL) , m_selectionTexture(NULL) , m_unstyledVBO(NULL) , m_scene(scene) { if (m_effectFrame->style() == EffectFrameUnstyled && !m_unstyledTexture) { updateUnstyledTexture(); } } SceneOpenGL::EffectFrame::~EffectFrame() { delete m_texture; delete m_textTexture; delete m_textPixmap; delete m_oldTextTexture; delete m_iconTexture; delete m_oldIconTexture; delete m_selectionTexture; delete m_unstyledVBO; } void SceneOpenGL::EffectFrame::free() { glFlush(); delete m_texture; m_texture = NULL; delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; delete m_iconTexture; m_iconTexture = NULL; delete m_selectionTexture; m_selectionTexture = NULL; delete m_unstyledVBO; m_unstyledVBO = NULL; delete m_oldIconTexture; m_oldIconTexture = NULL; delete m_oldTextTexture; m_oldTextTexture = NULL; } void SceneOpenGL::EffectFrame::freeIconFrame() { delete m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::freeTextFrame() { delete m_textTexture; m_textTexture = NULL; delete m_textPixmap; m_textPixmap = NULL; } void SceneOpenGL::EffectFrame::freeSelection() { delete m_selectionTexture; m_selectionTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeIcon() { delete m_oldIconTexture; m_oldIconTexture = m_iconTexture; m_iconTexture = NULL; } void SceneOpenGL::EffectFrame::crossFadeText() { delete m_oldTextTexture; m_oldTextTexture = m_textTexture; m_textTexture = NULL; } void SceneOpenGL::EffectFrame::render(QRegion region, double opacity, double frameOpacity) { if (m_effectFrame->geometry().isEmpty()) return; // Nothing to display region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL GLShader* shader = m_effectFrame->shader(); if (!shader) { shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::Modulate); } else if (shader) { ShaderManager::instance()->pushShader(shader); } if (shader) { shader->setUniform(GLShader::ModulationConstant, QVector4D(1.0, 1.0, 1.0, 1.0)); shader->setUniform(GLShader::Saturation, 1.0f); } const QMatrix4x4 projection = m_scene->projectionMatrix(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { if (!m_unstyledVBO) { m_unstyledVBO = new GLVertexBuffer(GLVertexBuffer::Static); QRect area = m_effectFrame->geometry(); area.moveTo(0, 0); area.adjust(-5, -5, 5, 5); const int roundness = 5; QVector verts, texCoords; verts.reserve(84); texCoords.reserve(84); // top left verts << area.left() << area.top(); texCoords << 0.0f << 0.0f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; // top verts << area.left() + roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.left() + roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; // top right verts << area.right() - roundness << area.top(); texCoords << 0.5f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; verts << area.right() - roundness << area.top() + roundness; texCoords << 0.5f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top(); texCoords << 1.0f << 0.0f; // bottom left verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.left() << area.bottom(); texCoords << 0.0f << 1.0f; verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom verts << area.left() + roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.left() + roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; // bottom right verts << area.right() - roundness << area.bottom() - roundness; texCoords << 0.5f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() - roundness << area.bottom(); texCoords << 0.5f << 1.0f; verts << area.right() << area.bottom(); texCoords << 1.0f << 1.0f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; // center verts << area.left() << area.top() + roundness; texCoords << 0.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; verts << area.left() << area.bottom() - roundness; texCoords << 0.0f << 0.5f; verts << area.right() << area.bottom() - roundness; texCoords << 1.0f << 0.5f; verts << area.right() << area.top() + roundness; texCoords << 1.0f << 0.5f; m_unstyledVBO->setData(verts.count() / 2, 2, verts.data(), texCoords.data()); } if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_unstyledTexture->bind(); const QPoint pt = m_effectFrame->geometry().topLeft(); QMatrix4x4 mvp(projection); mvp.translate(pt.x(), pt.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_unstyledVBO->render(region, GL_TRIANGLES); m_unstyledTexture->unbind(); } else if (m_effectFrame->style() == EffectFrameStyled) { if (!m_texture) // Lazy creation updateTexture(); if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_texture->bind(); qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry const QRect rect = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); QMatrix4x4 mvp(projection); mvp.translate(rect.x(), rect.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); m_texture->render(region, rect); m_texture->unbind(); } if (!m_effectFrame->selection().isNull()) { if (!m_selectionTexture) { // Lazy creation QPixmap pixmap = m_effectFrame->selectionFrame().framePixmap(); if (!pixmap.isNull()) m_selectionTexture = new GLTexture(pixmap); } if (m_selectionTexture) { if (shader) { const float a = opacity * frameOpacity; shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->selection().x(), m_effectFrame->selection().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); m_selectionTexture->bind(); m_selectionTexture->render(region, m_effectFrame->selection()); m_selectionTexture->unbind(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); QMatrix4x4 mvp(projection); mvp.translate(topLeft.x(), topLeft.y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldIconTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldIconTexture->bind(); m_oldIconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_oldIconTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_iconTexture) { // lazy creation m_iconTexture = new GLTexture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } m_iconTexture->bind(); m_iconTexture->render(region, QRect(topLeft, m_effectFrame->iconSize())); m_iconTexture->unbind(); } // Render text if (!m_effectFrame->text().isEmpty()) { QMatrix4x4 mvp(projection); mvp.translate(m_effectFrame->geometry().x(), m_effectFrame->geometry().y()); shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp); if (m_effectFrame->isCrossFade() && m_oldTextTexture) { if (shader) { const float a = opacity * (1.0 - m_effectFrame->crossFadeProgress()); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } m_oldTextTexture->bind(); m_oldTextTexture->render(region, m_effectFrame->geometry()); m_oldTextTexture->unbind(); if (shader) { const float a = opacity * m_effectFrame->crossFadeProgress(); shader->setUniform(GLShader::ModulationConstant, QVector4D(a, a, a, a)); } } else { if (shader) { const QVector4D constant(opacity, opacity, opacity, opacity); shader->setUniform(GLShader::ModulationConstant, constant); } } if (!m_textTexture) // Lazy creation updateTextTexture(); m_textTexture->bind(); m_textTexture->render(region, m_effectFrame->geometry()); m_textTexture->unbind(); } if (shader) { ShaderManager::instance()->popShader(); } glDisable(GL_BLEND); } void SceneOpenGL::EffectFrame::updateTexture() { delete m_texture; m_texture = 0L; if (m_effectFrame->style() == EffectFrameStyled) { QPixmap pixmap = m_effectFrame->frame().framePixmap(); m_texture = new GLTexture(pixmap); } } void SceneOpenGL::EffectFrame::updateTextTexture() { delete m_textTexture; m_textTexture = 0L; delete m_textPixmap; m_textPixmap = 0L; if (m_effectFrame->text().isEmpty()) return; // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) rect.setLeft(m_effectFrame->iconSize().width()); // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->font()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } m_textPixmap = new QPixmap(m_effectFrame->geometry().size()); m_textPixmap->fill(Qt::transparent); QPainter p(m_textPixmap); p.setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) p.setPen(m_effectFrame->styledTextColor()); else // TODO: What about no frame? Custom color setting required p.setPen(Qt::white); p.drawText(rect, m_effectFrame->alignment(), text); p.end(); m_textTexture = new GLTexture(*m_textPixmap); } void SceneOpenGL::EffectFrame::updateUnstyledTexture() { delete m_unstyledTexture; m_unstyledTexture = 0L; delete m_unstyledPixmap; m_unstyledPixmap = 0L; // Based off circle() from kwinxrenderutils.cpp #define CS 8 m_unstyledPixmap = new QPixmap(2 * CS, 2 * CS); m_unstyledPixmap->fill(Qt::transparent); QPainter p(m_unstyledPixmap); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(Qt::black); p.drawEllipse(m_unstyledPixmap->rect()); p.end(); #undef CS m_unstyledTexture = new GLTexture(*m_unstyledPixmap); } void SceneOpenGL::EffectFrame::cleanup() { delete m_unstyledTexture; m_unstyledTexture = NULL; delete m_unstyledPixmap; m_unstyledPixmap = NULL; } //**************************************** // SceneOpenGL::Shadow //**************************************** class DecorationShadowTextureCache { public: ~DecorationShadowTextureCache(); DecorationShadowTextureCache(const DecorationShadowTextureCache&) = delete; static DecorationShadowTextureCache &instance(); void unregister(SceneOpenGLShadow *shadow); QSharedPointer getTexture(SceneOpenGLShadow *shadow); private: DecorationShadowTextureCache() = default; struct Data { QSharedPointer texture; QVector shadows; }; QHash m_cache; }; DecorationShadowTextureCache &DecorationShadowTextureCache::instance() { static DecorationShadowTextureCache s_instance; return s_instance; } DecorationShadowTextureCache::~DecorationShadowTextureCache() { Q_ASSERT(m_cache.isEmpty()); } void DecorationShadowTextureCache::unregister(SceneOpenGLShadow *shadow) { auto it = m_cache.begin(); while (it != m_cache.end()) { auto &d = it.value(); // check whether the Vector of Shadows contains our shadow and remove all of them auto glIt = d.shadows.begin(); while (glIt != d.shadows.end()) { if (*glIt == shadow) { glIt = d.shadows.erase(glIt); } else { glIt++; } } // if there are no shadows any more we can erase the cache entry if (d.shadows.isEmpty()) { it = m_cache.erase(it); } else { it++; } } } QSharedPointer DecorationShadowTextureCache::getTexture(SceneOpenGLShadow *shadow) { Q_ASSERT(shadow->hasDecorationShadow()); unregister(shadow); const auto &decoShadow = shadow->decorationShadow(); Q_ASSERT(!decoShadow.isNull()); auto it = m_cache.find(decoShadow.data()); if (it != m_cache.end()) { Q_ASSERT(!it.value().shadows.contains(shadow)); it.value().shadows << shadow; return it.value().texture; } Data d; d.shadows << shadow; d.texture = QSharedPointer::create(shadow->decorationShadowImage()); m_cache.insert(decoShadow.data(), d); return d.texture; } SceneOpenGLShadow::SceneOpenGLShadow(Toplevel *toplevel) : Shadow(toplevel) { } SceneOpenGLShadow::~SceneOpenGLShadow() { if (effects) { effects->makeOpenGLContextCurrent(); DecorationShadowTextureCache::instance().unregister(this); m_texture.reset(); } } void SceneOpenGLShadow::buildQuads() { // prepare window quads m_shadowQuads.clear(); const QSizeF top(elementSize(ShadowElementTop)); const QSizeF topRight(elementSize(ShadowElementTopRight)); const QSizeF right(elementSize(ShadowElementRight)); const QSizeF bottomRight(elementSize(ShadowElementBottomRight)); const QSizeF bottom(elementSize(ShadowElementBottom)); const QSizeF bottomLeft(elementSize(ShadowElementBottomLeft)); const QSizeF left(elementSize(ShadowElementLeft)); const QSizeF topLeft(elementSize(ShadowElementTopLeft)); if ((left.width() - leftOffset() > topLevel()->width()) || (right.width() - rightOffset() > topLevel()->width()) || (top.height() - topOffset() > topLevel()->height()) || (bottom.height() - bottomOffset() > topLevel()->height())) { // if our shadow is bigger than the window, we don't render the shadow setShadowRegion(QRegion()); return; } const QRectF outerRect(QPointF(-leftOffset(), -topOffset()), QPointF(topLevel()->width() + rightOffset(), topLevel()->height() + bottomOffset())); const qreal width = topLeft.width() + top.width() + topRight.width(); const qreal height = topLeft.height() + left.height() + bottomLeft.height(); qreal tx1(0.0), tx2(0.0), ty1(0.0), ty2(0.0); tx2 = topLeft.width()/width; ty2 = topLeft.height()/height; WindowQuad topLeftQuad(WindowQuadShadow); topLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y(), tx1, ty1); topLeftQuad[ 1 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx2, ty1); topLeftQuad[ 2 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + topLeft.height(), tx2, ty2); topLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty2); m_shadowQuads.append(topLeftQuad); tx1 = tx2; tx2 = (topLeft.width() + top.width())/width; ty2 = top.height()/height; WindowQuad topQuad(WindowQuadShadow); topQuad[ 0 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y(), tx1, ty1); topQuad[ 1 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx2, ty1); topQuad[ 2 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + top.height(),tx2, ty2); topQuad[ 3 ] = WindowVertex(outerRect.x() + topLeft.width(), outerRect.y() + top.height(), tx1, ty2); m_shadowQuads.append(topQuad); tx1 = tx2; tx2 = 1.0; ty2 = topRight.height()/height; WindowQuad topRightQuad(WindowQuadShadow); topRightQuad[ 0 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y(), tx1, ty1); topRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y(), tx2, ty1); topRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty2); topRightQuad[ 3 ] = WindowVertex(outerRect.right() - topRight.width(), outerRect.y() + topRight.height(), tx1, ty2); m_shadowQuads.append(topRightQuad); tx1 = (width - right.width())/width; ty1 = topRight.height()/height; ty2 = (topRight.height() + right.height())/height; WindowQuad rightQuad(WindowQuadShadow); rightQuad[ 0 ] = WindowVertex(outerRect.right() - right.width(), outerRect.y() + topRight.height(), tx1, ty1); rightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.y() + topRight.height(), tx2, ty1); rightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty2); rightQuad[ 3 ] = WindowVertex(outerRect.right() - right.width(), outerRect.bottom() - bottomRight.height(), tx1, ty2); m_shadowQuads.append(rightQuad); tx1 = (width - bottomRight.width())/width; ty1 = ty2; ty2 = 1.0; WindowQuad bottomRightQuad(WindowQuadShadow); bottomRightQuad[ 0 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottomRight.height(), tx1, ty1); bottomRightQuad[ 1 ] = WindowVertex(outerRect.right(), outerRect.bottom() - bottomRight.height(), tx2, ty1); bottomRightQuad[ 2 ] = WindowVertex(outerRect.right(), outerRect.bottom(), tx2, ty2); bottomRightQuad[ 3 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomRightQuad); tx2 = tx1; tx1 = bottomLeft.width()/width; ty1 = (height - bottom.height())/height; WindowQuad bottomQuad(WindowQuadShadow); bottomQuad[ 0 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottom.height(), tx1, ty1); bottomQuad[ 1 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom() - bottom.height(), tx2, ty1); bottomQuad[ 2 ] = WindowVertex(outerRect.right() - bottomRight.width(), outerRect.bottom(), tx2, ty2); bottomQuad[ 3 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomQuad); tx1 = 0.0; tx2 = bottomLeft.width()/width; ty1 = (height - bottomLeft.height())/height; WindowQuad bottomLeftQuad(WindowQuadShadow); bottomLeftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty1); bottomLeftQuad[ 1 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty1); bottomLeftQuad[ 2 ] = WindowVertex(outerRect.x() + bottomLeft.width(), outerRect.bottom(), tx2, ty2); bottomLeftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom(), tx1, ty2); m_shadowQuads.append(bottomLeftQuad); tx2 = left.width()/width; ty2 = ty1; ty1 = topLeft.height()/height; WindowQuad leftQuad(WindowQuadShadow); leftQuad[ 0 ] = WindowVertex(outerRect.x(), outerRect.y() + topLeft.height(), tx1, ty1); leftQuad[ 1 ] = WindowVertex(outerRect.x() + left.width(), outerRect.y() + topLeft.height(), tx2, ty1); leftQuad[ 2 ] = WindowVertex(outerRect.x() + left.width(), outerRect.bottom() - bottomLeft.height(), tx2, ty2); leftQuad[ 3 ] = WindowVertex(outerRect.x(), outerRect.bottom() - bottomLeft.height(), tx1, ty2); m_shadowQuads.append(leftQuad); } bool SceneOpenGLShadow::prepareBackend() { if (hasDecorationShadow()) { // simplifies a lot by going directly to effects->makeOpenGLContextCurrent(); m_texture = DecorationShadowTextureCache::instance().getTexture(this); return true; } const QSize top(shadowPixmap(ShadowElementTop).size()); const QSize topRight(shadowPixmap(ShadowElementTopRight).size()); const QSize right(shadowPixmap(ShadowElementRight).size()); const QSize bottom(shadowPixmap(ShadowElementBottom).size()); const QSize bottomLeft(shadowPixmap(ShadowElementBottomLeft).size()); const QSize left(shadowPixmap(ShadowElementLeft).size()); const QSize topLeft(shadowPixmap(ShadowElementTopLeft).size()); const int width = topLeft.width() + top.width() + topRight.width(); const int height = topLeft.height() + left.height() + bottomLeft.height(); QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter p; p.begin(&image); p.drawPixmap(0, 0, shadowPixmap(ShadowElementTopLeft)); p.drawPixmap(topLeft.width(), 0, shadowPixmap(ShadowElementTop)); p.drawPixmap(topLeft.width() + top.width(), 0, shadowPixmap(ShadowElementTopRight)); p.drawPixmap(0, topLeft.height(), shadowPixmap(ShadowElementLeft)); p.drawPixmap(width - right.width(), topRight.height(), shadowPixmap(ShadowElementRight)); p.drawPixmap(0, topLeft.height() + left.height(), shadowPixmap(ShadowElementBottomLeft)); p.drawPixmap(bottomLeft.width(), height - bottom.height(), shadowPixmap(ShadowElementBottom)); p.drawPixmap(bottomLeft.width() + bottom.width(), topRight.height() + right.height(), shadowPixmap(ShadowElementBottomRight)); p.end(); // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format if (!GLPlatform::instance()->isGLES() && GLTexture::supportsSwizzle() && GLTexture::supportsFormatRG()) { QImage alphaImage(image.size(), QImage::Format_Indexed8); // Change to Format_Alpha8 w/ Qt 5.5 bool alphaOnly = true; for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) { const uint32_t * const src = reinterpret_cast(image.scanLine(y)); uint8_t * const dst = reinterpret_cast(alphaImage.scanLine(y)); for (ptrdiff_t x = 0; x < image.width(); x++) { if (src[x] & 0x00ffffff) alphaOnly = false; dst[x] = qAlpha(src[x]); } } if (alphaOnly) { image = alphaImage; } } effects->makeOpenGLContextCurrent(); m_texture = QSharedPointer::create(image); if (m_texture->internalFormat() == GL_R8) { // Swizzle red to alpha and all other channels to zero m_texture->bind(); m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED); } return true; } SwapProfiler::SwapProfiler() { init(); } void SwapProfiler::init() { m_time = 2 * 1000*1000; // we start with a long time mean of 2ms ... m_counter = 0; } void SwapProfiler::begin() { m_timer.start(); } char SwapProfiler::end() { // .. and blend in actual values. // this way we prevent extremes from killing our long time mean m_time = (10*m_time + m_timer.nsecsElapsed())/11; if (++m_counter > 500) { const bool blocks = m_time > 1000 * 1000; // 1ms, i get ~250µs and ~7ms w/o triple buffering... qCDebug(KWIN_CORE) << "Triple buffering detection:" << QString(blocks ? QStringLiteral("NOT available") : QStringLiteral("Available")) << " - Mean block time:" << m_time/(1000.0*1000.0) << "ms"; return blocks ? 'd' : 't'; } return 0; } SceneOpenGLDecorationRenderer::SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) , m_texture() { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneOpenGLDecorationRenderer::~SceneOpenGLDecorationRenderer() = default; // Rotates the given source rect 90° counter-clockwise, // and flips it vertically static QImage rotate(const QImage &srcImage, const QRect &srcRect) { QImage image(srcRect.height(), srcRect.width(), srcImage.format()); const uint32_t *src = reinterpret_cast(srcImage.bits()); uint32_t *dst = reinterpret_cast(image.bits()); for (int x = 0; x < image.width(); x++) { const uint32_t *s = src + (srcRect.y() + x) * srcImage.width() + srcRect.x(); uint32_t *d = dst + x; for (int y = 0; y < image.height(); y++) { *d = s[y]; d += image.width(); } } return image; } void SceneOpenGLDecorationRenderer::render() { const QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizeTexture(); resetImageSizesDirty(); } QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); const QRect geometry = scheduled.boundingRect(); auto renderPart = [this](const QRect &geo, const QRect &partRect, const QPoint &offset, bool rotated = false) { if (geo.isNull()) { return; } QImage image = renderToImage(geo); if (rotated) { // TODO: get this done directly when rendering to the image image = rotate(image, QRect(geo.topLeft() - partRect.topLeft(), geo.size())); } m_texture->update(image, geo.topLeft() - partRect.topLeft() + offset); }; renderPart(left.intersected(geometry), left, QPoint(0, top.height() + bottom.height() + 2), true); renderPart(top.intersected(geometry), top, QPoint(0, 0)); renderPart(right.intersected(geometry), right, QPoint(0, top.height() + bottom.height() + left.width() + 3), true); renderPart(bottom.intersected(geometry), bottom, QPoint(0, top.height() + 1)); } static int align(int value, int align) { return (value + align - 1) & ~(align - 1); } void SceneOpenGLDecorationRenderer::resizeTexture() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); QSize size; size.rwidth() = qMax(qMax(top.width(), bottom.width()), qMax(left.height(), right.height())); size.rheight() = top.height() + bottom.height() + left.width() + right.width() + 3; size.rwidth() = align(size.width(), 128); if (m_texture && m_texture->size() == size) return; if (!size.isEmpty()) { m_texture.reset(new GLTexture(GL_RGBA8, size.width(), size.height())); m_texture->setYInverted(true); m_texture->setWrapMode(GL_CLAMP_TO_EDGE); m_texture->clear(); } else { m_texture.reset(); } } void SceneOpenGLDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } } // namespace diff --git a/scene_qpainter.cpp b/scene_qpainter.cpp index 9bcf673be..51cdae723 100644 --- a/scene_qpainter.cpp +++ b/scene_qpainter.cpp @@ -1,663 +1,663 @@ /******************************************************************** 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 "scene_qpainter.h" // KWin #include "client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effects.h" #include "main.h" #include "screens.h" #include "toplevel.h" -#include "abstract_backend.h" +#include "platform.h" #include "wayland_server.h" #include #include #include #include "decorations/decoratedclient.h" // Qt #include #include #include namespace KWin { //**************************************** // QPainterBackend //**************************************** QPainterBackend::QPainterBackend() : m_failed(false) { } QPainterBackend::~QPainterBackend() { } OverlayWindow* QPainterBackend::overlayWindow() { return NULL; } void QPainterBackend::showOverlay() { } void QPainterBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) } void QPainterBackend::setFailed(const QString &reason) { qCWarning(KWIN_CORE) << "Creating the XRender backend failed: " << reason; m_failed = true; } void QPainterBackend::renderCursor(QPainter *painter) { Q_UNUSED(painter) } bool QPainterBackend::perScreenRendering() const { return false; } QImage *QPainterBackend::bufferForScreen(int screenId) { Q_UNUSED(screenId) return buffer(); } //**************************************** // SceneQPainter //**************************************** SceneQPainter *SceneQPainter::createScene(QObject *parent) { QScopedPointer backend; if (kwinApp()->shouldUseWaylandForCompositing()) { backend.reset(kwinApp()->platform()->createQPainterBackend()); if (backend.isNull()) { return nullptr; } if (backend->isFailed()) { return NULL; } return new SceneQPainter(backend.take(), parent); } return NULL; } SceneQPainter::SceneQPainter(QPainterBackend *backend, QObject *parent) : Scene(parent) , m_backend(backend) , m_painter(new QPainter()) { } SceneQPainter::~SceneQPainter() { } CompositingType SceneQPainter::compositingType() const { return QPainterCompositing; } bool SceneQPainter::initFailed() const { return false; } void SceneQPainter::paintGenericScreen(int mask, ScreenPaintData data) { m_painter->save(); m_painter->translate(data.xTranslation(), data.yTranslation()); m_painter->scale(data.xScale(), data.yScale()); Scene::paintGenericScreen(mask, data); m_painter->restore(); } qint64 SceneQPainter::paint(QRegion damage, ToplevelList toplevels) { QElapsedTimer renderTimer; renderTimer.start(); createStackingOrder(toplevels); int mask = 0; m_backend->prepareRenderingFrame(); if (m_backend->perScreenRendering()) { const bool needsFullRepaint = m_backend->needsFullRepaint(); if (needsFullRepaint) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = screens()->geometry(); } QRegion overallUpdate; for (int i = 0; i < screens()->count(); ++i) { const QRect geometry = screens()->geometry(i); QImage *buffer = m_backend->bufferForScreen(i); if (!buffer || buffer->isNull()) { continue; } m_painter->begin(buffer); m_painter->save(); m_painter->setWindow(geometry); QRegion updateRegion, validRegion; paintScreen(&mask, damage.intersected(geometry), QRegion(), &updateRegion, &validRegion); overallUpdate = overallUpdate.united(updateRegion); m_painter->restore(); m_painter->end(); } m_backend->showOverlay(); m_backend->present(mask, overallUpdate); } else { m_painter->begin(m_backend->buffer()); if (m_backend->needsFullRepaint()) { mask |= Scene::PAINT_SCREEN_BACKGROUND_FIRST; damage = QRegion(0, 0, displayWidth(), displayHeight()); } QRegion updateRegion, validRegion; paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion); m_backend->renderCursor(m_painter.data()); m_backend->showOverlay(); m_painter->end(); m_backend->present(mask, updateRegion); } // do cleanup clearStackingOrder(); return renderTimer.nsecsElapsed(); } void SceneQPainter::paintBackground(QRegion region) { m_painter->setBrush(Qt::black); m_painter->drawRects(region.rects()); } Scene::Window *SceneQPainter::createWindow(Toplevel *toplevel) { return new SceneQPainter::Window(this, toplevel); } Scene::EffectFrame *SceneQPainter::createEffectFrame(EffectFrameImpl *frame) { return new QPainterEffectFrame(frame, this); } Shadow *SceneQPainter::createShadow(Toplevel *toplevel) { return new SceneQPainterShadow(toplevel); } void SceneQPainter::screenGeometryChanged(const QSize &size) { Scene::screenGeometryChanged(size); m_backend->screenGeometryChanged(size); } //**************************************** // SceneQPainter::Window //**************************************** SceneQPainter::Window::Window(SceneQPainter *scene, Toplevel *c) : Scene::Window(c) , m_scene(scene) { } SceneQPainter::Window::~Window() { discardShape(); } static void paintSubSurface(QPainter *painter, const QPoint &pos, QPainterWindowPixmap *pixmap) { QPoint p = pos; if (!pixmap->subSurface().isNull()) { p += pixmap->subSurface()->position(); } painter->drawImage(p, pixmap->image()); const auto &children = pixmap->children(); for (auto it = children.begin(); it != children.end(); ++it) { auto pixmap = static_cast(*it); if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, p, pixmap); } } void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintData data) { if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED))) region &= toplevel->visibleRect(); if (region.isEmpty()) return; QPainterWindowPixmap *pixmap = windowPixmap(); if (!pixmap || !pixmap->isValid()) { return; } if (!toplevel->damage().isEmpty()) { pixmap->updateBuffer(); toplevel->resetDamage(); } QPainter *scenePainter = m_scene->painter(); QPainter *painter = scenePainter; painter->setClipRegion(region); painter->setClipping(true); painter->save(); painter->translate(x(), y()); if (mask & PAINT_WINDOW_TRANSFORMED) { painter->translate(data.xTranslation(), data.yTranslation()); painter->scale(data.xScale(), data.yScale()); } const bool opaque = qFuzzyCompare(1.0, data.opacity()); QImage tempImage; QPainter tempPainter; if (!opaque) { // need a temp render target which we later on blit to the screen tempImage = QImage(toplevel->visibleRect().size(), QImage::Format_ARGB32_Premultiplied); tempImage.fill(Qt::transparent); tempPainter.begin(&tempImage); tempPainter.save(); tempPainter.translate(toplevel->geometry().topLeft() - toplevel->visibleRect().topLeft()); painter = &tempPainter; } renderShadow(painter); renderWindowDecorations(painter); // render content const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), toplevel->clientSize()); painter->drawImage(toplevel->clientPos(), pixmap->image(), src); // render subsurfaces const auto &children = pixmap->children(); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); } if (!opaque) { tempPainter.restore(); tempPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); QColor translucent(Qt::transparent); translucent.setAlphaF(data.opacity()); tempPainter.fillRect(QRect(QPoint(0, 0), toplevel->visibleRect().size()), translucent); tempPainter.end(); painter = scenePainter; painter->drawImage(toplevel->visibleRect().topLeft() - toplevel->geometry().topLeft(), tempImage); } painter->restore(); painter->setClipRegion(QRegion()); painter->setClipping(false); } void SceneQPainter::Window::renderShadow(QPainter* painter) { if (!toplevel->shadow()) { return; } SceneQPainterShadow *shadow = static_cast(toplevel->shadow()); const QPixmap &topLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopLeft); const QPixmap &top = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTop); const QPixmap &topRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementTopRight); const QPixmap &bottomLeft = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomLeft); const QPixmap &bottom = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottom); const QPixmap &bottomRight = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementBottomRight); const QPixmap &left = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementLeft); const QPixmap &right = shadow->shadowPixmap(SceneQPainterShadow::ShadowElementRight); const int leftOffset = shadow->leftOffset(); const int topOffset = shadow->topOffset(); const int rightOffset = shadow->rightOffset(); const int bottomOffset = shadow->bottomOffset(); // top left painter->drawPixmap(-leftOffset, -topOffset, topLeft); // top right painter->drawPixmap(toplevel->width() - topRight.width() + rightOffset, -topOffset, topRight); // bottom left painter->drawPixmap(-leftOffset, toplevel->height() - bottomLeft.height() + bottomOffset, bottomLeft); // bottom right painter->drawPixmap(toplevel->width() - bottomRight.width() + rightOffset, toplevel->height() - bottomRight.height() + bottomOffset, bottomRight); // top painter->drawPixmap(topLeft.width() - leftOffset, -topOffset, toplevel->width() - topLeft.width() - topRight.width() + leftOffset + rightOffset, top.height(), top); // left painter->drawPixmap(-leftOffset, topLeft.height() - topOffset, left.width(), toplevel->height() - topLeft.height() - bottomLeft.height() + topOffset + bottomOffset, left); // right painter->drawPixmap(toplevel->width() - right.width() + rightOffset, topRight.height() - topOffset, right.width(), toplevel->height() - topRight.height() - bottomRight.height() + topOffset + bottomOffset, right); // bottom painter->drawPixmap(bottomLeft.width() - leftOffset, toplevel->height() - bottom.height() + bottomOffset, toplevel->width() - bottomLeft.width() - bottomRight.width() + leftOffset + rightOffset, bottom.height(), bottom); } void SceneQPainter::Window::renderWindowDecorations(QPainter *painter) { // TODO: custom decoration opacity AbstractClient *client = dynamic_cast(toplevel); Deleted *deleted = dynamic_cast(toplevel); if (!client && !deleted) { return; } bool noBorder = true; const SceneQPainterDecorationRenderer *renderer = nullptr; QRect dtr, dlr, drr, dbr; if (client && !client->noBorder()) { if (client->isDecorated()) { if (SceneQPainterDecorationRenderer *r = static_cast(client->decoratedClient()->renderer())) { r->render(); renderer = r; } } client->layoutDecorationRects(dlr, dtr, drr, dbr); noBorder = false; } else if (deleted && !deleted->noBorder()) { noBorder = false; deleted->layoutDecorationRects(dlr, dtr, drr, dbr); renderer = static_cast(deleted->decorationRenderer()); } if (noBorder || !renderer) { return; } painter->drawImage(dtr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Top)); painter->drawImage(dlr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Left)); painter->drawImage(drr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Right)); painter->drawImage(dbr, renderer->image(SceneQPainterDecorationRenderer::DecorationPart::Bottom)); } WindowPixmap *SceneQPainter::Window::createWindowPixmap() { return new QPainterWindowPixmap(this); } Decoration::Renderer *SceneQPainter::createDecorationRenderer(Decoration::DecoratedClientImpl *impl) { return new SceneQPainterDecorationRenderer(impl); } //**************************************** // QPainterWindowPixmap //**************************************** QPainterWindowPixmap::QPainterWindowPixmap(Scene::Window *window) : WindowPixmap(window) { } QPainterWindowPixmap::QPainterWindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : WindowPixmap(subSurface, parent) { } QPainterWindowPixmap::~QPainterWindowPixmap() { } void QPainterWindowPixmap::create() { if (isValid()) { return; } KWin::WindowPixmap::create(); if (!isValid()) { return; } // performing deep copy, this could probably be improved m_image = buffer()->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } WindowPixmap *QPainterWindowPixmap::createChild(const QPointer &subSurface) { return new QPainterWindowPixmap(subSurface, this); } void QPainterWindowPixmap::updateBuffer() { const auto oldBuffer = buffer(); WindowPixmap::updateBuffer(); const auto &b = buffer(); if (b.isNull()) { m_image = QImage(); return; } if (b == oldBuffer) { return; } // perform deep copy m_image = b->data().copy(); if (auto s = surface()) { s->resetTrackedDamage(); } } QPainterEffectFrame::QPainterEffectFrame(EffectFrameImpl *frame, SceneQPainter *scene) : Scene::EffectFrame(frame) , m_scene(scene) { } QPainterEffectFrame::~QPainterEffectFrame() { } void QPainterEffectFrame::render(QRegion region, double opacity, double frameOpacity) { Q_UNUSED(region) Q_UNUSED(opacity) // TODO: adjust opacity if (m_effectFrame->geometry().isEmpty()) { return; // Nothing to display } QPainter *painter = m_scene->painter(); // Render the actual frame if (m_effectFrame->style() == EffectFrameUnstyled) { painter->save(); painter->setPen(Qt::NoPen); QColor color(Qt::black); color.setAlphaF(frameOpacity); painter->setBrush(color); painter->setRenderHint(QPainter::Antialiasing); painter->drawRoundedRect(m_effectFrame->geometry().adjusted(-5, -5, 5, 5), 5.0, 5.0); painter->restore(); } else if (m_effectFrame->style() == EffectFrameStyled) { qreal left, top, right, bottom; m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom); painter->drawPixmap(geom, m_effectFrame->frame().framePixmap()); } if (!m_effectFrame->selection().isNull()) { painter->drawPixmap(m_effectFrame->selection(), m_effectFrame->selectionFrame().framePixmap()); } // Render icon if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { const QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2); const QRect geom = QRect(topLeft, m_effectFrame->iconSize()); painter->drawPixmap(geom, m_effectFrame->icon().pixmap(m_effectFrame->iconSize())); } // Render text if (!m_effectFrame->text().isEmpty()) { // Determine position on texture to paint text QRect rect(QPoint(0, 0), m_effectFrame->geometry().size()); if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) { rect.setLeft(m_effectFrame->iconSize().width()); } // If static size elide text as required QString text = m_effectFrame->text(); if (m_effectFrame->isStatic()) { QFontMetrics metrics(m_effectFrame->text()); text = metrics.elidedText(text, Qt::ElideRight, rect.width()); } painter->save(); painter->setFont(m_effectFrame->font()); if (m_effectFrame->style() == EffectFrameStyled) { painter->setPen(m_effectFrame->styledTextColor()); } else { // TODO: What about no frame? Custom color setting required painter->setPen(Qt::white); } painter->drawText(rect.translated(m_effectFrame->geometry().topLeft()), m_effectFrame->alignment(), text); painter->restore(); } } //**************************************** // QPainterShadow //**************************************** SceneQPainterShadow::SceneQPainterShadow(Toplevel* toplevel) : Shadow(toplevel) { } SceneQPainterShadow::~SceneQPainterShadow() { } bool SceneQPainterShadow::prepareBackend() { if (hasDecorationShadow()) { // TODO: implement for QPainter return false; } return true; } //**************************************** // QPainterDecorationRenderer //**************************************** SceneQPainterDecorationRenderer::SceneQPainterDecorationRenderer(Decoration::DecoratedClientImpl *client) : Renderer(client) { connect(this, &Renderer::renderScheduled, client->client(), static_cast(&AbstractClient::addRepaint)); } SceneQPainterDecorationRenderer::~SceneQPainterDecorationRenderer() = default; QImage SceneQPainterDecorationRenderer::image(SceneQPainterDecorationRenderer::DecorationPart part) const { Q_ASSERT(part != DecorationPart::Count); return m_images[int(part)]; } void SceneQPainterDecorationRenderer::render() { const QRegion scheduled = getScheduled(); if (scheduled.isEmpty()) { return; } if (areImageSizesDirty()) { resizeImages(); resetImageSizesDirty(); } const QRect top(QPoint(0, 0), m_images[int(DecorationPart::Top)].size()); const QRect left(QPoint(0, top.height()), m_images[int(DecorationPart::Left)].size()); const QRect right(QPoint(top.width() - m_images[int(DecorationPart::Right)].size().width(), top.height()), m_images[int(DecorationPart::Right)].size()); const QRect bottom(QPoint(0, left.y() + left.height()), m_images[int(DecorationPart::Bottom)].size()); const QRect geometry = scheduled.boundingRect(); auto renderPart = [this](const QRect &rect, const QRect &partRect, int index) { if (rect.isEmpty()) { return; } QPainter painter(&m_images[index]); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(partRect); painter.setClipRect(rect); painter.save(); // clear existing part painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(rect, Qt::transparent); painter.restore(); client()->decoration()->paint(&painter, rect); }; renderPart(left.intersected(geometry), left, int(DecorationPart::Left)); renderPart(top.intersected(geometry), top, int(DecorationPart::Top)); renderPart(right.intersected(geometry), right, int(DecorationPart::Right)); renderPart(bottom.intersected(geometry), bottom, int(DecorationPart::Bottom)); } void SceneQPainterDecorationRenderer::resizeImages() { QRect left, top, right, bottom; client()->client()->layoutDecorationRects(left, top, right, bottom); auto checkAndCreate = [this](int index, const QSize &size) { if (m_images[index].size() != size) { m_images[index] = QImage(size, QImage::Format_ARGB32_Premultiplied); m_images[index].fill(Qt::transparent); } }; checkAndCreate(int(DecorationPart::Left), left.size()); checkAndCreate(int(DecorationPart::Right), right.size()); checkAndCreate(int(DecorationPart::Top), top.size()); checkAndCreate(int(DecorationPart::Bottom), bottom.size()); } void SceneQPainterDecorationRenderer::reparent(Deleted *deleted) { render(); Renderer::reparent(deleted); } } // KWin diff --git a/screens.cpp b/screens.cpp index 77243761e..062e52039 100644 --- a/screens.cpp +++ b/screens.cpp @@ -1,249 +1,249 @@ /******************************************************************** 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 "screens.h" #include #include #include "cursor.h" #include "utils.h" #include "settings.h" #include #include #include "screens_xrandr.h" -#include "abstract_backend.h" +#include "platform.h" #include "wayland_server.h" #ifdef KWIN_UNIT_TEST #include #endif namespace KWin { Screens *Screens::s_self = nullptr; Screens *Screens::create(QObject *parent) { Q_ASSERT(!s_self); #ifdef KWIN_UNIT_TEST s_self = new MockScreens(parent); #else if (kwinApp()->shouldUseWaylandForCompositing()) { s_self = kwinApp()->platform()->createScreens(parent); } if (kwinApp()->operationMode() == Application::OperationModeX11) { s_self = new XRandRScreens(parent); } #endif Q_ASSERT(s_self); s_self->init(); return s_self; } Screens::Screens(QObject *parent) : QObject(parent) , m_count(0) , m_current(0) , m_currentFollowsMouse(false) , m_changedTimer(new QTimer(this)) { } Screens::~Screens() { s_self = NULL; } void Screens::init() { m_changedTimer->setSingleShot(true); m_changedTimer->setInterval(100); connect(m_changedTimer, SIGNAL(timeout()), SLOT(updateCount())); connect(m_changedTimer, SIGNAL(timeout()), SIGNAL(changed())); connect(this, &Screens::countChanged, this, &Screens::changed, Qt::QueuedConnection); connect(this, &Screens::changed, this, &Screens::updateSize); connect(this, &Screens::sizeChanged, this, &Screens::geometryChanged); Settings settings; settings.setDefaults(); m_currentFollowsMouse = settings.activeMouseScreen(); } QString Screens::name(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::name(int screen) is a stub, please reimplement it!", metaObject()->className()); return QLatin1String("DUMMY"); } float Screens::refreshRate(int screen) const { Q_UNUSED(screen) qCWarning(KWIN_CORE, "%s::refreshRate(int screen) is a stub, please reimplement it!", metaObject()->className()); return 60.0f; } void Screens::reconfigure() { if (!m_config) { return; } Settings settings(m_config); settings.read(); setCurrentFollowsMouse(settings.activeMouseScreen()); } void Screens::updateSize() { QRect bounding; for (int i = 0; i < count(); ++i) { bounding = bounding.united(geometry(i)); } if (m_boundingSize != bounding.size()) { m_boundingSize = bounding.size(); emit sizeChanged(); } } void Screens::setCount(int count) { if (m_count == count) { return; } const int previous = m_count; m_count = count; emit countChanged(previous, count); } void Screens::setCurrent(int current) { if (m_current == current) { return; } m_current = current; emit currentChanged(); } void Screens::setCurrent(const QPoint &pos) { setCurrent(number(pos)); } void Screens::setCurrent(const AbstractClient *c) { if (!c->isActive()) { return; } if (!c->isOnScreen(m_current)) { setCurrent(c->screen()); } } void Screens::setCurrentFollowsMouse(bool follows) { if (m_currentFollowsMouse == follows) { return; } m_currentFollowsMouse = follows; } int Screens::current() const { if (m_currentFollowsMouse) { return number(Cursor::pos()); } AbstractClient *client = Workspace::self()->activeClient(); if (client && !client->isOnScreen(m_current)) { return client->screen(); } return m_current; } int Screens::intersecting(const QRect &r) const { int cnt = 0; for (int i = 0; i < count(); ++i) { if (geometry(i).intersects(r)) { ++cnt; } } return cnt; } BasicScreens::BasicScreens(Platform *backend, QObject *parent) : Screens(parent) , m_backend(backend) { } BasicScreens::~BasicScreens() = default; void BasicScreens::init() { KWin::Screens::init(); #ifndef KWIN_UNIT_TEST connect(m_backend, &Platform::screenSizeChanged, this, &BasicScreens::startChangedTimer); #endif updateCount(); emit changed(); } QRect BasicScreens::geometry(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen); } return QRect(); } QSize BasicScreens::size(int screen) const { if (screen < m_geometries.count()) { return m_geometries.at(screen).size(); } return QSize(); } void BasicScreens::updateCount() { m_geometries = m_backend->screenGeometries(); setCount(m_geometries.count()); } int BasicScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; for (int i = 0; i < m_geometries.count(); ++i) { const QRect &geo = m_geometries.at(i); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } } // namespace diff --git a/wayland_server.cpp b/wayland_server.cpp index e549dab0d..3798c1de0 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,516 +1,516 @@ /******************************************************************** 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 "wayland_server.h" #include "client.h" -#include "abstract_backend.h" +#include "platform.h" #include "composite.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include #include #include // Server #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // system #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType("KWayland::Server::SurfaceInterface *"); qRegisterMetaType(); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { delete m_internalConnection.registry; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); const auto connections = m_display->connections(); for (auto it = connections.begin(); it != connections.end(); ++it) { (*it)->destroy(); } } void WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWayland::Server::Display(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } m_display->start(); m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, [this] (ShellSurfaceInterface *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } auto client = new ShellClient(surface); if (auto c = Compositor::self()) { connect(client, &Toplevel::needsRepaint, c, &Compositor::scheduleRepaint); } if (client->isInternal()) { m_internalClients << client; } else { m_clients << client; } if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, [this, client] { emit shellClientAdded(client); } ); } } ); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createDataDeviceManager(m_display)->create(); m_display->createIdle(m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } } ); m_qtExtendedSurface = m_display->createQtSurfaceExtension(m_display); m_qtExtendedSurface->create(); connect(m_qtExtendedSurface, &QtSurfaceExtensionInterface::surfaceCreated, [this] (QtExtendedSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installQtExtendedSurface(surface); } } ); m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } } ); m_decorationManager->create(); m_outputManagement = m_display->createOutputManagement(m_display); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, [this](KWayland::Server::OutputConfigurationInterface *config) { kwinApp()->platform()->configurationChangeRequested(config); }); m_display->createSubCompositor(m_display)->create(); } void WaylandServer::initWorkspace() { if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } ScreenLocker::KSldApp::self(); ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); ScreenLocker::KSldApp::self()->initialize(); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, [this] () { m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); } ); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, [this] () { m_screenLockerClientConnection = nullptr; } ); if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); } } void WaylandServer::initOutputs() { if (kwinApp()->platform()->handlesOutputs()) { return; } Screens *s = screens(); Q_ASSERT(s); for (int i = 0; i < s->count(); ++i) { OutputInterface *output = m_display->createOutput(m_display); const QRect &geo = s->geometry(i); output->setGlobalPosition(geo.topLeft()); output->setPhysicalSize(geo.size() / 3.8); output->addMode(geo.size()); output->create(); } } int WaylandServer::createXWaylandConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_xwayland.client = m_display->createClient(sx[0]); m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return sx[1]; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_inputMethodServerConnection = m_display->createClient(sx[0]); return sx[1]; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } void WaylandServer::createInternalConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return; } m_internalConnection.server = m_display->createClient(sx[0]); using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(sx[1]); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, id)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, surface)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(QWindow *w) const { if (!w) { return nullptr; } auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), [w] (const ShellClient *c) { return c->internalWindow() == w; } ); if (it != m_internalClients.constEnd()) { return *it; } return nullptr; } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } }