diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c5c298c4..b18ad319b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,756 +1,756 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(KWIN) set(PROJECT_VERSION "5.17.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.12.0") set(KF5_MIN_VERSION "5.62.0") set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) find_package(ECM 5.38 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 Sensors 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(KDEClangFormat) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0 -DQT_USE_QSTRINGBUILDER -DQT_NO_URL_CAST_FROM_STRING) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) 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 IconThemes IdleTime Init Notifications Package Plasma Wayland WidgetsAddons WindowSystem ) # required frameworks by config modules find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Completion Declarative KCMUtils KIO NewStuff Service TextWidgets 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(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "A QtQuick based components set" PURPOSE "Required at runtime for Virtual desktop KCM and the virtual keyboard" TYPE RUNTIME ) find_package(KDecoration2 5.13.0 CONFIG REQUIRED) find_package(KScreenLocker CONFIG REQUIRED) set_package_properties(KScreenLocker PROPERTIES TYPE REQUIRED PURPOSE "For screenlocker integration in kwin_wayland" ) find_package(Breeze 5.9.0 CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin" ) if (${Breeze_FOUND}) if (${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") 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 "https://github.com/anholt/libepoxy" TYPE REQUIRED PURPOSE "OpenGL dispatch library" ) set(HAVE_DL_LIBRARY FALSE) if (epoxy_HAS_GLX) find_library(DL_LIBRARY dl) if (DL_LIBRARY) set(HAVE_DL_LIBRARY TRUE) endif() endif() 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.7.0) set_package_properties(XKB PROPERTIES TYPE REQUIRED PURPOSE "Required for building KWin with Wayland support" ) find_package(Libinput 1.9) set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.") find_package(UDev) set_package_properties(UDev PROPERTIES - URL "http://www.freedesktop.org/software/systemd/libudev/" + URL "https://www.freedesktop.org/wiki/Software/systemd/" DESCRIPTION "Linux device library." TYPE REQUIRED PURPOSE "Required for input handling on Wayland." ) find_package(Libdrm 2.4.62) set_package_properties(Libdrm PROPERTIES TYPE OPTIONAL PURPOSE "Required for drm output on Wayland.") set(HAVE_DRM FALSE) if (Libdrm_FOUND) set(HAVE_DRM TRUE) endif() find_package(gbm) set_package_properties(gbm PROPERTIES TYPE OPTIONAL PURPOSE "Required for egl output of drm backend.") set(HAVE_GBM FALSE) if (HAVE_DRM AND gbm_FOUND) set(HAVE_GBM TRUE) endif() option(KWIN_BUILD_EGL_STREAM_BACKEND "Enable building of EGLStream based DRM backend" ON) if (HAVE_DRM AND KWIN_BUILD_EGL_STREAM_BACKEND) set(HAVE_EGL_STREAMS 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 "https://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 COMPOSITE CURSOR DAMAGE GLX ICCCM IMAGE KEYSYMS RANDR RENDER SHAPE SHM SYNC XCB XFIXES ) 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(Qt5FontDatabaseSupport REQUIRED) find_package(Qt5ThemeSupport REQUIRED) find_package(Qt5EventDispatcherSupport REQUIRED) find_package(Freetype REQUIRED) set_package_properties(Freetype PROPERTIES DESCRIPTION "A font rendering engine" URL "https://www.freetype.org" TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Fontconfig REQUIRED) set_package_properties(Fontconfig PROPERTIES TYPE REQUIRED PURPOSE "Needed for KWin's QPA plugin." ) find_package(Xwayland) set_package_properties(Xwayland PROPERTIES URL "https://x.org" DESCRIPTION "Xwayland X server" TYPE RUNTIME PURPOSE "Needed for running kwin_wayland" ) find_package(Libcap) set_package_properties(Libcap PROPERTIES TYPE OPTIONAL PURPOSE "Needed for running kwin_wayland with real-time scheduling policy" ) set(HAVE_LIBCAP ${Libcap_FOUND}) include(ECMQMLModules) ecm_find_qmlmodule(QtQuick 2.3) ecm_find_qmlmodule(QtQuick.Controls 1.2) ecm_find_qmlmodule(QtQuick.Layouts 1.3) ecm_find_qmlmodule(QtQuick.VirtualKeyboard 2.1) ecm_find_qmlmodule(QtQuick.Window 2.1) ecm_find_qmlmodule(QtMultimedia 5.0) ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) ecm_find_qmlmodule(org.kde.plasma.core 2.0) ecm_find_qmlmodule(org.kde.plasma.components 2.0) ########### 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) check_symbol_exists(PR_SET_PDEATHSIG "sys/prctl.h" HAVE_PR_SET_PDEATHSIG) check_include_file("sys/procctl.h" HAVE_SYS_PROCCTL_H) check_symbol_exists(PROC_TRACE_CTL "sys/procctl.h" HAVE_PROC_TRACE_CTL) if (HAVE_PR_SET_DUMPABLE OR HAVE_PROC_TRACE_CTL) set(CAN_DISABLE_PTRACE TRUE) endif() add_feature_info("prctl/procctl tracing control" CAN_DISABLE_PTRACE "Required for disallowing ptrace on kwin_wayland process") check_include_file("sys/sysmacros.h" HAVE_SYS_SYSMACROS_H) check_include_file("linux/vt.h" HAVE_LINUX_VT_H) add_feature_info("linux/vt.h" HAVE_LINUX_VT_H "Required for virtual terminal support under wayland") check_include_file("linux/fb.h" HAVE_LINUX_FB_H) add_feature_info("linux/fb.h" HAVE_LINUX_FB_H "Required for the fbdev backend") check_symbol_exists(SCHED_RESET_ON_FORK "sched.h" HAVE_SCHED_RESET_ON_FORK) add_feature_info("SCHED_RESET_ON_FORK" HAVE_SCHED_RESET_ON_FORK "Required for running kwin_wayland with real-time scheduling") 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 ${CMAKE_CURRENT_SOURCE_DIR}/platformsupport ) add_subdirectory(libkwineffects) if (KWIN_BUILD_KCMS) add_subdirectory(kcmkwin) endif() add_subdirectory(data) add_subdirectory(effects) add_subdirectory(scripts) add_subdirectory(tabbox) add_subdirectory(scripting) add_subdirectory(helpers) ########### next target ############### set(kwin_KDEINIT_SRCS abstract_client.cpp abstract_opengl_context_attribute_builder.cpp abstract_output.cpp abstract_wayland_output.cpp activation.cpp appmenu.cpp atoms.cpp client_machine.cpp colorcorrection/colorcorrectdbusinterface.cpp colorcorrection/manager.cpp colorcorrection/suncalc.cpp composite.cpp cursor.cpp dbusinterface.cpp debug_console.cpp decorations/decoratedclient.cpp decorations/decorationbridge.cpp decorations/decorationpalette.cpp decorations/decorationrenderer.cpp decorations/decorations_logging.cpp decorations/settings.cpp deleted.cpp effectloader.cpp effects.cpp egl_context_attribute_builder.cpp events.cpp focuschain.cpp geometry.cpp geometrytip.cpp gestures.cpp globalshortcuts.cpp group.cpp idle_inhibition.cpp input.cpp input_event.cpp input_event_spy.cpp internal_client.cpp keyboard_input.cpp keyboard_layout.cpp keyboard_layout_switching.cpp keyboard_repeat.cpp killwindow.cpp layers.cpp libinput/connection.cpp libinput/context.cpp libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp linux_dmabuf.cpp logind.cpp main.cpp manage.cpp modifier_only_shortcuts.cpp moving_client_x11_filter.cpp netinfo.cpp onscreennotification.cpp options.cpp orientation_sensor.cpp osd.cpp outline.cpp outputscreens.cpp overlaywindow.cpp placement.cpp platform.cpp pointer_input.cpp popup_input_filter.cpp rootinfo_filter.cpp rules.cpp scene.cpp screenedge.cpp screenlockerwatcher.cpp screens.cpp scripting/dbuscall.cpp scripting/meta.cpp scripting/screenedgeitem.cpp scripting/scriptedeffect.cpp scripting/scripting.cpp scripting/scripting_logging.cpp scripting/scripting_model.cpp scripting/scriptingutils.cpp scripting/timer.cpp scripting/workspace_wrapper.cpp shadow.cpp sm.cpp thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp touch_input.cpp udev.cpp unmanaged.cpp useractions.cpp utils.cpp virtualdesktops.cpp virtualdesktopsdbustypes.cpp virtualkeyboard.cpp virtualkeyboard_dbus.cpp was_user_interaction_x11_filter.cpp wayland_cursor_theme.cpp wayland_server.cpp window_property_notify_x11_filter.cpp workspace.cpp x11client.cpp x11eventfilter.cpp xcbutils.cpp xdgshellclient.cpp xkb.cpp xwl/xwayland_interface.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_KDEINIT_SRCS HEADER colorcorrect_logging.h IDENTIFIER KWIN_COLORCORRECTION CATEGORY_NAME kwin_colorcorrection DEFAULT_SEVERITY Critical ) if (KWIN_BUILD_TABBOX) include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} tabbox/clientmodel.cpp tabbox/desktopchain.cpp tabbox/desktopmodel.cpp tabbox/switcheritem.cpp tabbox/tabbox.cpp tabbox/tabbox_logging.cpp tabbox/tabboxconfig.cpp tabbox/tabboxhandler.cpp tabbox/x11_filter.cpp ) endif() if (KWIN_BUILD_ACTIVITIES) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} activities.cpp ) endif() if (HAVE_LINUX_VT_H) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} virtual_terminal.cpp ) endif() kconfig_add_kcfg_files(kwin_KDEINIT_SRCS settings.kcfgc) kconfig_add_kcfg_files(kwin_KDEINIT_SRCS colorcorrection/colorcorrect_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 org.kde.kwin.ColorCorrect.xml colorcorrection/colorcorrectdbusinterface.h KWin::ColorCorrect::ColorCorrectDBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/kf5_org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface) qt5_add_dbus_interface(kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface) ki18n_wrap_ui(kwin_KDEINIT_SRCS debug_console.ui shortcutdialog.ui ) ########### target link libraries ############### set(kwin_OWN_LIBS kwineffects kwin4_effect_builtins ) set(kwin_QT_LIBS Qt5::Concurrent Qt5::DBus Qt5::Quick Qt5::Script Qt5::Sensors ) set(kwin_KDE_LIBS KF5::ConfigCore KF5::ConfigWidgets KF5::CoreAddons KF5::GlobalAccel KF5::GlobalAccelPrivate KF5::I18n KF5::Notifications KF5::Package KF5::Plasma KF5::QuickAddons KF5::WindowSystem KDecoration2::KDecoration KDecoration2::KDecoration2Private PW::KScreenLocker ) set(kwin_XLIB_LIBS ${X11_ICE_LIB} ${X11_SM_LIB} ${X11_X11_LIB} ) set(kwin_XCB_LIBS XCB::COMPOSITE XCB::DAMAGE XCB::GLX XCB::ICCCM XCB::KEYSYMS XCB::RANDR XCB::RENDER XCB::SHAPE XCB::SHM XCB::SYNC XCB::XCB XCB::XFIXES ) set(kwin_WAYLAND_LIBS KF5::WaylandClient KF5::WaylandServer Wayland::Cursor XKB::XKB ${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} ${UDEV_LIBS} Libinput::Libinput ) 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}) kf5_add_kdeinit_executable(kwin_x11 main_x11.cpp) target_link_libraries(kdeinit_kwin_x11 kwin KF5::Crash Qt5::X11Extras) 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}) set(kwin_XWAYLAND_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/xwl/clipboard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/databridge.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/dnd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_wl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/drag_x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/selection_source.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/transfer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/xwl/xwayland.cpp ) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(kwin_XWAYLAND_SRCS HEADER xwayland_logging.h IDENTIFIER KWIN_XWL CATEGORY_NAME kwin_xwl DEFAULT_SEVERITY Critical ) set(kwin_WAYLAND_SRCS main_wayland.cpp tabletmodemanager.cpp ) add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS}) target_link_libraries(kwin_wayland kwin KF5::Crash) if (HAVE_LIBCAP) target_link_libraries(kwin_wayland ${Libcap_LIBRARIES}) endif() install(TARGETS kwin_wayland ${INSTALL_TARGETS_DEFAULT_ARGS}) if (HAVE_LIBCAP) install( CODE "execute_process( COMMAND ${SETCAP_EXECUTABLE} CAP_SYS_NICE=+ep \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/kwin_wayland)" ) endif() add_subdirectory(platformsupport) add_subdirectory(plugins) ########### install files ############### install(FILES kwin.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}.kcfg) install(FILES colorcorrection/colorcorrect_settings.kcfg DESTINATION ${KCFG_INSTALL_DIR} RENAME ${KWIN_NAME}_colorcorrect.kcfg) install(FILES kwin.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} RENAME ${KWIN_NAME}.notifyrc) install( FILES org.kde.KWin.VirtualDesktopManager.xml org.kde.KWin.xml org.kde.kwin.ColorCorrect.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}) add_subdirectory(qml) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() if (KF5DocTools_FOUND) add_subdirectory(doc) endif() add_subdirectory(kconf_update) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) include(CMakePackageConfigHelpers) set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KWinDBusInterface") 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/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp index c6025eb86..a6739a2f6 100644 --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -1,792 +1,792 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 David Edmundson 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 . +along with this program. If not, see . *********************************************************************/ #include "scripting/scriptedeffect.h" #include "libkwineffects/anidata_p.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "effect_builtins.h" #include "effectloader.h" #include "effects.h" #include "kwin_wayland_test.h" #include "platform.h" #include "xdgshellclient.h" #include "virtualdesktops.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KWin; using namespace std::chrono_literals; static const QString s_socketName = QStringLiteral("wayland_test_effects_scripts-0"); class ScriptedEffectsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEffectsHandler(); void testEffectsContext(); void testShortcuts(); void testAnimations_data(); void testAnimations(); void testScreenEdge(); void testScreenEdgeTouch(); void testFullScreenEffect_data(); void testFullScreenEffect(); void testKeepAlive_data(); void testKeepAlive(); void testGrab(); void testGrabAlreadyGrabbedWindow(); void testGrabAlreadyGrabbedWindowForced(); void testUngrab(); void testRedirect_data(); void testRedirect(); void testComplete(); private: ScriptedEffect *loadEffect(const QString &name); }; class ScriptedEffectWithDebugSpy : public KWin::ScriptedEffect { Q_OBJECT public: ScriptedEffectWithDebugSpy(); bool load(const QString &name); using AnimationEffect::AniMap; using AnimationEffect::state; signals: void testOutput(const QString &data); }; QScriptValue kwinEffectScriptTestOut(QScriptContext *context, QScriptEngine *engine) { auto *script = qobject_cast(context->callee().data().toQObject()); QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) { result.append(QLatin1Char(' ')); } result.append(context->argument(i).toString()); } emit script->testOutput(result); return engine->undefinedValue(); } ScriptedEffectWithDebugSpy::ScriptedEffectWithDebugSpy() : ScriptedEffect() { QScriptValue testHookFunc = engine()->newFunction(kwinEffectScriptTestOut); testHookFunc.setData(engine()->newQObject(this)); engine()->globalObject().setProperty(QStringLiteral("sendTestResponse"), testHookFunc); } bool ScriptedEffectWithDebugSpy::load(const QString &name) { const QString path = QFINDTESTDATA("./scripts/" + name + ".js"); if (!init(name, path)) { return false; } // inject our newly created effect to be registered with the EffectsHandlerImpl::loaded_effects // this is private API so some horrible code is used to find the internal effectloader // and register ourselves auto c = effects->children(); for (auto it = c.begin(); it != c.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { continue; } QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect*, this), Q_ARG(QString, name)); break; } return (static_cast(effects)->isEffectLoaded(name)); } void ScriptedEffectsTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); ScriptedEffectLoader loader; // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); QCOMPARE(scene->compositingType(), KWin::OpenGL2Compositing); KWin::VirtualDesktopManager::self()->setCount(2); } void ScriptedEffectsTest::init() { QVERIFY(Test::setupWaylandConnection()); } void ScriptedEffectsTest::cleanup() { Test::destroyWaylandConnection(); auto effectsImpl = static_cast(effects); effectsImpl->unloadAllEffects(); QVERIFY(effectsImpl->loadedEffects().isEmpty()); KWin::VirtualDesktopManager::self()->setCurrent(1); } void ScriptedEffectsTest::testEffectsHandler() { // this triggers and tests some of the signals in EffectHandler, which is exposed to JS as context property "effects" auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); auto waitFor = [&effectOutputSpy, this](const QString &expected) { QVERIFY(effectOutputSpy.count() > 0 || effectOutputSpy.wait()); QCOMPARE(effectOutputSpy.first().first(), expected); effectOutputSpy.removeFirst(); }; QVERIFY(effect->load("effectsHandler")); // trigger windowAdded signal // create a window using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("WindowA"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); waitFor("windowAdded - WindowA"); waitFor("stackingOrder - 1 WindowA"); // windowMinimsed c->minimize(); waitFor("windowMinimized - WindowA"); c->unminimize(); waitFor("windowUnminimized - WindowA"); surface->deleteLater(); waitFor("windowClosed - WindowA"); // desktop management KWin::VirtualDesktopManager::self()->setCurrent(2); waitFor("desktopChanged - 1 2"); } void ScriptedEffectsTest::testEffectsContext() { // this tests misc non-objects exposed to the script engine: animationTime, displaySize, use of external enums auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("effectContext")); QCOMPARE(effectOutputSpy[0].first(), "1280x1024"); QCOMPARE(effectOutputSpy[1].first(), "100"); QCOMPARE(effectOutputSpy[2].first(), "2"); QCOMPARE(effectOutputSpy[3].first(), "0"); } void ScriptedEffectsTest::testShortcuts() { // this tests method registerShortcut auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("shortcutsTest")); QCOMPARE(effect->shortcutCallbacks().count(), 1); QAction *action = effect->shortcutCallbacks().keys()[0]; QCOMPARE(action->objectName(), "testShortcut"); QCOMPARE(action->text(), "Test Shortcut"); QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence("Meta+Shift+Y")); action->trigger(); QCOMPARE(effectOutputSpy[0].first(), "shortcutTriggered"); } void ScriptedEffectsTest::testAnimations_data() { QTest::addColumn("file"); QTest::addColumn("animationCount"); QTest::newRow("single") << "animationTest" << 1; QTest::newRow("multi") << "animationTestMulti" << 2; } void ScriptedEffectsTest::testAnimations() { // this tests animate/set/cancel // methods take either an int or an array, as forced in the data above // also splits animate vs effects.animate(..) QFETCH(QString, file); QFETCH(int, animationCount); auto *effect = new ScriptedEffectWithDebugSpy; QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load(file)); // animated after window added connect using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("Window 1"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const auto &animationsForWindow = state.first().first; QCOMPARE(animationsForWindow.count(), animationCount); QCOMPARE(animationsForWindow[0].timeLine.duration(), 100ms); QCOMPARE(animationsForWindow[0].to, FPx2(1.4)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); QCOMPARE(animationsForWindow[0].timeLine.easingCurve().type(), QEasingCurve::OutQuad); QCOMPARE(animationsForWindow[0].terminationFlags, AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); if (animationCount == 2) { QCOMPARE(animationsForWindow[1].timeLine.duration(), 100ms); QCOMPARE(animationsForWindow[1].to, FPx2(0.0)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); QCOMPARE(animationsForWindow[1].terminationFlags, AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); } } QCOMPARE(effectOutputSpy[0].first(), "true"); // window state changes, scale should be retargetted c->setMinimized(true); { const auto state = effect->state(); QCOMPARE(state.count(), 1); const auto &animationsForWindow = state.first().first; QCOMPARE(animationsForWindow.count(), animationCount); QCOMPARE(animationsForWindow[0].timeLine.duration(), 200ms); QCOMPARE(animationsForWindow[0].to, FPx2(1.5)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); QCOMPARE(animationsForWindow[0].terminationFlags, AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); if (animationCount == 2) { QCOMPARE(animationsForWindow[1].timeLine.duration(), 200ms); QCOMPARE(animationsForWindow[1].to, FPx2(1.5)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); QCOMPARE(animationsForWindow[1].terminationFlags, AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); } } c->setMinimized(false); { const auto state = effect->state(); QCOMPARE(state.count(), 0); } } void ScriptedEffectsTest::testScreenEdge() { // this test checks registerScreenEdge functions auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("screenEdgeTest")); effect->borderActivated(KWin::ElectricTopRight); QCOMPARE(effectOutputSpy.count(), 1); } void ScriptedEffectsTest::testScreenEdgeTouch() { // this test checks registerTouchScreenEdge functions auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("screenEdgeTouchTest")); auto actions = effect->findChildren(QString(), Qt::FindDirectChildrenOnly); actions[0]->trigger(); QCOMPARE(effectOutputSpy.count(), 1); } void ScriptedEffectsTest::testFullScreenEffect_data() { QTest::addColumn("file"); QTest::newRow("single") << "fullScreenEffectTest"; QTest::newRow("multi") << "fullScreenEffectTestMulti"; QTest::newRow("global") << "fullScreenEffectTestGlobal"; } void ScriptedEffectsTest::testFullScreenEffect() { QFETCH(QString, file); auto *effectMain = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effectMain, &ScriptedEffectWithDebugSpy::testOutput); QSignalSpy fullScreenEffectActiveSpy(effects, &EffectsHandler::hasActiveFullScreenEffectChanged); QSignalSpy isActiveFullScreenEffectSpy(effectMain, &ScriptedEffect::isActiveFullScreenEffectChanged); QVERIFY(effectMain->load(file)); //load any random effect from another test to confirm fullscreen effect state is correctly //shown as being someone else auto effectOther = new ScriptedEffectWithDebugSpy(); QVERIFY(effectOther->load("screenEdgeTouchTest")); QSignalSpy isActiveFullScreenEffectSpyOther(effectOther, &ScriptedEffect::isActiveFullScreenEffectChanged); using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("Window 1"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(effects->hasActiveFullScreenEffect(), false); QCOMPARE(effectMain->isActiveFullScreenEffect(), false); //trigger animation KWin::VirtualDesktopManager::self()->setCurrent(2); QCOMPARE(effects->activeFullScreenEffect(), effectMain); QCOMPARE(effects->hasActiveFullScreenEffect(), true); QCOMPARE(fullScreenEffectActiveSpy.count(), 1); QCOMPARE(effectMain->isActiveFullScreenEffect(), true); QCOMPARE(isActiveFullScreenEffectSpy.count(), 1); QCOMPARE(effectOther->isActiveFullScreenEffect(), false); QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0); //after 500ms trigger another full screen animation QTest::qWait(500); KWin::VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(effects->activeFullScreenEffect(), effectMain); //after 1000ms (+a safety margin for time based tests) we should still be the active full screen effect //despite first animation expiring QTest::qWait(500+100); QCOMPARE(effects->activeFullScreenEffect(), effectMain); //after 1500ms (+a safetey margin) we should have no full screen effect QTest::qWait(500+100); QCOMPARE(effects->activeFullScreenEffect(), nullptr); } void ScriptedEffectsTest::testKeepAlive_data() { QTest::addColumn("file"); QTest::addColumn("keepAlive"); QTest::newRow("keep") << "keepAliveTest" << true; QTest::newRow("don't keep") << "keepAliveTestDontKeep" << false; } void ScriptedEffectsTest::testKeepAlive() { // this test checks whether closed windows are kept alive // when keepAlive property is set to true(false) QFETCH(QString, file); QFETCH(bool, keepAlive); auto *effect = new ScriptedEffectWithDebugSpy; QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effectOutputSpy.isValid()); QVERIFY(effect->load(file)); // create a window using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // no active animations at the beginning QCOMPARE(effect->state().count(), 0); // trigger windowClosed signal surface->deleteLater(); QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait()); if (keepAlive) { QCOMPARE(effect->state().count(), 1); QTest::qWait(500); QCOMPARE(effect->state().count(), 1); QTest::qWait(500 + 100); // 100ms is extra safety margin QCOMPARE(effect->state().count(), 0); } else { // the test effect doesn't keep the window alive, so it should be // removed immediately QSignalSpy deletedRemovedSpy(workspace(), &Workspace::deletedRemoved); QVERIFY(deletedRemovedSpy.isValid()); QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100)); // 100ms is less than duration of the animation QCOMPARE(effect->state().count(), 0); } } void ScriptedEffectsTest::testGrab() { // this test verifies that scripted effects can grab windows that are // not already grabbed // load the test effect auto effect = new ScriptedEffectWithDebugSpy; QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effectOutputSpy.isValid()); QVERIFY(effect->load(QStringLiteral("grabTest"))); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // the test effect should grab the test client successfully QCOMPARE(effectOutputSpy.count(), 1); QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), effect); } void ScriptedEffectsTest::testGrabAlreadyGrabbedWindow() { // this test verifies that scripted effects cannot grab already grabbed // windows (unless force is set to true of course) // load effect that will hold the window grab auto owner = new ScriptedEffectWithDebugSpy; QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(ownerOutputSpy.isValid()); QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowTest_owner"))); // load effect that will try to grab already grabbed window auto grabber = new ScriptedEffectWithDebugSpy; QSignalSpy grabberOutputSpy(grabber, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(grabberOutputSpy.isValid()); QVERIFY(grabber->load(QStringLiteral("grabAlreadyGrabbedWindowTest_grabber"))); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // effect that initially held the grab should still hold the grab QCOMPARE(ownerOutputSpy.count(), 1); QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), owner); // effect that tried to grab already grabbed window should fail miserably QCOMPARE(grabberOutputSpy.count(), 1); QCOMPARE(grabberOutputSpy.first().first(), QStringLiteral("fail")); } void ScriptedEffectsTest::testGrabAlreadyGrabbedWindowForced() { // this test verifies that scripted effects can steal window grabs when // they forcefully try to grab windows // load effect that initially will be holding the window grab auto owner = new ScriptedEffectWithDebugSpy; QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(ownerOutputSpy.isValid()); QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_owner"))); // load effect that will try to steal the window grab auto thief = new ScriptedEffectWithDebugSpy; QSignalSpy thiefOutputSpy(thief, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(thiefOutputSpy.isValid()); QVERIFY(thief->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_thief"))); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // verify that the owner in fact held the grab QCOMPARE(ownerOutputSpy.count(), 1); QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); // effect that grabbed the test client forcefully should now hold the grab QCOMPARE(thiefOutputSpy.count(), 1); QCOMPARE(thiefOutputSpy.first().first(), QStringLiteral("ok")); QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), thief); } void ScriptedEffectsTest::testUngrab() { // this test verifies that scripted effects can ungrab windows that they // are previously grabbed // load the test effect auto effect = new ScriptedEffectWithDebugSpy; QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effectOutputSpy.isValid()); QVERIFY(effect->load(QStringLiteral("ungrabTest"))); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // the test effect should grab the test client successfully QCOMPARE(effectOutputSpy.count(), 1); QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), effect); // when the test effect sees that a window was minimized, it will try to ungrab it effectOutputSpy.clear(); c->setMinimized(true); QCOMPARE(effectOutputSpy.count(), 1); QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); QCOMPARE(c->effectWindow()->data(WindowAddedGrabRole).value(), nullptr); } void ScriptedEffectsTest::testRedirect_data() { QTest::addColumn("file"); QTest::addColumn("shouldTerminate"); QTest::newRow("animate/DontTerminateAtSource") << "redirectAnimateDontTerminateTest" << false; QTest::newRow("animate/TerminateAtSource") << "redirectAnimateTerminateTest" << true; QTest::newRow("set/DontTerminate") << "redirectSetDontTerminateTest" << false; QTest::newRow("set/Terminate") << "redirectSetTerminateTest" << true; } void ScriptedEffectsTest::testRedirect() { // this test verifies that redirect() works // load the test effect auto effect = new ScriptedEffectWithDebugSpy; QFETCH(QString, file); QVERIFY(effect->load(file)); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); auto around = [] (std::chrono::milliseconds elapsed, std::chrono::milliseconds pivot, std::chrono::milliseconds margin) { return qAbs(elapsed.count() - pivot.count()) < margin.count(); }; // initially, the test animation is at the source position { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QCOMPARE(animations[0].timeLine.direction(), TimeLine::Forward); QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); } // minimize the test client after 250ms, when the test effect sees that // a window was minimized, it will try to reverse animation for it QTest::qWait(250); QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effectOutputSpy.isValid()); c->setMinimized(true); QCOMPARE(effectOutputSpy.count(), 1); QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); QVERIFY(around(animations[0].timeLine.elapsed(), 1000ms - 250ms, 50ms)); } // wait for the animation to reach the start position, 100ms is an extra // safety margin QTest::qWait(250 + 100); QFETCH(bool, shouldTerminate); if (shouldTerminate) { const auto state = effect->state(); QCOMPARE(state.count(), 0); } else { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); QCOMPARE(animations[0].timeLine.value(), 0.0); } } void ScriptedEffectsTest::testComplete() { // this test verifies that complete works // load the test effect auto effect = new ScriptedEffectWithDebugSpy; QVERIFY(effect->load(QStringLiteral("completeTest"))); // create test client using namespace KWayland::Client; Surface *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); XdgShellSurface *shellSurface = Test::createXdgShellStableSurface(surface, surface); QVERIFY(shellSurface); XdgShellClient *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); auto around = [] (std::chrono::milliseconds elapsed, std::chrono::milliseconds pivot, std::chrono::milliseconds margin) { return qAbs(elapsed.count() - pivot.count()) < margin.count(); }; // initially, the test animation should be at the start position { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); QVERIFY(!animations[0].timeLine.done()); } // wait for 250ms QTest::qWait(250); { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QVERIFY(around(animations[0].timeLine.elapsed(), 250ms, 50ms)); QVERIFY(!animations[0].timeLine.done()); } // minimize the test client, when the test effect sees that a window was // minimized, it will try to complete animation for it QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effectOutputSpy.isValid()); c->setMinimized(true); QCOMPARE(effectOutputSpy.count(), 1); QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); { const auto state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const QList animations = state.first().first; QCOMPARE(animations.count(), 1); QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); QVERIFY(animations[0].timeLine.done()); } } WAYLANDTEST_MAIN(ScriptedEffectsTest) #include "scripted_effects_test.moc" diff --git a/effects/screenshot/screenshot.cpp b/effects/screenshot/screenshot.cpp index d56cfb37b..86c4bd1e4 100644 --- a/effects/screenshot/screenshot.cpp +++ b/effects/screenshot/screenshot.cpp @@ -1,712 +1,712 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Martin Gräßlin Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) 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 "screenshot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { const static QString s_errorAlreadyTaking = QStringLiteral("org.kde.kwin.Screenshot.Error.AlreadyTaking"); const static QString s_errorAlreadyTakingMsg = QStringLiteral("A screenshot is already been taken"); const static QString s_errorFd = QStringLiteral("org.kde.kwin.Screenshot.Error.FileDescriptor"); const static QString s_errorFdMsg = QStringLiteral("No valid file descriptor"); const static QString s_errorCancelled = QStringLiteral("org.kde.kwin.Screenshot.Error.Cancelled"); const static QString s_errorCancelledMsg = QStringLiteral("Screenshot got cancelled"); const static QString s_errorInvalidArea = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidArea"); const static QString s_errorInvalidAreaMsg = QStringLiteral("Invalid area requested"); const static QString s_errorInvalidScreen = QStringLiteral("org.kde.kwin.Screenshot.Error.InvalidScreen"); const static QString s_errorInvalidScreenMsg = QStringLiteral("Invalid screen requested"); bool ScreenShotEffect::supported() { return effects->compositingType() == XRenderCompositing || (effects->isOpenGLCompositing() && GLRenderTarget::supported()); } ScreenShotEffect::ScreenShotEffect() : m_scheduledScreenshot(nullptr) { connect(effects, &EffectsHandler::windowClosed, this, &ScreenShotEffect::windowClosed); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Screenshot"), this, QDBusConnection::ExportScriptableContents); } ScreenShotEffect::~ScreenShotEffect() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Screenshot")); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING static QImage xPictureToImage(xcb_render_picture_t srcPic, const QRect &geometry, xcb_image_t **xImage) { xcb_connection_t *c = effects->xcbConnection(); xcb_pixmap_t xpix = xcb_generate_id(c); xcb_create_pixmap(c, 32, xpix, effects->x11RootWindow(), geometry.width(), geometry.height()); XRenderPicture pic(xpix, 32); xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, srcPic, XCB_RENDER_PICTURE_NONE, pic, geometry.x(), geometry.y(), 0, 0, 0, 0, geometry.width(), geometry.height()); xcb_flush(c); *xImage = xcb_image_get(c, xpix, 0, 0, geometry.width(), geometry.height(), ~0, XCB_IMAGE_FORMAT_Z_PIXMAP); QImage img((*xImage)->data, (*xImage)->width, (*xImage)->height, (*xImage)->stride, QImage::Format_ARGB32_Premultiplied); // TODO: byte order might need swapping xcb_free_pixmap(c, xpix); return img.copy(); } #endif static QSize pickWindowSize(const QImage &image) { xcb_connection_t *c = effects->xcbConnection(); // This will implicitly enable BIG-REQUESTS extension. const uint32_t maximumRequestSize = xcb_get_maximum_request_length(c); const xcb_setup_t *setup = xcb_get_setup(c); uint32_t requestSize = sizeof(xcb_put_image_request_t); // With BIG-REQUESTS extension an additional 32-bit field is inserted into // the request so we better take it into account. if (setup->maximum_request_length < maximumRequestSize) { requestSize += 4; } const uint32_t maximumDataSize = 4 * maximumRequestSize - requestSize; const uint32_t bytesPerPixel = image.depth() >> 3; const uint32_t bytesPerLine = image.bytesPerLine(); if (image.sizeInBytes() <= maximumDataSize) { return image.size(); } if (maximumDataSize < bytesPerLine) { return QSize(maximumDataSize / bytesPerPixel, 1); } return QSize(image.width(), maximumDataSize / bytesPerLine); } static xcb_pixmap_t xpixmapFromImage(const QImage &image) { xcb_connection_t *c = effects->xcbConnection(); xcb_pixmap_t pixmap = xcb_generate_id(c); xcb_gcontext_t gc = xcb_generate_id(c); xcb_create_pixmap(c, image.depth(), pixmap, effects->x11RootWindow(), image.width(), image.height()); xcb_create_gc(c, gc, pixmap, 0, nullptr); const int bytesPerPixel = image.depth() >> 3; // Figure out how much data we can send with one invocation of xcb_put_image. // In contrast to XPutImage, xcb_put_image doesn't implicitly split the data. const QSize window = pickWindowSize(image); for (int i = 0; i < image.height(); i += window.height()) { const int targetHeight = qMin(image.height() - i, window.height()); const uint8_t *line = image.scanLine(i); for (int j = 0; j < image.width(); j += window.width()) { const int targetWidth = qMin(image.width() - j, window.width()); const uint8_t *bytes = line + j * bytesPerPixel; const uint32_t byteCount = targetWidth * targetHeight * bytesPerPixel; xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, gc, targetWidth, targetHeight, j, i, 0, image.depth(), byteCount, bytes); } } xcb_flush(c); xcb_free_gc(c, gc); return pixmap; } void ScreenShotEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { m_cachedOutputGeometry = data.outputGeometry(); effects->paintScreen(mask, region, data); } void ScreenShotEffect::postPaintScreen() { effects->postPaintScreen(); if (m_scheduledScreenshot) { WindowPaintData d(m_scheduledScreenshot); double left = 0; double top = 0; double right = m_scheduledScreenshot->width(); double bottom = m_scheduledScreenshot->height(); if (m_scheduledScreenshot->hasDecoration() && m_type & INCLUDE_DECORATION) { foreach (const WindowQuad & quad, d.quads) { // we need this loop to include the decoration padding left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } else if (m_scheduledScreenshot->hasDecoration()) { WindowQuadList newQuads; left = m_scheduledScreenshot->width(); top = m_scheduledScreenshot->height(); right = 0; bottom = 0; foreach (const WindowQuad & quad, d.quads) { if (quad.type() == WindowQuadContents) { newQuads << quad; left = qMin(left, quad.left()); top = qMin(top, quad.top()); right = qMax(right, quad.right()); bottom = qMax(bottom, quad.bottom()); } } d.quads = newQuads; } const int width = right - left; const int height = bottom - top; bool validTarget = true; QScopedPointer offscreenTexture; QScopedPointer target; if (effects->isOpenGLCompositing()) { offscreenTexture.reset(new GLTexture(GL_RGBA8, width, height)); offscreenTexture->setFilter(GL_LINEAR); offscreenTexture->setWrapMode(GL_CLAMP_TO_EDGE); target.reset(new GLRenderTarget(*offscreenTexture)); validTarget = target->valid(); } if (validTarget) { d.setXTranslation(-m_scheduledScreenshot->x() - left); d.setYTranslation(-m_scheduledScreenshot->y() - top); // render window into offscreen texture int mask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_TRANSLUCENT; QImage img; if (effects->isOpenGLCompositing()) { GLRenderTarget::pushRenderTarget(target.data()); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0); QMatrix4x4 projection; projection.ortho(QRect(0, 0, offscreenTexture->width(), offscreenTexture->height())); d.setProjectionMatrix(projection); effects->drawWindow(m_scheduledScreenshot, mask, infiniteRegion(), d); // copy content from framebuffer into image img = QImage(QSize(width, height), QImage::Format_ARGB32); glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(), (GLvoid*)img.bits()); GLRenderTarget::popRenderTarget(); ScreenShotEffect::convertFromGLImage(img, width, height); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING xcb_image_t *xImage = nullptr; if (effects->compositingType() == XRenderCompositing) { setXRenderOffscreen(true); effects->drawWindow(m_scheduledScreenshot, mask, QRegion(0, 0, width, height), d); if (xRenderOffscreenTarget()) { img = xPictureToImage(xRenderOffscreenTarget(), QRect(0, 0, width, height), &xImage); } setXRenderOffscreen(false); } #endif if (m_type & INCLUDE_CURSOR) { grabPointerImage(img, m_scheduledScreenshot->x() + left, m_scheduledScreenshot->y() + top); } if (m_windowMode == WindowMode::Xpixmap) { const xcb_pixmap_t xpix = xpixmapFromImage(img); emit screenshotCreated(xpix); m_windowMode = WindowMode::NoCapture; } else if (m_windowMode == WindowMode::File) { sendReplyImage(img); } else if (m_windowMode == WindowMode::FileDescriptor) { QtConcurrent::run( [] (int fd, const QImage &img) { QFile file; if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { QDataStream ds(&file); ds << img; file.close(); } else { close(fd); } }, m_fd, img); m_windowMode = WindowMode::NoCapture; m_fd = -1; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (xImage) { xcb_image_destroy(xImage); } #endif } m_scheduledScreenshot = nullptr; } if (!m_scheduledGeometry.isNull()) { if (!m_cachedOutputGeometry.isNull()) { // special handling for per-output geometry rendering const QRect intersection = m_scheduledGeometry.intersected(m_cachedOutputGeometry); if (intersection.isEmpty()) { // doesn't intersect, not going onto this screenshot return; } const QImage img = blitScreenshot(intersection); if (img.size() == m_scheduledGeometry.size()) { // we are done sendReplyImage(img); return; } if (m_multipleOutputsImage.isNull()) { m_multipleOutputsImage = QImage(m_scheduledGeometry.size(), QImage::Format_ARGB32); m_multipleOutputsImage.fill(Qt::transparent); } QPainter p; p.begin(&m_multipleOutputsImage); p.drawImage(intersection.topLeft() - m_scheduledGeometry.topLeft(), img); p.end(); m_multipleOutputsRendered = m_multipleOutputsRendered.united(intersection); if (m_multipleOutputsRendered.boundingRect() == m_scheduledGeometry) { sendReplyImage(m_multipleOutputsImage); } } else { const QImage img = blitScreenshot(m_scheduledGeometry); sendReplyImage(img); } } } void ScreenShotEffect::sendReplyImage(const QImage &img) { if (m_fd != -1) { QtConcurrent::run( [] (int fd, const QImage &img) { QFile file; if (file.open(fd, QIODevice::WriteOnly, QFileDevice::AutoCloseHandle)) { QDataStream ds(&file); ds << img; file.close(); } else { close(fd); } }, m_fd, img); m_fd = -1; } else { QDBusConnection::sessionBus().send(m_replyMessage.createReply(saveTempImage(img))); } m_scheduledGeometry = QRect(); m_multipleOutputsImage = QImage(); m_multipleOutputsRendered = QRegion(); m_captureCursor = false; m_windowMode = WindowMode::NoCapture; } QString ScreenShotEffect::saveTempImage(const QImage &img) { if (img.isNull()) { return QString(); } QTemporaryFile temp(QDir::tempPath() + QDir::separator() + QLatin1String("kwin_screenshot_XXXXXX.png")); temp.setAutoRemove(false); if (!temp.open()) { return QString(); } img.save(&temp); temp.close(); KNotification::event(KNotification::Notification, i18nc("Notification caption that a screenshot got saved to file", "Screenshot"), i18nc("Notification with path to screenshot file", "Screenshot saved to %1", temp.fileName()), QStringLiteral("spectacle")); return temp.fileName(); } void ScreenShotEffect::screenshotWindowUnderCursor(int mask) { if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_type = (ScreenShotType)mask; const QPoint cursor = effects->cursorPos(); EffectWindowList order = effects->stackingOrder(); EffectWindowList::const_iterator it = order.constEnd(), first = order.constBegin(); while( it != first ) { m_scheduledScreenshot = *(--it); if (m_scheduledScreenshot->isOnCurrentDesktop() && !m_scheduledScreenshot->isMinimized() && !m_scheduledScreenshot->isDeleted() && m_scheduledScreenshot->geometry().contains(cursor)) break; m_scheduledScreenshot = nullptr; } if (m_scheduledScreenshot) { m_windowMode = WindowMode::Xpixmap; m_scheduledScreenshot->addRepaintFull(); } } void ScreenShotEffect::screenshotForWindow(qulonglong winid, int mask) { m_type = (ScreenShotType) mask; EffectWindow* w = effects->findWindow(winid); if(w && !w->isMinimized() && !w->isDeleted()) { m_windowMode = WindowMode::Xpixmap; m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } } QString ScreenShotEffect::interactive(int mask) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_type = (ScreenShotType) mask; m_windowMode = WindowMode::File; m_replyMessage = message(); setDelayedReply(true); effects->startInteractiveWindowSelection( [this] (EffectWindow *w) { hideInfoMessage(); if (!w) { QDBusConnection::sessionBus().send(m_replyMessage.createErrorReply(s_errorCancelled, s_errorCancelledMsg)); m_windowMode = WindowMode::NoCapture; return; } else { m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } }); showInfoMessage(InfoMessageMode::Window); return QString(); } void ScreenShotEffect::interactive(QDBusUnixFileDescriptor fd, int mask) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_type = (ScreenShotType) mask; m_windowMode = WindowMode::FileDescriptor; effects->startInteractiveWindowSelection( [this] (EffectWindow *w) { hideInfoMessage(); if (!w) { close(m_fd); m_fd = -1; m_windowMode = WindowMode::NoCapture; return; } else { m_scheduledScreenshot = w; m_scheduledScreenshot->addRepaintFull(); } }); showInfoMessage(InfoMessageMode::Window); } void ScreenShotEffect::showInfoMessage(InfoMessageMode mode) { QString text; switch (mode) { case InfoMessageMode::Window: text = i18n("Select window to screen shot with left click or enter.\nEscape or right click to cancel."); break; case InfoMessageMode::Screen: text = i18n("Create screen shot with left click or enter.\nEscape or right click to cancel."); break; } effects->showOnScreenMessage(text, QStringLiteral("spectacle")); } void ScreenShotEffect::hideInfoMessage() { effects->hideOnScreenMessage(EffectsHandler::OnScreenMessageHideFlag::SkipsCloseAnimation); } QString ScreenShotEffect::screenshotFullscreen(bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_replyMessage = message(); setDelayedReply(true); m_scheduledGeometry = effects->virtualScreenGeometry(); m_captureCursor = captureCursor; effects->addRepaintFull(); return QString(); } void ScreenShotEffect::screenshotFullscreen(QDBusUnixFileDescriptor fd, bool captureCursor) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_captureCursor = captureCursor; showInfoMessage(InfoMessageMode::Screen); effects->startInteractivePositionSelection( [this] (const QPoint &p) { hideInfoMessage(); if (p == QPoint(-1, -1)) { // error condition close(m_fd); m_fd = -1; } else { m_scheduledGeometry = effects->virtualScreenGeometry(); effects->addRepaint(m_scheduledGeometry); } } ); } QString ScreenShotEffect::screenshotScreen(int screen, bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_scheduledGeometry = effects->clientArea(FullScreenArea, screen, 0); if (m_scheduledGeometry.isNull()) { sendErrorReply(s_errorInvalidScreen, s_errorInvalidScreenMsg); return QString(); } m_captureCursor = captureCursor; m_replyMessage = message(); setDelayedReply(true); effects->addRepaint(m_scheduledGeometry); return QString(); } void ScreenShotEffect::screenshotScreen(QDBusUnixFileDescriptor fd, bool captureCursor) { if (!calledFromDBus()) { return; } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return; } m_fd = dup(fd.fileDescriptor()); if (m_fd == -1) { sendErrorReply(s_errorFd, s_errorFdMsg); return; } m_captureCursor = captureCursor; showInfoMessage(InfoMessageMode::Screen); effects->startInteractivePositionSelection( [this] (const QPoint &p) { hideInfoMessage(); if (p == QPoint(-1, -1)) { // error condition close(m_fd); m_fd = -1; } else { m_scheduledGeometry = effects->clientArea(FullScreenArea, effects->screenNumber(p), 0); if (m_scheduledGeometry.isNull()) { close(m_fd); m_fd = -1; return; } effects->addRepaint(m_scheduledGeometry); } } ); } QString ScreenShotEffect::screenshotArea(int x, int y, int width, int height, bool captureCursor) { if (!calledFromDBus()) { return QString(); } if (isTakingScreenshot()) { sendErrorReply(s_errorAlreadyTaking, s_errorAlreadyTakingMsg); return QString(); } m_scheduledGeometry = QRect(x, y, width, height); if (m_scheduledGeometry.isNull() || m_scheduledGeometry.isEmpty()) { m_scheduledGeometry = QRect(); sendErrorReply(s_errorInvalidArea, s_errorInvalidAreaMsg); return QString(); } m_captureCursor = captureCursor; m_replyMessage = message(); setDelayedReply(true); effects->addRepaint(m_scheduledGeometry); return QString(); } QImage ScreenShotEffect::blitScreenshot(const QRect &geometry) { QImage img; if (effects->isOpenGLCompositing()) { img = QImage(geometry.size(), QImage::Format_ARGB32); if (GLRenderTarget::blitSupported() && !GLPlatform::instance()->isGLES()) { GLTexture tex(GL_RGBA8, geometry.width(), geometry.height()); GLRenderTarget target(tex); target.blitFromFramebuffer(geometry); // copy content from framebuffer into image tex.bind(); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); tex.unbind(); } else { glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)img.bits()); } ScreenShotEffect::convertFromGLImage(img, geometry.width(), geometry.height()); } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) { xcb_image_t *xImage = nullptr; img = xPictureToImage(effects->xrenderBufferPicture(), geometry, &xImage); if (xImage) { xcb_image_destroy(xImage); } } #endif if (m_captureCursor) { grabPointerImage(img, geometry.x(), geometry.y()); } return img; } void ScreenShotEffect::grabPointerImage(QImage& snapshot, int offsetx, int offsety) { const auto cursor = effects->cursorImage(); if (cursor.image().isNull()) return; QPainter painter(&snapshot); painter.drawImage(effects->cursorPos() - cursor.hotSpot() - QPoint(offsetx, offsety), cursor.image()); } void ScreenShotEffect::convertFromGLImage(QImage &img, int w, int h) { // from QtOpenGL/qgl.cpp // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) - // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp + // see https://github.com/qt/qtbase/blob/dev/src/opengl/qgl.cpp if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // OpenGL gives RGBA; Qt wants ARGB uint *p = (uint*)img.bits(); uint *end = p + w * h; while (p < end) { uint a = *p << 24; *p = (*p >> 8) | a; p++; } } else { // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB for (int y = 0; y < h; y++) { uint *q = (uint*)img.scanLine(y); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } bool ScreenShotEffect::isActive() const { return (m_scheduledScreenshot != nullptr || !m_scheduledGeometry.isNull()) && !effects->isScreenLocked(); } void ScreenShotEffect::windowClosed( EffectWindow* w ) { if (w == m_scheduledScreenshot) { m_scheduledScreenshot = nullptr; screenshotWindowUnderCursor(m_type); } } bool ScreenShotEffect::isTakingScreenshot() const { if (!m_scheduledGeometry.isNull()) { return true; } if (m_windowMode != WindowMode::NoCapture) { return true; } if (m_fd != -1) { return true; } return false; } } // namespace diff --git a/options.cpp b/options.cpp index 07534d7c7..aa30eb6b0 100644 --- a/options.cpp +++ b/options.cpp @@ -1,1092 +1,1092 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2012 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "options.h" #include "config-kwin.h" #include "utils.h" #include "platform.h" #ifndef KCMRULES #include #include "screens.h" #include "settings.h" #include #include #endif //KCMRULES namespace KWin { #ifndef KCMRULES int currentRefreshRate() { return Options::currentRefreshRate(); } int Options::currentRefreshRate() { int rate = -1; QString syncScreenName(QLatin1String("primary screen")); if (options->refreshRate() > 0) { // use manually configured refresh rate rate = options->refreshRate(); } else if (Screens::self()->count() > 0) { // prefer the refreshrate calculated from the screens mode information // at least the nvidia driver reports 50Hz BS ... *again*! int syncScreen = 0; if (Screens::self()->count() > 1) { const QByteArray syncDisplayDevice(qgetenv("__GL_SYNC_DISPLAY_DEVICE")); // if __GL_SYNC_DISPLAY_DEVICE is exported, the GPU shall sync to that device // so we try to use its refresh rate if (!syncDisplayDevice.isEmpty()) { for (int i = 0; i < Screens::self()->count(); ++i) { if (Screens::self()->name(i) == syncDisplayDevice) { syncScreenName = Screens::self()->name(i); syncScreen = i; break; } } } } rate = qRound(Screens::self()->refreshRate(syncScreen)); // TODO forward float precision? } // 0Hz or less is invalid, so we fallback to a default rate if (rate <= 0) rate = 60; // and not shitty 50Hz for sure! *grrr* // QTimer gives us 1msec (1000Hz) at best, so we ignore anything higher; // however, additional throttling prevents very high rates from taking place anyway else if (rate > 1000) rate = 1000; qCDebug(KWIN_CORE) << "Vertical Refresh rate " << rate << "Hz (" << syncScreenName << ")"; return rate; } Options::Options(QObject *parent) : QObject(parent) , m_settings(new Settings(kwinApp()->config())) , m_focusPolicy(ClickToFocus) , m_nextFocusPrefersMouse(false) , m_clickRaise(false) , m_autoRaise(false) , m_autoRaiseInterval(0) , m_delayFocusInterval(0) , m_shadeHover(false) , m_shadeHoverInterval(0) , m_separateScreenFocus(false) , m_placement(Placement::NoPlacement) , m_borderSnapZone(0) , m_windowSnapZone(0) , m_centerSnapZone(0) , m_snapOnlyWhenOverlapping(false) , m_rollOverDesktops(false) , m_focusStealingPreventionLevel(0) , m_killPingTimeout(0) , m_hideUtilityWindowsForInactive(false) , m_compositingMode(Options::defaultCompositingMode()) , m_useCompositing(Options::defaultUseCompositing()) , m_hiddenPreviews(Options::defaultHiddenPreviews()) , m_glSmoothScale(Options::defaultGlSmoothScale()) , m_xrenderSmoothScale(Options::defaultXrenderSmoothScale()) , m_maxFpsInterval(Options::defaultMaxFpsInterval()) , m_refreshRate(Options::defaultRefreshRate()) , m_vBlankTime(Options::defaultVBlankTime()) , m_glStrictBinding(Options::defaultGlStrictBinding()) , m_glStrictBindingFollowsDriver(Options::defaultGlStrictBindingFollowsDriver()) , m_glCoreProfile(Options::defaultGLCoreProfile()) , m_glPreferBufferSwap(Options::defaultGlPreferBufferSwap()) , m_glPlatformInterface(Options::defaultGlPlatformInterface()) , m_windowsBlockCompositing(true) , OpTitlebarDblClick(Options::defaultOperationTitlebarDblClick()) , CmdActiveTitlebar1(Options::defaultCommandActiveTitlebar1()) , CmdActiveTitlebar2(Options::defaultCommandActiveTitlebar2()) , CmdActiveTitlebar3(Options::defaultCommandActiveTitlebar3()) , CmdInactiveTitlebar1(Options::defaultCommandInactiveTitlebar1()) , CmdInactiveTitlebar2(Options::defaultCommandInactiveTitlebar2()) , CmdInactiveTitlebar3(Options::defaultCommandInactiveTitlebar3()) , CmdTitlebarWheel(Options::defaultCommandTitlebarWheel()) , CmdWindow1(Options::defaultCommandWindow1()) , CmdWindow2(Options::defaultCommandWindow2()) , CmdWindow3(Options::defaultCommandWindow3()) , CmdWindowWheel(Options::defaultCommandWindowWheel()) , CmdAll1(Options::defaultCommandAll1()) , CmdAll2(Options::defaultCommandAll2()) , CmdAll3(Options::defaultCommandAll3()) , CmdAllWheel(Options::defaultCommandAllWheel()) , CmdAllModKey(Options::defaultKeyCmdAllModKey()) , electric_border_maximize(false) , electric_border_tiling(false) , electric_border_corner_ratio(0.0) , borderless_maximized_windows(false) , show_geometry_tip(false) , condensed_title(false) { m_settings->setDefaults(); syncFromKcfgc(); m_configWatcher = KConfigWatcher::create(m_settings->sharedConfig()); connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { if (group.name() == QLatin1String("KDE") && names.contains(QByteArrayLiteral("AnimationDurationFactor"))) { emit animationSpeedChanged(); } }); } Options::~Options() { } void Options::setFocusPolicy(FocusPolicy focusPolicy) { if (m_focusPolicy == focusPolicy) { return; } m_focusPolicy = focusPolicy; emit focusPolicyChanged(); if (m_focusPolicy == ClickToFocus) { setAutoRaise(false); setAutoRaiseInterval(0); setDelayFocusInterval(0); } } void Options::setNextFocusPrefersMouse(bool nextFocusPrefersMouse) { if (m_nextFocusPrefersMouse == nextFocusPrefersMouse) { return; } m_nextFocusPrefersMouse = nextFocusPrefersMouse; emit nextFocusPrefersMouseChanged(); } void Options::setClickRaise(bool clickRaise) { if (m_autoRaise) { // important: autoRaise implies ClickRaise clickRaise = true; } if (m_clickRaise == clickRaise) { return; } m_clickRaise = clickRaise; emit clickRaiseChanged(); } void Options::setAutoRaise(bool autoRaise) { if (m_focusPolicy == ClickToFocus) { autoRaise = false; } if (m_autoRaise == autoRaise) { return; } m_autoRaise = autoRaise; if (m_autoRaise) { // important: autoRaise implies ClickRaise setClickRaise(true); } emit autoRaiseChanged(); } void Options::setAutoRaiseInterval(int autoRaiseInterval) { if (m_focusPolicy == ClickToFocus) { autoRaiseInterval = 0; } if (m_autoRaiseInterval == autoRaiseInterval) { return; } m_autoRaiseInterval = autoRaiseInterval; emit autoRaiseIntervalChanged(); } void Options::setDelayFocusInterval(int delayFocusInterval) { if (m_focusPolicy == ClickToFocus) { delayFocusInterval = 0; } if (m_delayFocusInterval == delayFocusInterval) { return; } m_delayFocusInterval = delayFocusInterval; emit delayFocusIntervalChanged(); } void Options::setShadeHover(bool shadeHover) { if (m_shadeHover == shadeHover) { return; } m_shadeHover = shadeHover; emit shadeHoverChanged(); } void Options::setShadeHoverInterval(int shadeHoverInterval) { if (m_shadeHoverInterval == shadeHoverInterval) { return; } m_shadeHoverInterval = shadeHoverInterval; emit shadeHoverIntervalChanged(); } void Options::setSeparateScreenFocus(bool separateScreenFocus) { if (m_separateScreenFocus == separateScreenFocus) { return; } m_separateScreenFocus = separateScreenFocus; emit separateScreenFocusChanged(m_separateScreenFocus); } void Options::setPlacement(int placement) { if (m_placement == static_cast(placement)) { return; } m_placement = static_cast(placement); emit placementChanged(); } void Options::setBorderSnapZone(int borderSnapZone) { if (m_borderSnapZone == borderSnapZone) { return; } m_borderSnapZone = borderSnapZone; emit borderSnapZoneChanged(); } void Options::setWindowSnapZone(int windowSnapZone) { if (m_windowSnapZone == windowSnapZone) { return; } m_windowSnapZone = windowSnapZone; emit windowSnapZoneChanged(); } void Options::setCenterSnapZone(int centerSnapZone) { if (m_centerSnapZone == centerSnapZone) { return; } m_centerSnapZone = centerSnapZone; emit centerSnapZoneChanged(); } void Options::setSnapOnlyWhenOverlapping(bool snapOnlyWhenOverlapping) { if (m_snapOnlyWhenOverlapping == snapOnlyWhenOverlapping) { return; } m_snapOnlyWhenOverlapping = snapOnlyWhenOverlapping; emit snapOnlyWhenOverlappingChanged(); } void Options::setRollOverDesktops(bool rollOverDesktops) { if (m_rollOverDesktops == rollOverDesktops) { return; } m_rollOverDesktops = rollOverDesktops; emit rollOverDesktopsChanged(m_rollOverDesktops); } void Options::setFocusStealingPreventionLevel(int focusStealingPreventionLevel) { if (!focusPolicyIsReasonable()) { focusStealingPreventionLevel = 0; } if (m_focusStealingPreventionLevel == focusStealingPreventionLevel) { return; } m_focusStealingPreventionLevel = qMax(0, qMin(4, focusStealingPreventionLevel)); emit focusStealingPreventionLevelChanged(); } void Options::setOperationTitlebarDblClick(WindowOperation operationTitlebarDblClick) { if (OpTitlebarDblClick == operationTitlebarDblClick) { return; } OpTitlebarDblClick = operationTitlebarDblClick; emit operationTitlebarDblClickChanged(); } void Options::setOperationMaxButtonLeftClick(WindowOperation op) { if (opMaxButtonLeftClick == op) { return; } opMaxButtonLeftClick = op; emit operationMaxButtonLeftClickChanged(); } void Options::setOperationMaxButtonRightClick(WindowOperation op) { if (opMaxButtonRightClick == op) { return; } opMaxButtonRightClick = op; emit operationMaxButtonRightClickChanged(); } void Options::setOperationMaxButtonMiddleClick(WindowOperation op) { if (opMaxButtonMiddleClick == op) { return; } opMaxButtonMiddleClick = op; emit operationMaxButtonMiddleClickChanged(); } void Options::setCommandActiveTitlebar1(MouseCommand commandActiveTitlebar1) { if (CmdActiveTitlebar1 == commandActiveTitlebar1) { return; } CmdActiveTitlebar1 = commandActiveTitlebar1; emit commandActiveTitlebar1Changed(); } void Options::setCommandActiveTitlebar2(MouseCommand commandActiveTitlebar2) { if (CmdActiveTitlebar2 == commandActiveTitlebar2) { return; } CmdActiveTitlebar2 = commandActiveTitlebar2; emit commandActiveTitlebar2Changed(); } void Options::setCommandActiveTitlebar3(MouseCommand commandActiveTitlebar3) { if (CmdActiveTitlebar3 == commandActiveTitlebar3) { return; } CmdActiveTitlebar3 = commandActiveTitlebar3; emit commandActiveTitlebar3Changed(); } void Options::setCommandInactiveTitlebar1(MouseCommand commandInactiveTitlebar1) { if (CmdInactiveTitlebar1 == commandInactiveTitlebar1) { return; } CmdInactiveTitlebar1 = commandInactiveTitlebar1; emit commandInactiveTitlebar1Changed(); } void Options::setCommandInactiveTitlebar2(MouseCommand commandInactiveTitlebar2) { if (CmdInactiveTitlebar2 == commandInactiveTitlebar2) { return; } CmdInactiveTitlebar2 = commandInactiveTitlebar2; emit commandInactiveTitlebar2Changed(); } void Options::setCommandInactiveTitlebar3(MouseCommand commandInactiveTitlebar3) { if (CmdInactiveTitlebar3 == commandInactiveTitlebar3) { return; } CmdInactiveTitlebar3 = commandInactiveTitlebar3; emit commandInactiveTitlebar3Changed(); } void Options::setCommandWindow1(MouseCommand commandWindow1) { if (CmdWindow1 == commandWindow1) { return; } CmdWindow1 = commandWindow1; emit commandWindow1Changed(); } void Options::setCommandWindow2(MouseCommand commandWindow2) { if (CmdWindow2 == commandWindow2) { return; } CmdWindow2 = commandWindow2; emit commandWindow2Changed(); } void Options::setCommandWindow3(MouseCommand commandWindow3) { if (CmdWindow3 == commandWindow3) { return; } CmdWindow3 = commandWindow3; emit commandWindow3Changed(); } void Options::setCommandWindowWheel(MouseCommand commandWindowWheel) { if (CmdWindowWheel == commandWindowWheel) { return; } CmdWindowWheel = commandWindowWheel; emit commandWindowWheelChanged(); } void Options::setCommandAll1(MouseCommand commandAll1) { if (CmdAll1 == commandAll1) { return; } CmdAll1 = commandAll1; emit commandAll1Changed(); } void Options::setCommandAll2(MouseCommand commandAll2) { if (CmdAll2 == commandAll2) { return; } CmdAll2 = commandAll2; emit commandAll2Changed(); } void Options::setCommandAll3(MouseCommand commandAll3) { if (CmdAll3 == commandAll3) { return; } CmdAll3 = commandAll3; emit commandAll3Changed(); } void Options::setKeyCmdAllModKey(uint keyCmdAllModKey) { if (CmdAllModKey == keyCmdAllModKey) { return; } CmdAllModKey = keyCmdAllModKey; emit keyCmdAllModKeyChanged(); } void Options::setShowGeometryTip(bool showGeometryTip) { if (show_geometry_tip == showGeometryTip) { return; } show_geometry_tip = showGeometryTip; emit showGeometryTipChanged(); } void Options::setCondensedTitle(bool condensedTitle) { if (condensed_title == condensedTitle) { return; } condensed_title = condensedTitle; emit condensedTitleChanged(); } void Options::setElectricBorderMaximize(bool electricBorderMaximize) { if (electric_border_maximize == electricBorderMaximize) { return; } electric_border_maximize = electricBorderMaximize; emit electricBorderMaximizeChanged(); } void Options::setElectricBorderTiling(bool electricBorderTiling) { if (electric_border_tiling == electricBorderTiling) { return; } electric_border_tiling = electricBorderTiling; emit electricBorderTilingChanged(); } void Options::setElectricBorderCornerRatio(float electricBorderCornerRatio) { if (electric_border_corner_ratio == electricBorderCornerRatio) { return; } electric_border_corner_ratio = electricBorderCornerRatio; emit electricBorderCornerRatioChanged(); } void Options::setBorderlessMaximizedWindows(bool borderlessMaximizedWindows) { if (borderless_maximized_windows == borderlessMaximizedWindows) { return; } borderless_maximized_windows = borderlessMaximizedWindows; emit borderlessMaximizedWindowsChanged(); } void Options::setKillPingTimeout(int killPingTimeout) { if (m_killPingTimeout == killPingTimeout) { return; } m_killPingTimeout = killPingTimeout; emit killPingTimeoutChanged(); } void Options::setHideUtilityWindowsForInactive(bool hideUtilityWindowsForInactive) { if (m_hideUtilityWindowsForInactive == hideUtilityWindowsForInactive) { return; } m_hideUtilityWindowsForInactive = hideUtilityWindowsForInactive; emit hideUtilityWindowsForInactiveChanged(); } void Options::setCompositingMode(int compositingMode) { if (m_compositingMode == static_cast(compositingMode)) { return; } m_compositingMode = static_cast(compositingMode); emit compositingModeChanged(); } void Options::setUseCompositing(bool useCompositing) { if (m_useCompositing == useCompositing) { return; } m_useCompositing = useCompositing; emit useCompositingChanged(); } void Options::setHiddenPreviews(int hiddenPreviews) { if (m_hiddenPreviews == static_cast(hiddenPreviews)) { return; } m_hiddenPreviews = static_cast(hiddenPreviews); emit hiddenPreviewsChanged(); } void Options::setGlSmoothScale(int glSmoothScale) { if (m_glSmoothScale == glSmoothScale) { return; } m_glSmoothScale = glSmoothScale; emit glSmoothScaleChanged(); } void Options::setXrenderSmoothScale(bool xrenderSmoothScale) { if (m_xrenderSmoothScale == xrenderSmoothScale) { return; } m_xrenderSmoothScale = xrenderSmoothScale; emit xrenderSmoothScaleChanged(); } void Options::setMaxFpsInterval(qint64 maxFpsInterval) { if (m_maxFpsInterval == maxFpsInterval) { return; } m_maxFpsInterval = maxFpsInterval; emit maxFpsIntervalChanged(); } void Options::setRefreshRate(uint refreshRate) { if (m_refreshRate == refreshRate) { return; } m_refreshRate = refreshRate; emit refreshRateChanged(); } void Options::setVBlankTime(qint64 vBlankTime) { if (m_vBlankTime == vBlankTime) { return; } m_vBlankTime = vBlankTime; emit vBlankTimeChanged(); } void Options::setGlStrictBinding(bool glStrictBinding) { if (m_glStrictBinding == glStrictBinding) { return; } m_glStrictBinding = glStrictBinding; emit glStrictBindingChanged(); } void Options::setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver) { if (m_glStrictBindingFollowsDriver == glStrictBindingFollowsDriver) { return; } m_glStrictBindingFollowsDriver = glStrictBindingFollowsDriver; emit glStrictBindingFollowsDriverChanged(); } void Options::setGLCoreProfile(bool value) { if (m_glCoreProfile == value) { return; } m_glCoreProfile = value; emit glCoreProfileChanged(); } void Options::setWindowsBlockCompositing(bool value) { if (m_windowsBlockCompositing == value) { return; } m_windowsBlockCompositing = value; emit windowsBlockCompositingChanged(); } void Options::setGlPreferBufferSwap(char glPreferBufferSwap) { if (glPreferBufferSwap == 'a') { - // buffer cpying is very fast with the nvidia blob + // buffer copying is very fast with the nvidia blob // but due to restrictions in DRI2 *incredibly* slow for all MESA drivers - // see http://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5 + // see https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5 if (GLPlatform::instance()->driver() == Driver_NVidia) glPreferBufferSwap = CopyFrontBuffer; else if (GLPlatform::instance()->driver() != Driver_Unknown) // undetected, finally resolved when context is initialized glPreferBufferSwap = ExtendDamage; } if (m_glPreferBufferSwap == (GlSwapStrategy)glPreferBufferSwap) { return; } m_glPreferBufferSwap = (GlSwapStrategy)glPreferBufferSwap; emit glPreferBufferSwapChanged(); } void Options::setGlPlatformInterface(OpenGLPlatformInterface interface) { // check environment variable const QByteArray envOpenGLInterface(qgetenv("KWIN_OPENGL_INTERFACE")); if (!envOpenGLInterface.isEmpty()) { if (qstrcmp(envOpenGLInterface, "egl") == 0) { qCDebug(KWIN_CORE) << "Forcing EGL native interface through environment variable"; interface = EglPlatformInterface; } else if (qstrcmp(envOpenGLInterface, "glx") == 0) { qCDebug(KWIN_CORE) << "Forcing GLX native interface through environment variable"; interface = GlxPlatformInterface; } } if (kwinApp()->shouldUseWaylandForCompositing() && interface == GlxPlatformInterface) { // Glx is impossible on Wayland, enforce egl qCDebug(KWIN_CORE) << "Forcing EGL native interface for Wayland mode"; interface = EglPlatformInterface; } #if !HAVE_EPOXY_GLX qCDebug(KWIN_CORE) << "Forcing EGL native interface as compiled without GLX support"; interface = EglPlatformInterface; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { qCDebug(KWIN_CORE) << "Forcing EGL native interface as Qt uses OpenGL ES"; interface = EglPlatformInterface; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { qCDebug(KWIN_CORE) << "Forcing EGL native interface as OpenGL ES requested through KWIN_COMPOSE environment variable."; interface = EglPlatformInterface; } if (m_glPlatformInterface == interface) { return; } m_glPlatformInterface = interface; emit glPlatformInterfaceChanged(); } void Options::reparseConfiguration() { m_settings->config()->reparseConfiguration(); } void Options::updateSettings() { loadConfig(); // Read button tooltip animation effect from kdeglobals // Since we want to allow users to enable window decoration tooltips // and not kstyle tooltips and vise-versa, we don't read the // "EffectNoTooltip" setting from kdeglobals. // QToolTip::setGloballyEnabled( d->show_tooltips ); // KDE4 this probably needs to be done manually in clients // Driver-specific config detection reloadCompositingSettings(); emit configChanged(); } void Options::loadConfig() { m_settings->load(); syncFromKcfgc(); // Electric borders KConfigGroup config(m_settings->config(), "Windows"); OpTitlebarDblClick = windowOperation(config.readEntry("TitlebarDoubleClickCommand", "Maximize"), true); setOperationMaxButtonLeftClick(windowOperation(config.readEntry("MaximizeButtonLeftClickCommand", "Maximize"), true)); setOperationMaxButtonMiddleClick(windowOperation(config.readEntry("MaximizeButtonMiddleClickCommand", "Maximize (vertical only)"), true)); setOperationMaxButtonRightClick(windowOperation(config.readEntry("MaximizeButtonRightClickCommand", "Maximize (horizontal only)"), true)); // Mouse bindings config = KConfigGroup(m_settings->config(), "MouseBindings"); // TODO: add properties for missing options CmdTitlebarWheel = mouseWheelCommand(config.readEntry("CommandTitlebarWheel", "Nothing")); CmdAllModKey = (config.readEntry("CommandAllKey", "Alt") == QStringLiteral("Meta")) ? Qt::Key_Meta : Qt::Key_Alt; CmdAllWheel = mouseWheelCommand(config.readEntry("CommandAllWheel", "Nothing")); setCommandActiveTitlebar1(mouseCommand(config.readEntry("CommandActiveTitlebar1", "Raise"), true)); setCommandActiveTitlebar2(mouseCommand(config.readEntry("CommandActiveTitlebar2", "Nothing"), true)); setCommandActiveTitlebar3(mouseCommand(config.readEntry("CommandActiveTitlebar3", "Operations menu"), true)); setCommandInactiveTitlebar1(mouseCommand(config.readEntry("CommandInactiveTitlebar1", "Activate and raise"), true)); setCommandInactiveTitlebar2(mouseCommand(config.readEntry("CommandInactiveTitlebar2", "Nothing"), true)); setCommandInactiveTitlebar3(mouseCommand(config.readEntry("CommandInactiveTitlebar3", "Operations menu"), true)); setCommandWindow1(mouseCommand(config.readEntry("CommandWindow1", "Activate, raise and pass click"), false)); setCommandWindow2(mouseCommand(config.readEntry("CommandWindow2", "Activate and pass click"), false)); setCommandWindow3(mouseCommand(config.readEntry("CommandWindow3", "Activate and pass click"), false)); setCommandWindowWheel(mouseCommand(config.readEntry("CommandWindowWheel", "Scroll"), false)); setCommandAll1(mouseCommand(config.readEntry("CommandAll1", "Move"), false)); setCommandAll2(mouseCommand(config.readEntry("CommandAll2", "Toggle raise and lower"), false)); setCommandAll3(mouseCommand(config.readEntry("CommandAll3", "Resize"), false)); // TODO: should they be moved into reloadCompositingSettings? config = KConfigGroup(m_settings->config(), "Compositing"); setMaxFpsInterval(1 * 1000 * 1000 * 1000 / config.readEntry("MaxFPS", Options::defaultMaxFps())); setRefreshRate(config.readEntry("RefreshRate", Options::defaultRefreshRate())); setVBlankTime(config.readEntry("VBlankTime", Options::defaultVBlankTime()) * 1000); // config in micro, value in nano resolution // Modifier Only Shortcuts config = KConfigGroup(m_settings->config(), "ModifierOnlyShortcuts"); m_modifierOnlyShortcuts.clear(); if (config.hasKey("Shift")) { m_modifierOnlyShortcuts.insert(Qt::ShiftModifier, config.readEntry("Shift", QStringList())); } if (config.hasKey("Control")) { m_modifierOnlyShortcuts.insert(Qt::ControlModifier, config.readEntry("Control", QStringList())); } if (config.hasKey("Alt")) { m_modifierOnlyShortcuts.insert(Qt::AltModifier, config.readEntry("Alt", QStringList())); } m_modifierOnlyShortcuts.insert(Qt::MetaModifier, config.readEntry("Meta", QStringList{QStringLiteral("org.kde.plasmashell"), QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("activateLauncherMenu")})); } void Options::syncFromKcfgc() { setShowGeometryTip(m_settings->geometryTip()); setCondensedTitle(m_settings->condensedTitle()); setFocusPolicy(m_settings->focusPolicy()); setNextFocusPrefersMouse(m_settings->nextFocusPrefersMouse()); setSeparateScreenFocus(m_settings->separateScreenFocus()); setRollOverDesktops(m_settings->rollOverDesktops()); setFocusStealingPreventionLevel(m_settings->focusStealingPreventionLevel()); #ifdef KWIN_BUILD_DECORATIONS setPlacement(m_settings->placement()); #else setPlacement(Placement::Maximizing); #endif setAutoRaise(m_settings->autoRaise()); setAutoRaiseInterval(m_settings->autoRaiseInterval()); setDelayFocusInterval(m_settings->delayFocusInterval()); setShadeHover(m_settings->shadeHover()); setShadeHoverInterval(m_settings->shadeHoverInterval()); setClickRaise(m_settings->clickRaise()); setBorderSnapZone(m_settings->borderSnapZone()); setWindowSnapZone(m_settings->windowSnapZone()); setCenterSnapZone(m_settings->centerSnapZone()); setSnapOnlyWhenOverlapping(m_settings->snapOnlyWhenOverlapping()); setKillPingTimeout(m_settings->killPingTimeout()); setHideUtilityWindowsForInactive(m_settings->hideUtilityWindowsForInactive()); setBorderlessMaximizedWindows(m_settings->borderlessMaximizedWindows()); setElectricBorderMaximize(m_settings->electricBorderMaximize()); setElectricBorderTiling(m_settings->electricBorderTiling()); setElectricBorderCornerRatio(m_settings->electricBorderCornerRatio()); setWindowsBlockCompositing(m_settings->windowsBlockCompositing()); } bool Options::loadCompositingConfig (bool force) { KConfigGroup config(m_settings->config(), "Compositing"); bool useCompositing = false; CompositingType compositingMode = NoCompositing; QString compositingBackend = config.readEntry("Backend", "OpenGL"); if (compositingBackend == QStringLiteral("XRender")) compositingMode = XRenderCompositing; else if (compositingBackend == "QPainter") compositingMode = QPainterCompositing; else compositingMode = OpenGLCompositing; if (const char *c = getenv("KWIN_COMPOSE")) { switch(c[0]) { case 'O': qCDebug(KWIN_CORE) << "Compositing forced to OpenGL mode by environment variable"; compositingMode = OpenGLCompositing; useCompositing = true; break; case 'X': qCDebug(KWIN_CORE) << "Compositing forced to XRender mode by environment variable"; compositingMode = XRenderCompositing; useCompositing = true; break; case 'Q': qCDebug(KWIN_CORE) << "Compositing forced to QPainter mode by environment variable"; compositingMode = QPainterCompositing; useCompositing = true; break; case 'N': if (getenv("KDE_FAILSAFE")) qCDebug(KWIN_CORE) << "Compositing disabled forcefully by KDE failsafe mode"; else qCDebug(KWIN_CORE) << "Compositing disabled forcefully by environment variable"; compositingMode = NoCompositing; break; default: qCDebug(KWIN_CORE) << "Unknown KWIN_COMPOSE mode set, ignoring"; break; } } setCompositingMode(compositingMode); const bool platformSupportsNoCompositing = kwinApp()->platform()->supportedCompositors().contains(NoCompositing); if (m_compositingMode == NoCompositing && platformSupportsNoCompositing) { setUseCompositing(false); return false; // do not even detect compositing preferences if explicitly disabled } // it's either enforced by env or by initial resume from "suspend" or we check the settings setUseCompositing(useCompositing || force || config.readEntry("Enabled", Options::defaultUseCompositing() || !platformSupportsNoCompositing)); if (!m_useCompositing) return false; // not enforced or necessary and not "enabled" by settings return true; } void Options::reloadCompositingSettings(bool force) { if (!loadCompositingConfig(force)) { return; } m_settings->load(); syncFromKcfgc(); // Compositing settings KConfigGroup config(m_settings->config(), "Compositing"); setGlSmoothScale(qBound(-1, config.readEntry("GLTextureFilter", Options::defaultGlSmoothScale()), 2)); setGlStrictBindingFollowsDriver(!config.hasKey("GLStrictBinding")); if (!isGlStrictBindingFollowsDriver()) { setGlStrictBinding(config.readEntry("GLStrictBinding", Options::defaultGlStrictBinding())); } setGLCoreProfile(config.readEntry("GLCore", Options::defaultGLCoreProfile())); char c = 'a'; const QString s = config.readEntry("GLPreferBufferSwap", QString(Options::defaultGlPreferBufferSwap())); if (!s.isEmpty()) c = s.at(0).toLatin1(); if (c != 'a' && c != 'c' && c != 'p' && c != 'e') c = 'a'; setGlPreferBufferSwap(c); m_xrenderSmoothScale = config.readEntry("XRenderSmoothScale", false); HiddenPreviews previews = Options::defaultHiddenPreviews(); // 4 - off, 5 - shown, 6 - always, other are old values int hps = config.readEntry("HiddenPreviews", 5); if (hps == 4) previews = HiddenPreviewsNever; else if (hps == 5) previews = HiddenPreviewsShown; else if (hps == 6) previews = HiddenPreviewsAlways; setHiddenPreviews(previews); auto interfaceToKey = [](OpenGLPlatformInterface interface) { switch (interface) { case GlxPlatformInterface: return QStringLiteral("glx"); case EglPlatformInterface: return QStringLiteral("egl"); default: return QString(); } }; auto keyToInterface = [](const QString &key) { if (key == QStringLiteral("glx")) { return GlxPlatformInterface; } else if (key == QStringLiteral("egl")) { return EglPlatformInterface; } return defaultGlPlatformInterface(); }; setGlPlatformInterface(keyToInterface(config.readEntry("GLPlatformInterface", interfaceToKey(m_glPlatformInterface)))); } // restricted should be true for operations that the user may not be able to repeat // if the window is moved out of the workspace (e.g. if the user moves a window // by the titlebar, and moves it too high beneath Kicker at the top edge, they // may not be able to move it back, unless they know about Alt+LMB) Options::WindowOperation Options::windowOperation(const QString &name, bool restricted) { if (name == QStringLiteral("Move")) return restricted ? MoveOp : UnrestrictedMoveOp; else if (name == QStringLiteral("Resize")) return restricted ? ResizeOp : UnrestrictedResizeOp; else if (name == QStringLiteral("Maximize")) return MaximizeOp; else if (name == QStringLiteral("Minimize")) return MinimizeOp; else if (name == QStringLiteral("Close")) return CloseOp; else if (name == QStringLiteral("OnAllDesktops")) return OnAllDesktopsOp; else if (name == QStringLiteral("Shade")) return ShadeOp; else if (name == QStringLiteral("Operations")) return OperationsOp; else if (name == QStringLiteral("Maximize (vertical only)")) return VMaximizeOp; else if (name == QStringLiteral("Maximize (horizontal only)")) return HMaximizeOp; else if (name == QStringLiteral("Lower")) return LowerOp; return NoOp; } Options::MouseCommand Options::mouseCommand(const QString &name, bool restricted) { QString lowerName = name.toLower(); if (lowerName == QStringLiteral("raise")) return MouseRaise; if (lowerName == QStringLiteral("lower")) return MouseLower; if (lowerName == QStringLiteral("operations menu")) return MouseOperationsMenu; if (lowerName == QStringLiteral("toggle raise and lower")) return MouseToggleRaiseAndLower; if (lowerName == QStringLiteral("activate and raise")) return MouseActivateAndRaise; if (lowerName == QStringLiteral("activate and lower")) return MouseActivateAndLower; if (lowerName == QStringLiteral("activate")) return MouseActivate; if (lowerName == QStringLiteral("activate, raise and pass click")) return MouseActivateRaiseAndPassClick; if (lowerName == QStringLiteral("activate and pass click")) return MouseActivateAndPassClick; if (lowerName == QStringLiteral("scroll")) return MouseNothing; if (lowerName == QStringLiteral("activate and scroll")) return MouseActivateAndPassClick; if (lowerName == QStringLiteral("activate, raise and scroll")) return MouseActivateRaiseAndPassClick; if (lowerName == QStringLiteral("activate, raise and move")) return restricted ? MouseActivateRaiseAndMove : MouseActivateRaiseAndUnrestrictedMove; if (lowerName == QStringLiteral("move")) return restricted ? MouseMove : MouseUnrestrictedMove; if (lowerName == QStringLiteral("resize")) return restricted ? MouseResize : MouseUnrestrictedResize; if (lowerName == QStringLiteral("shade")) return MouseShade; if (lowerName == QStringLiteral("minimize")) return MouseMinimize; if (lowerName == QStringLiteral("close")) return MouseClose; if (lowerName == QStringLiteral("increase opacity")) return MouseOpacityMore; if (lowerName == QStringLiteral("decrease opacity")) return MouseOpacityLess; if (lowerName == QStringLiteral("nothing")) return MouseNothing; return MouseNothing; } Options::MouseWheelCommand Options::mouseWheelCommand(const QString &name) { QString lowerName = name.toLower(); if (lowerName == QStringLiteral("raise/lower")) return MouseWheelRaiseLower; if (lowerName == QStringLiteral("shade/unshade")) return MouseWheelShadeUnshade; if (lowerName == QStringLiteral("maximize/restore")) return MouseWheelMaximizeRestore; if (lowerName == QStringLiteral("above/below")) return MouseWheelAboveBelow; if (lowerName == QStringLiteral("previous/next desktop")) return MouseWheelPreviousNextDesktop; if (lowerName == QStringLiteral("change opacity")) return MouseWheelChangeOpacity; if (lowerName == QStringLiteral("nothing")) return MouseWheelNothing; return MouseWheelNothing; } bool Options::showGeometryTip() const { return show_geometry_tip; } bool Options::condensedTitle() const { return condensed_title; } Options::MouseCommand Options::wheelToMouseCommand(MouseWheelCommand com, int delta) const { switch(com) { case MouseWheelRaiseLower: return delta > 0 ? MouseRaise : MouseLower; case MouseWheelShadeUnshade: return delta > 0 ? MouseSetShade : MouseUnsetShade; case MouseWheelMaximizeRestore: return delta > 0 ? MouseMaximize : MouseRestore; case MouseWheelAboveBelow: return delta > 0 ? MouseAbove : MouseBelow; case MouseWheelPreviousNextDesktop: return delta > 0 ? MousePreviousDesktop : MouseNextDesktop; case MouseWheelChangeOpacity: return delta > 0 ? MouseOpacityMore : MouseOpacityLess; default: return MouseNothing; } } #endif double Options::animationTimeFactor() const { #ifndef KCMRULES return m_settings->animationDurationFactor(); #else return 0; #endif } Options::WindowOperation Options::operationMaxButtonClick(Qt::MouseButtons button) const { return button == Qt::RightButton ? opMaxButtonRightClick : button == Qt::MidButton ? opMaxButtonMiddleClick : opMaxButtonLeftClick; } QStringList Options::modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const { return m_modifierOnlyShortcuts.value(mod); } bool Options::isUseCompositing() const { return m_useCompositing || kwinApp()->platform()->requiresCompositing(); } } // namespace diff --git a/plugins/platforms/virtual/egl_gbm_backend.cpp b/plugins/platforms/virtual/egl_gbm_backend.cpp index 6bceaee35..80514311d 100644 --- a/plugins/platforms/virtual/egl_gbm_backend.cpp +++ b/plugins/platforms/virtual/egl_gbm_backend.cpp @@ -1,251 +1,251 @@ /******************************************************************** 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 "egl_gbm_backend.h" // kwin #include "composite.h" #include "virtual_backend.h" #include "options.h" #include "screens.h" #include // kwin libs #include #include // Qt #include #ifndef EGL_PLATFORM_SURFACELESS_MESA #define EGL_PLATFORM_SURFACELESS_MESA 0x31DD #endif namespace KWin { EglGbmBackend::EglGbmBackend(VirtualBackend *b) : AbstractEglBackend() , m_backend(b) { // Egl is always direct rendering setIsDirectRendering(true); } EglGbmBackend::~EglGbmBackend() { while (GLRenderTarget::isRenderTargetBound()) { GLRenderTarget::popRenderTarget(); } delete m_fbo; delete m_backBuffer; cleanup(); } bool EglGbmBackend::initializeEgl() { initClientExtensions(); EGLDisplay display = m_backend->sceneEglDisplay(); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (display == EGL_NO_DISPLAY) { // first try surfaceless if (hasClientExtension(QByteArrayLiteral("EGL_MESA_platform_surfaceless"))) { display = eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr); } else { qCWarning(KWIN_VIRTUAL) << "Extension EGL_MESA_platform_surfaceless not available"; } } if (display == EGL_NO_DISPLAY) return false; setEglDisplay(display); return initEglAPI(); } void EglGbmBackend::init() { if (!initializeEgl()) { setFailed("Could not initialize egl"); return; } if (!initRenderingContext()) { setFailed("Could not initialize rendering context"); return; } initKWinGL(); m_backBuffer = new GLTexture(GL_RGB8, screens()->size().width(), screens()->size().height()); m_fbo = new GLRenderTarget(*m_backBuffer); if (!m_fbo->valid()) { setFailed("Could not create framebuffer object"); return; } GLRenderTarget::pushRenderTarget(m_fbo); if (!m_fbo->isRenderTargetBound()) { setFailed("Failed to bind framebuffer object"); return; } if (checkGLError("Init")) { setFailed("Error during init of EglGbmBackend"); return; } setSupportsBufferAge(false); initWayland(); } bool EglGbmBackend::initRenderingContext() { initBufferConfigs(); const char* eglExtensionsCString = eglQueryString(eglDisplay(), EGL_EXTENSIONS); const QList extensions = QByteArray::fromRawData(eglExtensionsCString, qstrlen(eglExtensionsCString)).split(' '); if (!extensions.contains(QByteArrayLiteral("EGL_KHR_surfaceless_context"))) { return false; } if (!createContext()) { return false; } setSurfaceLessContext(true); return makeCurrent(); } bool EglGbmBackend::initBufferConfigs() { const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 0, EGL_RENDERABLE_TYPE, isOpenGLES() ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE, }; EGLint count; EGLConfig configs[1024]; if (eglChooseConfig(eglDisplay(), config_attribs, configs, 1, &count) == EGL_FALSE) { return false; } if (count != 1) { return false; } setConfig(configs[0]); return true; } void EglGbmBackend::present() { Compositor::self()->aboutToSwapBuffers(); eglSwapBuffers(eglDisplay(), surface()); setLastDamage(QRegion()); Compositor::self()->bufferSwapComplete(); } void EglGbmBackend::screenGeometryChanged(const QSize &size) { Q_UNUSED(size) // TODO, create new buffer? } SceneOpenGLTexturePrivate *EglGbmBackend::createBackendTexture(SceneOpenGLTexture *texture) { return new EglGbmTexture(texture, this); } QRegion EglGbmBackend::prepareRenderingFrame() { if (!lastDamage().isEmpty()) { present(); } startRenderTimer(); if (!GLRenderTarget::isRenderTargetBound()) { GLRenderTarget::pushRenderTarget(m_fbo); } return QRegion(0, 0, screens()->size().width(), screens()->size().height()); } static void convertFromGLImage(QImage &img, int w, int h) { // from QtOpenGL/qgl.cpp // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) - // see http://qt.gitorious.org/qt/qt/blobs/master/src/opengl/qgl.cpp + // see https://github.com/qt/qtbase/blob/dev/src/opengl/qgl.cpp if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // OpenGL gives RGBA; Qt wants ARGB uint *p = (uint*)img.bits(); uint *end = p + w * h; while (p < end) { uint a = *p << 24; *p = (*p >> 8) | a; p++; } } else { // OpenGL gives ABGR (i.e. RGBA backwards); Qt wants ARGB for (int y = 0; y < h; y++) { uint *q = (uint*)img.scanLine(y); for (int x = 0; x < w; ++x) { const uint pixel = *q; *q = ((pixel << 16) & 0xff0000) | ((pixel >> 16) & 0xff) | (pixel & 0xff00ff00); q++; } } } img = img.mirrored(); } void EglGbmBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { Q_UNUSED(damagedRegion) glFlush(); if (m_backend->saveFrames()) { QImage img = QImage(QSize(m_backBuffer->width(), m_backBuffer->height()), QImage::Format_ARGB32); glReadnPixels(0, 0, m_backBuffer->width(), m_backBuffer->height(), GL_RGBA, GL_UNSIGNED_BYTE, img.sizeInBytes(), (GLvoid*)img.bits()); convertFromGLImage(img, m_backBuffer->width(), m_backBuffer->height()); img.save(QStringLiteral("%1/%2.png").arg(m_backend->saveFrames()).arg(QString::number(m_frameCounter++))); } GLRenderTarget::popRenderTarget(); setLastDamage(renderedRegion); } bool EglGbmBackend::usesOverlayWindow() const { return false; } /************************************************ * EglTexture ************************************************/ EglGbmTexture::EglGbmTexture(KWin::SceneOpenGLTexture *texture, EglGbmBackend *backend) : AbstractEglTexture(texture, backend) { } EglGbmTexture::~EglGbmTexture() = default; } // namespace